PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Como incluir dados ausentes para vários agrupamentos dentro do intervalo de tempo?


Com base em algumas suposições (ambiguidades na pergunta) sugiro:
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;

Pontos principais


  • Construa uma grade de todas as combinações desejadas com CROSS JOIN . E então LEFT JOIN às linhas existentes. Relacionado:

  • No seu caso, é uma junção de várias tabelas, então eu uso parênteses no FROM list para LEFT JOIN para o resultado de INNER JOIN entre parênteses. Seria incorreto para LEFT JOIN para cada tabela separadamente, porque você incluiria acertos em correspondências parciais e obteria contagens potencialmente incorretas.

  • Assumindo integridade referencial e trabalhando diretamente com colunas PK, não precisamos incluir rooms e teachers no lado esquerdo uma segunda vez. Mas ainda temos uma junção de duas tabelas (studies e teacher_contacts ). A função de teacher_contacts não está claro para mim. Normalmente, eu esperaria uma relação entre studies e teachers diretamente. Poderia ser mais simplificado...

  • Precisamos contar uma coluna não nula no lado esquerdo para obter as contagens desejadas. Como count(s.room_id)

  • Para manter isso rápido para tabelas grandes, certifique-se de que seus predicados sejam sargable . E adicione índices correspondentes .

  • A coluna teacher é dificilmente (confiavelmente) único. Opere com um ID único, preferencialmente o PK (mais rápido e simples também). Ainda estou usando o teacher para que a saída corresponda ao resultado desejado. Pode ser aconselhável incluir um ID exclusivo, pois os nomes podem ser duplicados.

  • Você quer:

    Então comece com date_trunc('month', now() - interval '12 month' (não 13). Isso já está arredondando o início e faz o que você deseja - com mais precisão do que sua consulta original.

Como você mencionou desempenho lento, dependendo das definições reais da tabela e da distribuição de dados, provavelmente é mais rápido agregar primeiro e juntar depois , como nesta resposta 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;

Sobre sua observação de encerramento:

Provavelmente, você pode use a forma de dois parâmetros de crosstab() para produzir o resultado desejado diretamente e com excelente desempenho e a consulta acima não é necessária para começar. Considerar: