SQL :fonction d'agrégation et regrouper par
Question
Considérez l'Oracle emp
tableau.J'aimerais que les employés ayant les salaires les plus élevés department = 20
et job = clerk
.Supposons également qu’il n’y ait pas de colonne « empno » et que la clé primaire implique un certain nombre de colonnes.Vous pouvez le faire avec :
select * from scott.emp
where deptno = 20 and job = 'CLERK'
and sal = (select max(sal) from scott.emp
where deptno = 20 and job = 'CLERK')
Cela fonctionne, mais je dois dupliquer le test deptno = 20 et job = 'CLERK', ce que j'aimerais éviter.Existe-t-il une façon plus élégante d'écrire ceci, peut-être en utilisant un group by
?BTW, si cela compte, j'utilise Oracle.
La solution
Ce qui suit est légèrement sur-conçu, mais constitue un bon modèle SQL pour les requêtes "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
)
Notez également que cela fonctionnera dans Oracle et Postgress (je pense) mais pas dans MS SQL.Pour quelque chose de similaire dans MS SQL, voir la question Requête SQL pour obtenir le dernier prix
Autres conseils
Si j'étais certain de la base de données ciblée, j'opterais pour la solution de Mark Nold, mais si jamais vous voulez du SQL* indépendant du dialecte, essayez
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
)
*Je pense que cela devrait fonctionner partout, mais je n'ai pas les environnements pour le tester.
Dans Oracle, je le ferais avec une fonction analytique, vous n'interrogeriez donc la table emp qu'une seule fois :
SELECT *
FROM (SELECT e.*, MAX (sal) OVER () AS max_sal
FROM scott.emp e
WHERE deptno = 20
AND job = 'CLERK')
WHERE sal = max_sal
C'est plus simple, plus facile à lire et plus efficace.
Si vous souhaitez le modifier pour répertorier ces informations pour tous les départements, vous devrez alors utiliser la clause « PARTITION BY » dans 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
C'est super!Je ne savais pas que l'on pouvait comparer (x, y, z) avec le résultat d'une instruction SELECT.Cela fonctionne très bien avec Oracle.
En guise de remarque pour les autres lecteurs, il manque un "=" après "(deptno,job,sal)" dans la requête ci-dessus.Peut-être que le formateur Stack Overflow l'a mangé (?).
Encore une fois merci Marc.
Dans Oracle, vous pouvez également utiliser l'instruction EXISTS, qui dans certains cas est plus rapide.
Par exemple...Sélectionnez Nom, numéro dans Cust Where Cust In (SELECT CUST_ID FROM BIG_TABLE) et entré> Sysdate -1 serait lent.
Mais sélectionnez Nom, numéro dans Cust cOù existe (sélectionnez Cust_id dans big_table OÙ cust_id=c.cust_id ) Et entré> sysdate -1 serait très rapide avec une indexation appropriée.Vous pouvez également l'utiliser avec plusieurs paramètres.
Il existe de nombreuses solutions.Vous pouvez également conserver la présentation de votre requête d'origine en ajoutant simplement des alias de table et en joignant les noms de colonnes, vous n'aurez toujours qu'une seule fois DEPTNO = 20 et JOB = 'CLERK' dans la requête.
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
)
On pourrait également noter que le mot clé « ALL » peut être utilisé pour ce type de requêtes ce qui permettrait de supprimer la fonction « 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
)
J'espère que cela aide et a du sens.