SQL:агрегатная функция и группировка по
Вопрос
Рассмотрим оракула emp
стол.Я хотел бы получить сотрудников с максимальной зарплатой с department = 20
и job = clerk
.Также предположим, что столбца «empno» нет и что первичный ключ включает в себя несколько столбцов.Вы можете сделать это с помощью:
select * from scott.emp
where deptno = 20 and job = 'CLERK'
and sal = (select max(sal) from scott.emp
where deptno = 20 and job = 'CLERK')
Это работает, но мне приходится дублировать test deptno = 20 и job = 'CLERK', чего мне хотелось бы избежать.Есть ли более элегантный способ написать это, возможно, используя group by
?Кстати, если это имеет значение, я использую Oracle.
Решение
Следующий пример несколько усложнен, но является хорошим шаблоном SQL для запросов «top x».
SELECT
*
FROM
scott.emp
WHERE
(deptno,job,sal) IN
(SELECT
deptno,
job,
max(sal)
FROM
scott.emp
WHERE
deptno = 20
and job = 'CLERK'
GROUP BY
deptno,
job
)
Также обратите внимание, что это будет работать в Oracle и Postgress (я думаю), но не в MS SQL.Что-то подобное в MS SQL см. в вопросе SQL-запрос для получения последней цены
Другие советы
Если бы я был уверен в целевой базе данных, я бы выбрал решение Марка Нольда, но если вам когда-нибудь понадобится какой-нибудь диалектно-независимый SQL *, попробуйте
SELECT *
FROM scott.emp e
WHERE e.deptno = 20
AND e.job = 'CLERK'
AND e.sal = (
SELECT MAX(e2.sal)
FROM scott.emp e2
WHERE e.deptno = e2.deptno
AND e.job = e2.job
)
*Я считаю, что это должно работать везде, но у меня нет среды для тестирования.
В Oracle я бы сделал это с помощью аналитической функции, чтобы вы запрашивали таблицу emp только один раз:
SELECT *
FROM (SELECT e.*, MAX (sal) OVER () AS max_sal
FROM scott.emp e
WHERE deptno = 20
AND job = 'CLERK')
WHERE sal = max_sal
Это проще, легче читать и эффективнее.
Если вы хотите изменить его, чтобы перечислить эту информацию для всех отделов, вам нужно будет использовать предложение «PARTITION BY» в OVER:
SELECT *
FROM (SELECT e.*, MAX (sal) OVER (PARTITION BY deptno) AS max_sal
FROM scott.emp e
WHERE job = 'CLERK')
WHERE sal = max_sal
ORDER BY deptno
Замечательно!Я не знал, что можно сравнивать (x, y, z) с результатом оператора SELECT.Это прекрасно работает с Oracle.
В качестве примечания для других читателей: в приведенном выше запросе отсутствует знак «=" после «(deptno,job,sal)».Может быть, форматтер Stack Overflow съел это (?).
Еще раз спасибо, Марк.
В Oracle вы также можете использовать оператор EXISTS, который в некоторых случаях работает быстрее.
Например...Выберите имя, номер из CUSH, где CUSH в (выберите cust_id из big_table) и введен> sysdate -1 будет медленным.
Но выберите имя, номер из CUST сГде существует (выберите cust_id из big_table ГДЕ cust_id=c.cust_id ) И введенный> sysdate -1 был бы очень быстрым с надлежащей индексацией.Вы также можете использовать это с несколькими параметрами.
Есть много решений.Вы также можете сохранить исходный макет запроса, просто добавив псевдонимы таблиц и объединив имена столбцов, но в запросе все равно будут только один раз DEPTNO = 20 и JOB = 'CLERK'.
SELECT
*
FROM
scott.emp emptbl
WHERE
emptbl.DEPTNO = 20
AND emptbl.JOB = 'CLERK'
AND emptbl.SAL =
(
select
max(salmax.SAL)
from
scott.emp salmax
where
salmax.DEPTNO = emptbl.DEPTNO
AND salmax.JOB = emptbl.JOB
)
Также можно отметить, что для этих типов запросов можно использовать ключевое слово «ВСЕ», что позволит вам удалить функцию «МАКС».
SELECT
*
FROM
scott.emp emptbl
WHERE
emptbl.DEPTNO = 20
AND emptbl.JOB = 'CLERK'
AND emptbl.SAL >= ALL
(
select
salmax.SAL
from
scott.emp salmax
where
salmax.DEPTNO = emptbl.DEPTNO
AND salmax.JOB = emptbl.JOB
)
Надеюсь, это поможет и имеет смысл.