sql >> Database >  >> RDS >> PostgreSQL

Krijg n gegroepeerde categorieën en tel anderen op tot één

De specifieke moeilijkheid hier:Query's met een of meer statistische functies in de SELECT lijst en geen GROUP BY clausule produceert precies één rij, zelfs als er geen rij is gevonden in de onderliggende tabel.

U kunt niets doen in de WHERE clausule om die rij te onderdrukken. Je moet zo'n rij achteraf uitsluiten , d.w.z. in de HAVING clausule, of in een buitenste vraag.

Per documentatie:

Als een query geaggregeerde functieaanroepen bevat, maar geen GROUP BY clausule,groepering komt nog steeds voor:het resultaat is een enkele groepsrij (of misschien helemaal geen norows, als de enkele rij dan wordt geëlimineerd door HAVING ). Hetzelfde geldt als het een HAVING . bevat clausule, zelfs zonder aggregatiefunctie-aanroepen of GROUP BY clausule.

Opgemerkt moet worden dat het toevoegen van een GROUP BY clausule met alleen een constante uitdrukking (die verder volkomen zinloos is!) werkt ook. Zie voorbeeld hieronder. Maar ik gebruik dat trucje liever niet, ook al is het kort, goedkoop en eenvoudig, omdat het nauwelijks duidelijk is wat het doet.

De volgende zoekopdracht heeft alleen een enkele tabelscan nodig en retourneert de top 7 categorieën gerangschikt op aantal. Als (en alleen als ) er zijn meer categorieën, de rest is samengevat in 'Overige':

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
  • Je moet de banden verbreken als meerdere categorieën hetzelfde aantal kunnen hebben op de 7e/8e rang. In mijn voorbeeld, categorieën met de kleinere categoryid win zo'n race.

  • Haakjes zijn vereist om een ​​LIMIT . op te nemen of ORDER BY clausule naar een individuele tak van een UNION vraag.

  • Je hoeft alleen maar lid te worden van de tabel category voor de top 7 categorieën. En het is over het algemeen goedkoper om eerst te aggregeren en later in dit scenario mee te doen. Dus doe niet mee aan de basisquery in de CTE (common table expression) met de naam cte , doe alleen mee aan de eerste SELECT van de UNION vraag, dat is goedkoper.

  • Ik weet niet zeker waarom je de COALESCE . nodig hebt . Als u een externe sleutel heeft van contents.categoryid naar category.id en beide contents.categoryid en category.name zijn gedefinieerd NOT NULL (zoals ze waarschijnlijk zouden moeten zijn), dan heb je het niet nodig.

De oneven GROUP BY true

Dit zou ook werken:

...

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

En ik krijg zelfs iets snellere queryplannen. Maar het is een nogal vreemde hack ...

SQL Fiddle alles demonstreren.

Gerelateerd antwoord met meer uitleg voor de UNION ALL / LIMIT techniek:

  • Stel de resultaten van een paar zoekopdrachten bij elkaar op en vind dan de top 5 in SQL


  1. Werken met SQL-cursors

  2. Trends in 2020 waar DBA's zich bewust van moeten zijn

  3. Tips voor het afstemmen van PostgreSQL-prestaties

  4. Joins gebruiken om gegevens uit verschillende tabellen te combineren in PostgreSQL