menghasilkan_series untuk beberapa jenis rekaman di postgresql

8

Saya punya dua tabel yang ingin saya tanyakan: pest_countsdan pestsyang terlihat seperti:

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

Saya ingin menggunakan postgres ' generate_seriesuntuk menunjukkan jumlah setiap jenis hama yang ditemukan untuk seri tanggal:

hasil yang diharapkan

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

Saya tahu saya akan membutuhkan sesuatu seperti yang berikut ini tetapi saya tidak yakin bagaimana melakukan sisanya:

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

Jawaban:

8

Saya biasanya memecahkan masalah seperti itu dengan mengatur tabel untuk semua poin data yang mungkin (di sini hama dan tanggal). Ini mudah dicapai oleh CROSS JOIN, lihat WITHkueri di bawah ini.

Kemudian, sebagai langkah akhir, saya hanya (luar) bergabung dengan pengukuran yang ada, berdasarkan ID hama dan tanggal - opsional memberikan default untuk nilai yang hilang melalui COALESCE().

Jadi, seluruh permintaan adalah:

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;

Periksa di tempat kerja di SQLFiddle .

Catatan: saat tabel atau seri yang dihasilkan besar, melakukan bagian CROSS JOINdalam CTE mungkin merupakan ide yang buruk. (Itu harus mematerialisasikan semua baris, terlepas dari ada data untuk hari tertentu atau tidak). Dalam hal ini orang harus melakukan hal yang sama dalam FROMklausa, sebagai sub-gabung yang disisipkan sebagai pengganti referensi saat ini data_points. Dengan cara ini perencana memiliki pemahaman yang lebih baik tentang baris yang terpengaruh dan kemungkinan untuk menggunakan indeks. Saya menggunakan CTE dalam contoh karena terlihat lebih bersih untuk contoh ini.

dezso
sumber
0

Saya akan menyarankan lain kali agar Anda menggunakan fiddle.com untuk memiliki skema online untuk dimainkan.

fungsi menghasilkan_series mengembalikan set timestamp, jadi Anda harus melemparkannya ke tanggal di luar fungsi. Ini diperlukan dalam permintaan saat ini karena timestamptidak akan cocok dengan datedalam pest_countstabel.

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)

Saya akan menyarankan sesuatu seperti:

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
sumber