sql >> Base de Datos >  >> RDS >> PostgreSQL

Obtenga n categorías agrupadas y sume otras en una

La dificultad específica aquí:consultas con una o más funciones agregadas en SELECT lista y no GROUP BY cláusula produce exactamente una fila, incluso si no se encuentra ninguna en la tabla subyacente.

No hay nada que puedas hacer en WHERE cláusula para suprimir esa fila. Debe excluir dicha fila después del hecho , es decir, en el HAVING cláusula, o en una consulta externa.

Por documentación:

Si una consulta contiene llamadas a funciones agregadas, pero no GROUP BY cláusula, la agrupación aún ocurre:el resultado es una sola fila de grupo (o quizás ninguna fila en absoluto, si la fila única es luego eliminada por HAVING ). Lo mismo es cierto si contiene un HAVING cláusula, incluso sin ninguna llamada de función agregada o GROUP BY cláusula.

Cabe señalar que agregar un GROUP BY La cláusula con solo una expresión constante (¡que de otra manera es completamente inútil!) también funciona. Ver ejemplo a continuación. Pero prefiero no usar ese truco, incluso si es corto, barato y simple, porque no es muy obvio lo que hace.

La siguiente consulta solo necesita un análisis de una sola tabla y devuelve las 7 categorías principales ordenadas por conteo. Si (y solo si ) hay más categorías, el resto se resume en 'Otros':

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
  • Debe romper los empates si varias categorías pueden tener el mismo recuento en el rango 7 u 8. En mi ejemplo, las categorías con el categoryid más pequeño ganar tal carrera.

  • Se requieren paréntesis para incluir un LIMIT o ORDER BY cláusula a un tramo individual de una UNION consulta.

  • Solo necesitas unirte a la tabla category para las 7 primeras categorías. Y generalmente es más barato agregar primero y unirse más tarde en este escenario. Por lo tanto, no se una a la consulta base en la CTE (expresión de tabla común) llamada cte , solo únete al primero SELECT de la UNION consulta, eso es más barato.

  • No estoy seguro de por qué necesita COALESCE . Si tiene una clave externa de contents.categoryid a category.id y ambos contents.categoryid y category.name están definidos NOT NULL (como probablemente deberían ser), entonces no lo necesitas.

El extraño GROUP BY true

Esto también funcionaría:

...

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

E incluso obtengo planes de consulta un poco más rápidos. Pero es un truco bastante extraño...

Violín SQL demostrando todo.

Respuesta relacionada con más explicaciones para UNION ALL / LIMIT técnica:

  • Sumar los resultados de algunas consultas y luego encontrar los 5 principales en SQL