PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Holen Sie sich n gruppierte Kategorien und fassen Sie andere zu einer zusammen

Die spezifische Schwierigkeit hier:Abfragen mit einer oder mehreren Aggregatfunktionen im SELECT Liste und kein GROUP BY -Klausel erzeugt genau eine Zeile, auch wenn keine Zeile gefunden wird in der zugrunde liegenden Tabelle.

In WHERE können Sie nichts tun -Klausel, um diese Zeile zu unterdrücken. Sie müssen eine solche Zeile im Nachhinein ausschließen , also im HAVING -Klausel oder in einer äußeren Abfrage.

Per Dokumentation:

Wenn eine Abfrage aggregierte Funktionsaufrufe enthält, aber kein GROUP BY -Klausel findet die Gruppierung dennoch statt:Das Ergebnis ist eine einzelne Gruppenzeile (oder vielleicht überhaupt keine Zeilen, wenn die einzelne Zeile dann durch HAVING eliminiert wird ). Dasselbe gilt, wenn es ein HAVING enthält -Klausel, auch ohne Aggregatfunktionsaufrufe oder GROUP BY Klausel.

Es sollte beachtet werden, dass das Hinzufügen eines GROUP BY Klausel mit nur einem konstanten Ausdruck (was sonst völlig sinnlos ist!) funktioniert auch. Siehe Beispiel unten. Aber ich würde diesen Trick lieber nicht anwenden, auch wenn er kurz, billig und einfach ist, weil es kaum offensichtlich ist, was er tut.

Die folgende Abfrage benötigt nur einen einzelnen Tabellenscan und gibt die Top-7-Kategorien sortiert nach Anzahl zurück. Wenn (und nur wenn ) gibt es mehr Kategorien, der Rest ist in 'Andere' zusammengefasst:

WITH cte AS (
   SELECT categoryid, count(*) AS data
        , row_number() OVER (ORDER BY count(*) DESC, categoryid) AS rn
   FROM   contents
   GROUP  BY 1
   )
(  -- parentheses required again
SELECT categoryid, COALESCE(ca.name, 'Unknown') AS label, data
FROM   cte
LEFT   JOIN category ca ON ca.id = cte.categoryid
WHERE  rn <= 7
ORDER  BY rn
)
UNION ALL
SELECT NULL, 'Others', sum(data)
FROM   cte
WHERE  rn > 7         -- only take the rest
HAVING count(*) > 0;  -- only if there actually is a rest
-- or: HAVING  sum(data) > 0
  • Sie müssen Gleichstände aufheben, wenn mehrere Kategorien auf dem 7./8. Rang die gleiche Anzahl haben können. In meinem Beispiel Kategorien mit der kleineren categoryid ein solches Rennen gewinnen.

  • Klammern müssen ein LIMIT enthalten oder ORDER BY -Klausel zu einem einzelnen Zweig einer UNION Abfrage.

  • Sie müssen nur der Tabelle category beitreten für die Top 7 Kategorien. Und in diesem Szenario ist es im Allgemeinen billiger, zuerst zu aggregieren und später beizutreten. Schließen Sie sich also nicht der Basisabfrage im CTE (allgemeiner Tabellenausdruck) mit dem Namen cte an , nur beim ersten SELECT mitmachen der UNION abfragen, das ist billiger.

  • Nicht sicher, warum Sie COALESCE benötigen . Wenn Sie einen Fremdschlüssel von contents.categoryid haben zu category.id und sowohl contents.categoryid und category.name sind NOT NULL definiert (wie sie wahrscheinlich sein sollten), dann brauchen Sie es nicht.

Das ungerade GROUP BY true

Das würde auch funktionieren:

...

UNION ALL
SELECT NULL , 'Others', sum(data)
FROM   cte
WHERE  rn > 7
GROUP BY true; 

Und ich bekomme sogar etwas schnellere Abfragepläne. Aber es ist ein ziemlich seltsamer Hack ...

SQL-Geige alles demonstrieren.

Zugehörige Antwort mit mehr Erklärung für UNION ALL / LIMIT Technik:

  • Summiere die Ergebnisse einiger Abfragen und finde dann die Top 5 in SQL