Project: YugabyteDB (App Definition and Development / Database)
File: rank-dense-rank-percent-rank-cume-dist.md

title: rank(), denserank(), percentrank(), cume_dist() linkTitle: rank(), denserank(), percentrank(), cume_dist() headerTitle: Within-group hypothetical-set aggregate functions description: Describes the within-group hypothetical-set functionality of the rank(), denserank(), percentrank(), cume_dist() YSQL aggregate functions menu: v2.18: identifier: rank-dense-rank-percent-rank-cume-dist parent: aggregate-function-syntax-semantics weight: 100 type: docs This section describes the uses of , , , and as "within-group hypothetical-set" aggregate functions. The same functions can also be invoked as window functions. That use is described here: The basic semantic definition of each function is the same in each invocation scenario. But the goals of the two invocation methods are critically different. Syntax example: Suppose that table "t" can be populated like this: ``` insert into t(g1,... gN, v1,... vN) values (:a,... :b, :c,... :d); ``` The columns with names like "gN" represent the grouping columns and the columns with names like "vN" represent the columns whose values will be used to invoke the function. In the "within-group hypothetical-set" use, the function "f()" would be invoked like this: ``` select g1,... gN, f(:v1,... :vN) within group (order by v1,... vN) from t group by g1,... gN; ``` For each of these hypothetical-set aggregates, the list of actual arguments specified for the function must match the number and types of the column expressions specified following `ORDER BY`. Consider this counter-example: ``` select g1 f(:v1,... :vN) within group (order by v1,... vN) from t; ``` Notice that "g1" is not mentioned in a group by clause. It causes this error: ``` 42803: column "t.g1" must appear in the GROUP BY clause or be used in an aggregate function ``` Generic purpose: Suppose that "f()" is invoked as a window function like this: ``` select g1,... gN, v1,... vN, f() over(partition by g1,... gN order by v1,... vN) from t; ``` Imagine, hypothetically, that, now, a row is inserted with a particular set of values for "(g1,... gN, v1,... vN)", that the same `SELECT` is repeated, and that the value returned by "f()" for the new row is "x". The data type of "x" is function-specific. It's `bigint` for `rank()` and `denserank()`. And it's `double precision` for `percentrank()` and `cume_dist()`. This thought experiment can be trivially conducted in actuality simply by starting a transaction, inserting the new row, running the query, noting the result for the new row, and then rolling back. Suppose that, instead of doing the hypothetical insert, the function is invoked using the "within-group hypothetical-set" syntax shown above using the values for "(v1,... vN)" that were hypothetically used for the thought experiment. The result will be, for the hypothesized values for "(g1,... gN)", the same as would have been seen if the "start transaction;... rollback;" approach had been used. But, beyond this, values will be produced for each possible combination of values for "(g1,... gN)" that occur in the table at the moment that the `SELECT` statement is executed. (The expressions "g1,... gN" are those that are mentioned in the `GROUP BY` clause.) See the section. Unlike most built-in aggregate functions, these aggregate functions are not strictthat is they do not filter out rows containing `NULL`s. Rather, `NULL`s sort according to the rule specified in the `ORDER BY` clause. Signature: ``` input value: VARIADIC "any" ORDER BY VARIADIC "any" return value: bigint ``` Purpose: Returns the integer ordinal rank of each row according to the emergent order that the window `ORDER BY` clause specifies. The series of values starts with 1 but, when the contains ties, the series is not

