N045-M3 Tier 4 · Advanced · medium hr · Helix Systems

Return the 25th-percentile, 50th-percentile, and 75th-percentile actual-value salary across every current pay record as a single row

Part of NTILE and Percentile Functions in SQL

The problem

Helix Systems' HR director needs three salary benchmarks from current pay records to calibrate compensation bands.

Write a query to return the 25th-percentile, 50th-percentile, and 75th-percentile actual-value salary across every current pay record as a single row.

Assumptions:

  • The salaries table has one row per pay record with an amount and an end_date.
  • A current pay record has a missing end_date. Only current pay records contribute to the benchmarks.
  • Each percentile threshold is the actual amount value at or above that percentile position when the values are sorted ascending — the result is always one of the actual salary amounts in the data.

Output:

  • A single row with columns p25_salary, p50_salary, and p75_salary.
Schema · hr 4 tables
departments
id integer
name text
location text
budget numeric
salaries
id integer
employee_id integer
amount numeric
effective_date date
end_date? date
employees
id integer
name text
email text
department_id integer
manager_id? integer
hire_date date
title text
is_active boolean
job_history
id integer
employee_id integer
title text
department_id integer
start_date date
end_date? date

Run previews · Check grades

Write a query, then run it to see results here.

Worked solution Try it yourself first
Solution query
SELECT
  PERCENTILE_DISC(0.25) WITHIN GROUP (
    ORDER BY
      amount
  ) AS p25_salary,
  PERCENTILE_DISC(0.5) WITHIN GROUP (
    ORDER BY
      amount
  ) AS p50_salary,
  PERCENTILE_DISC(0.75) WITHIN GROUP (
    ORDER BY
      amount
  ) AS p75_salary
FROM
  salaries
WHERE
  end_date IS NULL

The shape

Three PERCENTILE_DISC calls on the same column at three percentile positions, filtered to current pay records, return the 25th, 50th, and 75th salary thresholds on a single row. PERCENTILE_DISC always returns an actual salary from the data, which is the HR director's requirement: the benchmarks have to be real numbers that exist in someone's pay record, not interpolated values.

Clause by clause

  • SELECT PERCENTILE_DISC(0.25) WITHIN GROUP (ORDER BY amount) AS p25_salary, PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY amount) AS p50_salary, PERCENTILE_DISC(0.75) WITHIN GROUP (ORDER BY amount) AS p75_salary returns the three thresholds in a single row. Each PERCENTILE_DISC walks the same sorted set of amount values; each picks the actual value at or just above its percentile position. The three calls run independently, but they share the same sort.
  • FROM salaries is the row source.
  • WHERE end_date IS NULL keeps only the current pay records. A NULL end_date is the marker for "still active"; any row with a non-NULL end_date is a historical record and is excluded before the percentile calculations run.

Why PERCENTILE_DISC and not PERCENTILE_CONT

The prompt is explicit: each benchmark must be one of the actual salary amounts in the data. PERCENTILE_CONT would interpolate between two surrounding salaries at the 25th, 50th, and 75th positions, and the interpolated value would typically not match any real salary in the table. PERCENTILE_DISC always returns a value that exists in the data, which is what calibrating compensation bands to real pay records requires.

The trap

WHERE end_date IS NULL is doing load-bearing work. NULL means "no end date set, so the pay record is current." Comparing with = instead of IS NULL would return no rows, because nothing equals NULL in SQL. If the filter is left off entirely, the percentile calculations include every historical pay record alongside the current ones, and the benchmarks shift to reflect a much larger population than the HR director intended.

You practiced PERCENTILE_DISC at multiple percentile positions over a pre-restricted record set — three thresholds in one query, each returning a value drawn from the input data.

How you actually get good at SQL

Reading explains SQL. Writing it, over and over with instant feedback, is what makes you fluent.

That's the whole SQLMaxx loop: 600+ real problems, instant AI feedback, mastery you can actually see, and spaced review that won't let you forget.

A stack of SQL practice problem cards, the top card showing an employees table.
615 problems · 66 concepts

Real problems. Not toy examples.

615 hand-built problems spanning all 66 concepts, from basic SELECTs to window functions, built on real schemas and real business questions, the kind you'll actually get asked on the job. Enough reps to make SQL automatic.

A retro computer showing a SQL query marked correct with a green checkmark.
Instant AI feedback

Write a query. Know if it's right in one second.

No copying an answer and hoping it clicked. The AI grader checks your real query against real data, catches exactly what's wrong, and explains the fix in plain English, like a senior analyst reading over your shoulder on every problem.

A circular mastery progress dial filling from blue to green, the SQLMaxx diamond at its center.
Mastery tracking

Stop guessing whether you actually know it.

SQLMaxx tracks every concept and shows you what you've mastered and what's still shaky. Your skills fill in one concept at a time, so 'I think I get joins' becomes something you can prove.

A SQL query editor circled by a blue return arrow with a clock, scheduled to come back for review.
Spaced review

Learn it once. Keep it for good.

Most of what you learn this week fades by next week. So when a concept comes due for review, SQLMaxx hands you a fresh problem to solve from a blank editor, not a flashcard to re-read. A research-backed spaced-repetition algorithm (FSRS) times each return for right before you'd forget, so your SQL is still there months later, when the interview or the job actually needs it.

Practice, feedback, mastery, review. That's the loop that turns reading into real skill.

Start free

No account, no credit card. Start solving in under a minute.