SQL:função agregada e grupo por
Pergunta
Considere o Oráculo emp
mesa.Eu gostaria de conseguir os funcionários com o salário mais alto com department = 20
e job = clerk
.Suponha também que não existe uma coluna "empno" e que a chave primária envolve várias colunas.Você pode fazer isso com:
select * from scott.emp
where deptno = 20 and job = 'CLERK'
and sal = (select max(sal) from scott.emp
where deptno = 20 and job = 'CLERK')
Isso funciona, mas tenho que duplicar o teste deptno = 20 e job = 'CLERK', o que gostaria de evitar.Existe uma maneira mais elegante de escrever isso, talvez usando um group by
?Aliás, se isso importa, estou usando o Oracle.
Solução
O seguinte é um pouco exagerado, mas é um bom padrão SQL para consultas "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
)
Observe também que isso funcionará no Oracle e no Postgress (eu acho), mas não no MS SQL.Para algo semelhante no MS SQL, consulte a pergunta Consulta SQL para obter o preço mais recente
Outras dicas
Se eu tivesse certeza do banco de dados de destino, escolheria a solução de Mark Nold, mas se você quiser algum SQL * agnóstico de dialeto, tente
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
)
*Acredito que isso deveria funcionar em qualquer lugar, mas não tenho ambientes para testá-lo.
No Oracle eu faria isso com uma função analítica, então você só consultaria a tabela emp uma vez:
SELECT *
FROM (SELECT e.*, MAX (sal) OVER () AS max_sal
FROM scott.emp e
WHERE deptno = 20
AND job = 'CLERK')
WHERE sal = max_sal
É mais simples, mais fácil de ler e mais eficiente.
Se quiser modificá-lo para listar essas informações para todos os departamentos, você precisará usar a cláusula "PARTITION BY" em 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
Isso é ótimo!Eu não sabia que você poderia fazer uma comparação de (x, y, z) com o resultado de uma instrução SELECT.Isso funciona muito bem com o Oracle.
Como observação para outros leitores, falta um "=" depois de "(deptno,job,sal)" na consulta acima.Talvez o formatador Stack Overflow tenha comido (?).
Mais uma vez, obrigado Marcos.
No Oracle você também pode usar a instrução EXISTS, que em alguns casos é mais rápida.
Por exemplo...Selecione o nome, número de custos onde custa (selecione cust_id de big_table) e digitado> sysdate -1 seria lento.
Mas selecione o nome, número de custos cOnde existe (selecione cust_id de big_table ONDE cust_id=c.cust_id ) E inserido> Sysdate -1 seria muito rápido com a indexação adequada.Você também pode usar isso com vários parâmetros.
Existem muitas soluções.Você também pode manter o layout de consulta original simplesmente adicionando aliases de tabela e juntando os nomes das colunas; você ainda teria DEPTNO = 20 e JOB = 'CLERK' na consulta uma vez.
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
)
Também se pode notar que a palavra-chave "ALL" pode ser usada para estes tipos de consultas, o que permitiria remover a função "MAX".
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
)
Espero que ajude e faça sentido.