sql >> Databasteknik >  >> RDS >> PostgreSQL

Få n grupperade kategorier och summera andra till en

Den specifika svårigheten här:Frågor med en eller flera aggregerade funktioner i SELECT lista och ingen GROUP BY sats producera exakt en rad, även om ingen rad hittas i den underliggande tabellen.

Det finns inget du kan göra i WHERE klausul för att undertrycka den raden. Du måste utesluta en sådan rad i efterhand , det vill säga i HAVING klausul, eller i en yttre fråga.

Per dokumentation:

Om en fråga innehåller samlade funktionsanrop, men inga GROUP BY sats, gruppering förekommer fortfarande:resultatet är en enda grupprad (eller kanske norows alls, om den enstaka raden sedan elimineras av HAVING ). Detsamma gäller om den innehåller en HAVING klausul, även utan aggregatfunktionsanrop eller GROUP BY klausul.

Det bör noteras att lägga till en GROUP BY sats med bara ett konstant uttryck (som annars är helt meningslöst!) fungerar också. Se exempel nedan. Men jag vill helst inte använda det tricket, även om det är kort, billigt och enkelt, för det är knappast självklart vad det gör.

Följande fråga behöver bara en genomsökning av en tabell och returnerar de 7 bästa kategorierna sorterade efter antal. Om (och endast om ) det finns fler kategorier, resten sammanfattas i 'Andra':

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
  • Du måste bryta banden om flera kategorier kan ha samma antal över 7:e/8:e rangen. I mitt exempel kategorier med det mindre categoryid vinna ett sådant lopp.

  • Parenteser krävs för att inkludera en LIMIT eller ORDER BY sats till en enskild del av en UNION fråga.

  • Du behöver bara gå med i tabellen category för de 7 bästa kategorierna. Och det är generellt sett billigare att aggregera först och gå med senare i det här scenariot. Så gå inte med i basfrågan i CTE (common table expression) som heter cte , gå bara med i den första SELECT av UNION fråga, det är billigare.

  • Inte säker på varför du behöver COALESCE . Om du har en främmande nyckel på plats från contents.categoryid till category.id och båda contents.categoryid och category.name är definierade NOT NULL (som de förmodligen borde vara), då behöver du det inte.

Det udda GROUP BY true

Detta skulle också fungera:

...

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

Och jag får till och med lite snabbare frågeplaner. Men det är ett ganska udda hack ...

SQL Fiddle demonstrerar alla.

Relaterat svar med mer förklaring för UNION ALL / LIMIT teknik:

  • Summera resultaten av några få frågor och hitta sedan topp 5 i SQL


  1. Lista över NLS-parametrar i Oracle Database

  2. Oracle - ORA-01489:resultatet av strängsammansättningen är för lång

  3. Hur man numrerar om det primära indexet

  4. Oracle Autonomous Transaction Exempel