See the account of in the section for more information. Signature: ``` input value: VARIADIC "any" ORDER BY VARIADIC "any" return value: bigint ``` Purpose: Returns the integer ordinal rank of the distinct value of each row according to what the window `ORDER BY` clause specifies. The series of values starts with 1 and, even when the contains ties, the series is dense. See the account of in the section for more information. Signature: ``` input value: VARIADIC "any" ORDER BY VARIADIC "any" return value: double precision ``` Purpose: Returns the percentile rank of each row within the , with respect to the argument of the 's window `ORDER BY` clause. The value p returned by `percentrank()` is a number in the range 0 <= p <= 1_. It is calculated like this: ``` percentile_rank = (rank - 1) / ("no. of rows in window" - 1) ``` See the account of in the section for more information. ``` input value: VARIADIC "any" ORDER BY VARIADIC "any" return value: double precision ``` Purpose: Returns a value that represents the number of rows with values less than or equal to the current rows value divided by the total number of rowsin other words, the relative position of a value in a set of values. The graph of all values of `cumedist()` within the is known as the cumulative distribution of the argument of the 's window `ORDER BY` clause. The value c returned by `cumedist()` is a number in the range 0 <= c <= 1. It is calculated like this: ``` cume_dist() = "no of rows with a value <= the current row's value" / "no. of rows in window" ``` See the account of in the section for more information. First, create and populate the test table: ```plpgsql drop table if exists t cascade; create table t( k int primary key, class int not null, score int); insert into t(k, class, score) with a as ( select s.v from generate_series(1, 10) as s(v)) values(0, 1, null::int) union all select v, ntile(2) over (order by v), case v <= 5 when true then v*2 else (v - 5)*2 end from a; \pset null <null> select class, score from t order by class, score nulls first; ``` This is the result: ``` class | score -+-- 1 | <null> 1 | 2 1 | 4 1 | 6 1 | 8 1 | 10 2 | 2 2 | 4 2 | 6 2 | 8 2 | 10 ``` Next, create a view defined by a `SELECT` statement that invokes the four functions of interest as window functions: ```plpgsql create or replace view v as select k, class, score, (rank() over w) as r, (dense_rank() over w) as dr, (percent_rank() over w) as pr, (cume_dist() over w) as cd from t window w as (partition by class order by score nulls first); ``` Visualize the results that the view defines: ```plpgsql select class, score, r, dr, tochar(pr, '90.99') as pr, tochar(cd, '90.99') as cd from v order by class, r; ``` This is the result: ``` class | score | r | dr | pr | cd -+--++-+--+-- 1 | <null> | 1 | 1 | 0.00 | 0.17 1 | 2 | 2 | 2 | 0.20 | 0.33 1 | 4 | 3 | 3 | 0.40 | 0.50 1 | 6 | 4 | 4 | 0.60 | 0.67 1 | 8 | 5 | 5 | 0.80 |

1 | 10 | 6 | 6 | 1.00 | 1.00 2 | 2 | 1 | 1 | 0.00 | 0.20 2 | 4 | 2 | 2 | 0.25 | 0.40 2 | 6 | 3 | 3 | 0.50 | 0.60 2 | 8 | 4 | 4 | 0.75 | 0.80 2 | 10 | 5 | 5 | 1.00 | 1.00 ``` Now, simulate the hypothetical insert of two new rows, one in each class, and visualize the values that the four functions of interest produce for them. Do this within a transaction that you rollback. ```plpgsql start transaction; insert into t(k, class, score) values (21, 1, 5), (22, 2, 6); select class, score, r, dr, tochar(pr, '90.99') as pr, tochar(cd, '90.99') as cd from v where k in (21, 22) order by class, r; rollback; ``` This is the result: ``` class | score | r | dr | pr | cd -+-++-+--+-- 1 | 5 | 4 | 4 | 0.50 | 0.57 2 | 6 | 3 | 3 | 0.40 | 0.67 ``` Next, create a table function, parameterized by the value of the hypothetical to-be-inserted score, to show the result of the "Within-group hypothetical-set" invocation of the four functions of interest: ```plpgsql drop function if exists h(int) cascade; create function h(hypothetical_score in int) returns table( class int, score int, r bigint, dr bigint, pr double precision, cd double precision) language sql as $body$ with v as ( select class, rank(hypothetical_score) within group (order by score nulls first) as r, denserank(hypotheticalscore) within group (order by score nulls first) as dr, percentrank(hypotheticalscore) within group (order by score nulls first) as pr, cumedist(hypotheticalscore) within group (order by score nulls first) as cd from t group by class) select class, hypothetical_score as score, r, dr, pr, cd from v; $body$; ``` First invoke it with a hypothetical score of 5: ```plpgsql select class, score, r, dr, tochar(pr, '90.99') as pr, tochar(cd, '90.99') as cd from h(5) order by class; ``` This is the result: ``` class | score | r | dr | pr | cd -+-++-+--+-- 1 | 5 | 4 | 4 | 0.50 | 0.57 2 | 5 | 3 | 3 | 0.40 | 0.50 ``` Now invoke it with a hypothetical score of 6: ```plpgsql select class, score, r, dr, tochar(pr, '90.99') as pr, tochar(cd, '90.99') as cd from h(6) order by class; ``` This is the result: ``` class | score | r | dr | pr | cd -+-++-+--+-- 1 | 6 | 4 | 4 | 0.50 | 0.71 2 | 6 | 3 | 3 | 0.40 | 0.67 ``` Notice that, as promised, each invocation produces two rowsone row for each of the two distinct values of "class". It's easy to produce the identical result that simulating the hypothetical inserts within a rolled back transaction produced: ```plpgsql with v as ( select class, score, r, dr, pr, cd from h(5) where class = 1 union all select class, score, r, dr, pr, cd from h(6) where class = 2) select class, score, r, dr, tochar(pr, '90.99') as pr, tochar(cd, '90.99') as cd from v order by class; ``` This is the result: ``` class | score | r | dr | pr | cd -+-++-+--+-- 1 | 5 | 4 | 4 | 0.50 | 0.57 2 | 6 | 3 | 3 | 0.40 | 0.67 ``` This is indeed identical to the result that the simulated two hypothetical inserts produced.