generate_series para vários tipos de registro no postgresql


8

Eu tenho duas tabelas que eu quero consultar: pest_countse pestsque se parecem com:

CREATE TABLE pests(id,name)
AS VALUES
  (1,'Thrip'),
  (2,'Fungus Gnosts');

CREATE TABLE pest_counts(id,pest_id,date,count)
AS VALUES
  (1,1,'2015-01-01'::date,14),
  (2,2,'2015-01-02'::date,5);

Eu quero usar o postgres ' generate_seriespara mostrar o número de cada tipo de praga que foi encontrado para a série de datas:

resultados esperados

name         | date       | count
-------------+------------+-------
Thrip        | 2015-01-01 | 14
Thrip        | 2015-01-02 | 0
....
Fungus Gnats | 2015-01-01 | 0
Fungus Gnats | 2015-01-02 | 5
...

Sei que precisarei de algo como o seguinte, mas não sei exatamente como fazer o resto:

SELECT date FROM generate_series('2015-01-01'::date, '2015-12-31'::date, '1 day') date

Respostas:


8

Geralmente resolvo esses problemas configurando uma tabela para todos os pontos de dados possíveis (aqui as pragas e datas). Isso é facilmente alcançado por a CROSS JOIN, consulte a WITHconsulta abaixo.

Então, como etapa de acabamento, eu apenas (externamente) associo as medidas existentes, com base no ID e na data da praga - opcionalmente fornecendo um padrão para os valores ausentes via COALESCE().

Portanto, toda a consulta é:

WITH data_points AS (
    SELECT id, name, i::date
    FROM pests
    CROSS JOIN generate_series('2015-01-01'::date, '2015-01-05', '1 day') t(i)
) 
SELECT d.name, d.i, COALESCE(p.cnt, 0) 
FROM data_points AS d 
LEFT JOIN pest_counts AS p 
    ON d.id = p.pest_id 
    AND d.i = p.count_date;

Verifique no trabalho no SQLFiddle .

Nota: quando as tabelas ou as séries geradas são grandes, fazer CROSS JOINum CTE interno pode ser uma má ideia. (Ele precisa materializar todas as linhas, independentemente de haver dados para um determinado dia ou não). Nesse caso, deve-se fazer o mesmo na FROMcláusula, como uma sub junção entre parênteses, em vez da referência atual a data_points. Dessa forma, o planejador tem um melhor entendimento sobre as linhas afetadas e as possibilidades de uso de índices. Eu uso o CTE no exemplo, porque parece mais limpo por causa do exemplo.


0

Vou sugerir na próxima vez que você usar o fiddle.com para ter um esquema on-line para brincar.

A função generate_series retorna um conjunto de registro de data e hora, portanto você precisará convertê-lo para data fora da função. Isso é necessário na consulta atual, pois timestampnão corresponderá à dateda pest_countstabela.

sandbox=# \df generate_series
   Schema   |      Name       |         Result data type          |                        Argument data types                         |  Type  
(...)
 pg_catalog | generate_series | SETOF timestamp without time zone | timestamp without time zone, timestamp without time zone, interval | normal
 pg_catalog | generate_series | SETOF timestamp with time zone    | timestamp with time zone, timestamp with time zone, interval       | normal
(6 rows)

Vou sugerir algo como:

SELECT p.name, pc.date, pc.count 
FROM generate_series('2015-01-01'::date, '2015-12-31'::date, '1 day') days 
join pest_counts pc ON (days::date = pc.date) 
join pests p ON (p.id = pc.pest_id) ;
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.