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

¿Cómo incluir datos faltantes para múltiples agrupaciones dentro del lapso de tiempo?

Basado en algunas suposiciones (ambigüedades en la pregunta), sugiero:

SELECT upper(trim(t.full_name)) AS teacher
     , m.study_month
     , r.room_code              AS room
     , count(s.room_id)         AS study_count

FROM   teachers t
CROSS  JOIN generate_series(date_trunc('month', now() - interval '12 month')  -- 12!
                          , date_trunc('month', now())
                          , interval '1 month') m(study_month)
CROSS  JOIN rooms r
LEFT   JOIN (                                                  -- parentheses!
          studies s
   JOIN   teacher_contacts tc ON tc.id = s.teacher_contact_id  -- INNER JOIN!
   ) ON tc.teacher_id = t.id
    AND s.study_dt >= m.study_month
    AND s.study_dt <  m.study_month + interval '1 month'      -- sargable!
    AND s.room_id = r.id
GROUP  BY t.id, m.study_month, r.id  -- id is PK of respective tables
ORDER  BY t.id, m.study_month, r.id;

Puntos principales

  • Cree una cuadrícula de todas las combinaciones deseadas con CROSS JOIN . Y luego LEFT JOIN a las filas existentes. Relacionado:

  • En su caso, es una unión de varias tablas, por lo que uso paréntesis en FROM lista para LEFT JOIN al resultado de INNER JOIN entre paréntesis. Sería incorrecto a LEFT JOIN a cada tabla por separado, porque incluiría aciertos en coincidencias parciales y obtendría recuentos potencialmente incorrectos.

  • Asumiendo integridad referencial y trabajando con columnas PK directamente, no necesitamos incluir rooms y teachers en el lado izquierdo por segunda vez. Pero todavía tenemos una combinación de dos tablas (studies y teacher_contacts ). El rol de teacher_contacts no está claro para mí. Normalmente, esperaría una relación entre studies y teachers directamente. Podría simplificarse aún más...

  • Necesitamos contar una columna no nula en el lado izquierdo para obtener los recuentos deseados. Me gusta count(s.room_id)

  • Para mantener esto rápido para tablas grandes, asegúrese de que sus predicados sean sargable . Y agregue índices coincidentes .

  • La columna teacher es difícilmente (confiablemente) único. Opere con una identificación única, preferiblemente la PK (también más rápida y sencilla). Todavía estoy usando teacher para que la salida coincida con el resultado deseado. Puede ser conveniente incluir una identificación única, ya que los nombres pueden estar duplicados.

  • Quieres:

    Así que empieza con date_trunc('month', now() - interval '12 month' (no 13). Eso ya está redondeando hacia abajo el inicio y hace lo que quiere, con más precisión que su consulta original.

Dado que mencionó un rendimiento lento, dependiendo de las definiciones de tabla reales y la distribución de datos, probablemente sea más rápido agregar primero y unirse más tarde , como en esta respuesta relacionada:

SELECT upper(trim(t.full_name)) AS teacher
     , m.mon                    AS study_month
     , r.room_code              AS room
     , COALESCE(s.ct, 0)        AS study_count

FROM   teachers t
CROSS  JOIN generate_series(date_trunc('month', now() - interval '12 month')  -- 12!
                          , date_trunc('month', now())
                          , interval '1 month') mon
CROSS  JOIN rooms r
LEFT   JOIN (                                                  -- parentheses!
   SELECT tc.teacher_id, date_trunc('month', s.study_dt) AS mon, s.room_id, count(*) AS ct
   FROM   studies s
   JOIN   teacher_contacts tc ON s.teacher_contact_id = tc.id
   WHERE  s.study_dt >= date_trunc('month', now() - interval '12 month')  -- sargable
   GROUP  BY 1, 2, 3
   ) s ON s.teacher_id = t.id
      AND s.mon = m.mon
      AND s.room_id = r.id
ORDER  BY 1, 2, 3;

Acerca de su comentario de cierre:

Lo más probable es que puedas use la forma de dos parámetros de crosstab() para producir el resultado deseado directamente y con un rendimiento excelente y la consulta anterior no es necesaria para empezar. Considere: