it-swarm.com.de

Wie wende ich ORDER BY und LIMIT in Kombination mit einer Aggregatfunktion an?

Eine Geige für meine Frage finden Sie unter https://dbfiddle.uk/?rdbms=postgres_10&fiddle=3cd9335fa07565960c1837aa65143685 .

Ich habe ein einfaches Tabellenlayout:

class
person: belongs to a class

Ich möchte alle Klassen auswählen und für jede Klasse möchte ich die ersten beiden Personenkennungen der zugehörigen Personen nach absteigendem Namen sortieren.

Ich habe dies mit der folgenden Abfrage gelöst:

select     c.identifier, array_agg(p.identifier order by p.name desc) as persons
from       class as c
left join lateral (
             select   p.identifier, p.name
             from     person as p
             where    p.class_identifier = c.identifier
             order by p.name desc
             limit    2
           ) as p
on         true
group by   c.identifier
order by   c.identifier

Hinweis: Ich hätte eine Korrelationsunterabfrage in der SELECT -Klausel verwenden können, aber ich versuche dies als Teil eines Lernprozesses zu vermeiden.

Wie Sie sehen können, wende ich order by p.name desc An zwei Stellen an:

  • in der Unterabfrage
  • in der Aggregatfunktion

Gibt es eine Möglichkeit, dies zu vermeiden? Mein Zug des Unterrichts:

  • Erstens kann ich natürlich das order by In der Unterabfrage nicht entfernen, da dies eine Abfrage ergeben würde, die meine oben angegebene Anforderung nicht erfüllt.

  • Zweitens denke ich, dass das order by In der Aggregatfunktion nicht ausgelassen werden kann, da die Zeilenreihenfolge der Unterabfrage nicht unbedingt in der Aggregatfunktion erhalten bleibt.

Soll ich die Abfrage umschreiben?

8
Jarius Hebzo

Ich bewerbe order by p.name desc An zwei Stellen ... Gibt es eine Möglichkeit, dies zu vermeiden?

Ja. Aggregieren Sie mit einem ARRAY-Konstruktor direkt in der lateralen Unterabfrage:

SELECT c.identifier, p.persons
FROM   class c
CROSS  JOIN LATERAL (
   SELECT ARRAY (
      SELECT identifier
      FROM   person
      WHERE  class_identifier = c.identifier
      ORDER  BY name DESC
      LIMIT  2
      ) AS persons
   ) p
ORDER  BY c.identifier;

Auf diese Weise benötigen Sie auch nicht GROUP BY Im äußeren SELECT. Kürzer, sauberer, schneller.

Ich habe LEFT JOIN Durch ein einfaches CROSS JOIN Ersetzt, da der ARRAY-Konstruktor immer genau 1 Zeile zurückgibt. (Wie Sie in einem Kommentar betont haben.)

db <> fiddle hier.

Verbunden:

Reihenfolge der Zeilen in Unterabfragen

Um Ihren Kommentar anzusprechen:

Ich habe erfahren, dass die Reihenfolge der Zeilen in einer Unterabfrage in der äußeren Abfrage niemals garantiert bleibt.

Nun, nein. Während der SQL-Standard keine Garantien bietet, gibt es in Postgres eingeschränkte Garantien . Das Handbuch:

Diese Reihenfolge ist standardmäßig nicht angegeben, kann jedoch durch Schreiben einer ORDER BY - Klausel innerhalb des Gesamtaufrufs gesteuert werden, wie in Abschnitt 4.2.7 gezeigt. Alternativ funktioniert normalerweise die Angabe der Eingabewerte aus einer sortierten Unterabfrage. Zum Beispiel:

SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;

Beachten Sie, dass dieser Ansatz fehlschlagen kann, wenn die äußere Abfrageebene zusätzliche Verarbeitung enthält, z. B. einen Join, da dies dazu führen kann, dass die Ausgabe der Unterabfrage vor der Berechnung des Aggregats neu angeordnet wird.

Wenn Sie in der nächsten Ebene nur Zeilen aggregieren, ist die Reihenfolge positiv garantiert. Ja, was wir dem ARRAY-Konstruktor zuführen, ist auch eine Unterabfrage. Das ist nicht der Punkt. Es würde auch mit array_agg() funktionieren:

SELECT c.identifier, p.persons
FROM   class c
CROSS  JOIN LATERAL (
   SELECT array_agg(identifier) AS persons
   FROM  (
      SELECT identifier
      FROM   person
      WHERE  class_identifier = c.identifier
      ORDER  BY name DESC
      LIMIT  2
      ) sub
   ) p
ORDER  BY c.identifier;

Ich erwarte jedoch, dass der ARRAY-Konstruktor für den Fall schneller ist. Sehen:

4

Hier ist eine Alternative, aber sie ist nicht besser als das, was Sie bereits haben:

with enumeration (class_identifier, identifier, name, n) as (
    select  p.class_identifier, p.identifier, p.name
         , row_number() over (partition by p.class_identifier 
                              order by p.name desc)
    from     person as p
)
select c.identifier, array_agg(e.identifier order by e.n) as persons
from class as c
left join  enumeration e
    on c.identifier = e.class_identifier
where e.n <= 2
group by   c.identifier
order by   c.identifier;
2
Lennart