generate_series para múltiples tipos de registros en postgresql

8

Tengo dos tablas que quiero consultar: pest_countsy pestsque se parecen a:

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);

Quiero usar postgres ' generate_seriespara mostrar el número de cada tipo de plaga que se encontró para la serie de fechas:

Resultados previstos

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
...

Sé que necesitaré algo como lo siguiente, pero no estoy exactamente seguro de cómo hacer el resto:

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

Respuestas:

8

Por lo general, resuelvo estos problemas estableciendo una tabla para todos los puntos de datos posibles (aquí las plagas y las fechas). Esto se logra fácilmente mediante un CROSS JOIN, consulte la WITHconsulta a continuación.

Luego, como paso final, simplemente (exterior) unir las medidas existentes, en función de la identificación de la plaga y la fecha, opcionalmente dando un valor predeterminado para los valores faltantes a través de COALESCE().

Entonces, toda la consulta es:

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;

Compruébelo en el trabajo en SQLFiddle .

Nota: cuando las tablas o las series generadas son grandes, hacer CROSS JOINuna CTE dentro puede ser una mala idea. (Tiene que materializar todas las filas, independientemente de si hay datos para un día determinado o no). En este caso, uno debería hacer lo mismo en la FROMcláusula, como una subunión entre paréntesis en lugar de la referencia actual a data_points. De esta manera, el planificador comprende mejor las filas afectadas y las posibilidades de usar índices. Utilizo el CTE en el ejemplo porque parece más limpio por el bien del ejemplo.

dezso
fuente
0

Le sugeriré la próxima vez que use fiddle.com para tener un esquema en línea para jugar.

La función generate_series devuelve un conjunto de marcas de tiempo, por lo que deberá convertirlo a la fecha fuera de la función. Esto es necesario en la consulta actual ya timestampque no coincidirá con el datede la pest_countstabla.

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)

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) ;
3manuek
fuente