Как рассчитать квартили по группам?

Допустим, у меня есть стол

VAL     PERSON
  1          1
  2          1
  3          1
  4          1
  2          2
  4          2
  6          2
  3          3
  6          3
  9          3
  12         3
  15         3

И я хотел бы рассчитать квартили для каждого человека.

Я понимаю, что могу легко рассчитать их для одного человека как такового:

SELECT 
    VAL,
    NTILE(4) OVER(ORDER BY VAL) AS QUARTILE
WHERE PERSON = 1;

Получите мне желаемые результаты:

VAL    QUARTILE
1      1
2      2
3      3
4      4

Проблема в том, что я хотел бы сделать это для каждого человека. Я знаю, что-то вроде этого сделало бы работу:

SELECT 
    PERSON,
    VAL,
    NTILE(4) OVER(ORDER BY VAL) AS QUARTILE
WHERE PERSON = 1
UNION
SELECT 
    PERSON,
    VAL,
    NTILE(4) OVER(ORDER BY VAL) AS QUARTILE
WHERE PERSON = 2
UNION
SELECT 
    PERSON,
    VAL,
    NTILE(4) OVER(ORDER BY VAL) AS QUARTILE
WHERE PERSON = 3
UNION
SELECT 
    PERSON,
    VAL,
    NTILE(4) OVER(ORDER BY VAL) AS QUARTILE
WHERE PERSON = 4

Но что, если на столе новый человек? Тогда мне придется изменить код SQL. Какие-либо предложения?

2 ответа

Почему бы вам не попробовать использовать раздел по.

SELECT 
  PERSON,
  VAL,
  NTILE(4) OVER(PARTITION BY PERSON ORDER BY VAL) AS QUARTILE;
FROM TABLE 

Привет

ntile() не очень хорошо справляется со связями Вы можете легко увидеть это на примере:

select v.x, ntile(2) over (order by x) as tile
from (values (1), (1), (1), (1)) v(x);

который возвращает:

x tile
1   1
1   1
1   2
1   2

То же значение Разные плитки. Это ухудшается, если вы отслеживаете, в каком тайле находится значение. Разные строки могут иметь разные тайлы в разных прогонах одного и того же запроса - даже если данные не меняются.

Обычно вы хотите, чтобы строки с одинаковым значением имели одинаковый квартиль, даже если плитки не одного размера. По этой причине я рекомендую явный расчет, используя rank() вместо:

select t.*,
       ((seqnum - 1) * 4 / cnt) + 1 as quartile
from (select t.*,
             rank() over (partition by person order by val) as seqnum,
             count(*) over (partition by person) as cnt
      from t
     ) t;

Если вы действительно хотите, чтобы значения были разбиты по плиткам, используйте row_number() скорее, чем rank(),

Другие вопросы по тегам