generate_series pour plusieurs types d'enregistrement dans postgresql

8

J'ai deux tables que je veux interroger: pest_countset pestsqui ressemblent à:

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

Je veux utiliser postgres generate_seriespour afficher le nombre de chaque type de ravageur trouvé pour la série de dates:

Résultats attendus

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

Je sais que j'aurai besoin de quelque chose comme ceci, mais je ne sais pas exactement comment faire le reste:

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

Réponses:

8

Je résout généralement ces problèmes en créant une table pour tous les points de données possibles (ici les parasites et les dates). Ceci est facilement réalisé par un CROSS JOIN, voir la WITHrequête ci-dessous.

Ensuite, en tant qu'étape de finition, je viens (externe) de rejoindre les mesures existantes, en fonction de l'ID du ravageur et de la date - donnant éventuellement une valeur par défaut pour les valeurs manquantes via COALESCE().

Donc, la requête entière est:

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;

Vérifiez-le au travail sur SQLFiddle .

Remarque: lorsque la ou les tables ou la série générée sont volumineuses, faire l' CROSS JOINintérieur d'un CTE peut être une mauvaise idée. (Il doit matérialiser toutes les lignes, qu'il existe ou non des données pour un jour donné). Dans ce cas, il convient de faire de même dans la FROMclause, en tant que sous-jointure entre parenthèses au lieu de la référence actuelle à data_points. De cette façon, le planificateur a une meilleure compréhension des lignes affectées et des possibilités d'utilisation des index. J'utilise le CTE dans l'exemple parce qu'il a l'air plus propre pour le plaisir de l'exemple.

dezso
la source
0

Je suggérerai la prochaine fois que vous utiliserez fiddle.com afin d'avoir un schéma en ligne avec lequel jouer.

La fonction generate_series renvoie un ensemble d'horodatage, vous devrez donc le convertir à ce jour en dehors de la fonction. Ceci est nécessaire dans la requête en cours car le timestampne correspondra pas à datedans le pest_countstableau.

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)

Je vais suggérer quelque chose comme:

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
la source