SQL:Aggregatfunktion und Gruppierung nach
Frage
Betrachten Sie das Orakel emp
Tisch.Ich möchte die Mitarbeiter mit dem Top-Gehalt gewinnen department = 20
Und job = clerk
.Nehmen Sie außerdem an, dass es keine Spalte „empno“ gibt und dass der Primärschlüssel mehrere Spalten umfasst.Sie können dies tun mit:
select * from scott.emp
where deptno = 20 and job = 'CLERK'
and sal = (select max(sal) from scott.emp
where deptno = 20 and job = 'CLERK')
Das funktioniert, aber ich muss den Test deptno = 20 und job = 'CLERK' duplizieren, was ich gerne vermeiden würde.Gibt es eine elegantere Möglichkeit, dies zu schreiben, vielleicht mit a group by
?Übrigens, wenn das wichtig ist, verwende ich Oracle.
Lösung
Das Folgende ist etwas überentwickelt, stellt aber ein gutes SQL-Muster für „Top-x“-Abfragen dar.
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
)
Beachten Sie auch, dass dies in Oracle und Postgress funktioniert (glaube ich), aber nicht in MS SQL.Für etwas Ähnliches in MS SQL siehe Frage SQL-Abfrage, um den aktuellen Preis zu erhalten
Andere Tipps
Wenn ich mir der Zieldatenbank sicher wäre, würde ich mich für die Lösung von Mark Nold entscheiden, aber wenn Sie jemals ein dialektunabhängiges SQL* wünschen, versuchen Sie es
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
)
*Ich glaube, das sollte überall funktionieren, aber ich habe nicht die Umgebungen, um es zu testen.
In Oracle würde ich es mit einer analytischen Funktion machen, sodass Sie die emp-Tabelle nur einmal abfragen würden:
SELECT *
FROM (SELECT e.*, MAX (sal) OVER () AS max_sal
FROM scott.emp e
WHERE deptno = 20
AND job = 'CLERK')
WHERE sal = max_sal
Es ist einfacher, leichter zu lesen und effizienter.
Wenn Sie es ändern möchten, um diese Informationen für alle Abteilungen aufzulisten, müssen Sie die Klausel „PARTITION BY“ in OVER verwenden:
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
Das ist großartig!Ich wusste nicht, dass man einen Vergleich von (x, y, z) mit dem Ergebnis einer SELECT-Anweisung durchführen kann.Das funktioniert hervorragend mit Oracle.
Als Randbemerkung für andere Leser: In der obigen Abfrage fehlt ein „=“ nach „(deptno,job,sal)“.Vielleicht hat es der Stack Overflow-Formatierer gefressen (?).
Nochmals vielen Dank, Mark.
In Oracle können Sie auch die EXISTS-Anweisung verwenden, die in manchen Fällen schneller ist.
Zum Beispiel...Wählen Sie Name, Nummer von Cust Where Cust in (Wählen Sie Cust_id von big_table) und eingegeben> sysdate -1 wäre langsam.
Wählen Sie jedoch den Namen, die Nummer von Cust CWo existiert WHERE cust_id=c.cust_id ) Und eingegeben> sysdate -1 wäre sehr schnell mit ordnungsgemäßer Indexierung.Sie können dies auch mit mehreren Parametern verwenden.
Es gibt viele Lösungen.Sie könnten auch Ihr ursprüngliches Abfragelayout beibehalten, indem Sie einfach Tabellenaliase hinzufügen und die Spaltennamen verknüpfen. Sie hätten DEPTNO = 20 und JOB = 'CLERK' immer noch nur einmal in der Abfrage.
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
)
Es ist auch zu beachten, dass für diese Art von Abfragen das Schlüsselwort „ALL“ verwendet werden kann, wodurch Sie die Funktion „MAX“ entfernen können.
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
)
Ich hoffe, das hilft und macht Sinn.