PostgreSQL. Особенности SQL: filter и distinct on.
Сначала создадим в схеме SCOTT табличку и заполним данными:
CREATE TABLE EMP (EMPNO integer NOT NULL, ENAME VARCHAR(10), JOB VARCHAR(9), MGR integer, HIREDATE DATE, SAL integer, COMM integer, DEPTNO integer)
INSERT INTO EMP VALUES (7369, 'SMITH', 'CLERK', 7902, '1980-12-17', 800, NULL, 20) INSERT INTO EMP VALUES (7499, 'ALLEN', 'SALESMAN', 7698, '1981-2-20', 1600, 300, 30) INSERT INTO EMP VALUES (7521, 'WARD', 'SALESMAN', 7698, '1981-2-22', 1250, 500, 30) INSERT INTO EMP VALUES (7566, 'JONES', 'MANAGER', 7839, '1981-4-2', 2975, NULL, 20) INSERT INTO EMP VALUES (7654, 'MARTIN', 'SALESMAN', 7698, '1981-9-28', 1250, 1400, 30) INSERT INTO EMP VALUES (7698, 'BLAKE', 'MANAGER', 7839, '1981-5-1', 2850, NULL, 30) INSERT INTO EMP VALUES (7782, 'CLARK', 'MANAGER', 7839, '1981-6-9', 2450, NULL, 10) INSERT INTO EMP VALUES (7788, 'SCOTT', 'ANALYST', 7566, '1982-12-9', 3000, NULL, 20) INSERT INTO EMP VALUES (7839, 'KING', 'PRESIDENT', NULL, '1981-11-17', 5000, NULL, 10) INSERT INTO EMP VALUES (7844, 'TURNER', 'SALESMAN', 7698, '1981-9-8', 1500, 0, 30) INSERT INTO EMP VALUES (7876, 'ADAMS', 'CLERK', 7788, '1983-1-12', 1100, NULL, 20) INSERT INTO EMP VALUES (7900, 'JAMES', 'CLERK', 7698, '1981-12-3', 950, NULL, 30) INSERT INTO EMP VALUES (7902, 'FORD', 'ANALYST', 7566, '1981-12-3', 3000, NULL, 20) INSERT INTO EMP VALUES (7934, 'MILLER', 'CLERK', 7782, '1982-1-23', 1300, NULL, 10)
DISTINCT ON
Допустим, хочется вывести информацию об одном работнике с наибольшим окладом в разрезе должности.
Можно сделать это с помощью оконной функции, например row_number - будет работать не только в PostgreSQL, но и в Oracle и в MS SQL Server.
with x as ( SELECT empno, ename, job, sal ,row_number () over (partition by job order by sal desc, empno asc) rn FROM scott.emp) select x.* from x where rn = 1
А можно воспользоваться присущей только PostgreSQL кляузой distinct on.
select distinct on (job) empno, ename, job, sal, 2 rn from scott.emp order by job, sal desc, empno asc
Смысл такой же - для каждой уникальной комбинации из списка значений, стоящих в скобках после distinct on - в нашем случае это одно поле - job, - возвращаем единственную запись. Порядок выбора этой записи указывается после order by job: sal desc, empno asc.
Если бы нам понадобилось найти данные уже в разрезе не только должности, но и номера департамента, то запрос выглядел бы так:
select distinct on (job, deptno) empno, ename, job, sal, deptno from scott.emp order by job, deptno, sal desc, empno asc
Обратите внимание, слева от order by теперь 2 поля - job, deptno - т.к. они в скобках после distinct on.
В книжке пишут, что такой способ обычно работает быстрее, чем традиционные. Насколько этот код проще для понимания - видимо, дело привычки.
FILTER
Допустим, на этом же примере мы хотим посчитать в разрезе департаментов число сотрудников с окладами менее 1000, от 1000 до 2000, и более 2000.
Используя стандарт, это можно так сделать:
select deptno ,count (case when sal < 1000 then 1 end) beggar_qt ,count (case when sal >= 1000 and sal < 2000 then 1 end) middle_qt ,count (case when sal >= 2000 then 1 end) high_qt from scott.emp group by deptno order by 1
Но можно для этого же воспользоваться кляузой PostgreSQL filter - в этот раз запрос действительно смотрится понятней.
select deptno ,count (*) filter (where sal < 1000) beggar_qt ,count (*) filter (where sal > 1000 and sal < 2000) middle_qt ,count (*) filter (where sal >= 2000 ) high_qt from scott.emp group by deptno order by 1
В книжке пишут, что "Кроме того, FILTER работает быстрее его имитации с помощью конструкции CASE WHEN ... THEN NULL ... ELSE END . Результаты сравнения на реальных данных можно найти по адресуhttps://www.cybertec-postgresql.com/en/postgresql-9-4-aggregation-filters-they-do-pay-off/" (Ссылка от 2015 года).
А ещё кляуза FILTER может использоваться в агрегирующих оконных функциях.