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 BYclausule,groepering komt nog steeds voor:het resultaat is een enkele groepsrij (of misschien helemaal geen norows, als de enkele rij dan wordt geëlimineerd doorHAVING). Hetzelfde geldt als het eenHAVING. bevat clausule, zelfs zonder aggregatiefunctie-aanroepen ofGROUP BYclausule.
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
categoryidwin zo'n race. -
Haakjes zijn vereist om een
LIMIT. op te nemen ofORDER BYclausule naar een individuele tak van eenUNIONvraag. -
Je hoeft alleen maar lid te worden van de tabel
categoryvoor 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 naamcte, doe alleen mee aan de eersteSELECTvan deUNIONvraag, dat is goedkoper. -
Ik weet niet zeker waarom je de
COALESCE. nodig hebt . Als u een externe sleutel heeft vancontents.categoryidnaarcategory.iden beidecontents.categoryidencategory.namezijn gedefinieerdNOT 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