generate_series для нескольких типов записей в postgresql

8

У меня есть две таблицы, которые я хочу запросить: pest_countsи pestsкоторые выглядят так:

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

Я хочу использовать postgres ', generate_seriesчтобы показать количество каждого типа вредного организма, найденного для ряда дат:

Ожидаемые результаты

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

Я знаю, что мне нужно что-то вроде следующего, но я не совсем уверен, как сделать остальное:

SELECT date FROM generate_series('2015-01-01'::date, '2015-12-31'::date, '1 day') date
Кайл Деко
источник

Ответы:

8

Я обычно решаю такие проблемы, устанавливая таблицу для всех возможных точек данных (здесь вредители и даты). Это легко достигается с помощью CROSS JOIN, увидеть WITHзапрос ниже.

Затем, в качестве завершающего шага, я просто (внешний) присоединяюсь к существующим измерениям, основываясь на идентификаторе вредителя и дате - при желании, давая значение по умолчанию для отсутствующих значений через COALESCE().

Итак, весь запрос:

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;

Проверьте это на работе на SQLFiddle .

Примечание: если либо таблица (ы), либо сгенерированная серия большие, выполнение CROSS JOINвнутри CTE может быть плохой идеей. (Он должен материализовать все строки, независимо от того, есть данные за данный день или нет). В этом случае нужно сделать то же самое в FROMпредложении, как заключить в скобки вложенное соединение вместо текущей ссылки на data_points. Таким образом, планировщик лучше понимает затронутые строки и возможности использования индексов. Я использую CTE в примере, потому что он выглядит чище для примера.

Dezso
источник
0

В следующий раз я предлагаю вам использовать fiddle.com, чтобы иметь онлайн-схему для игры.

Функция generate_series возвращает набор меток времени, поэтому вам нужно будет привести его к дате вне функции. Это необходимо в текущем запросе, так как в таблице timestampне будет совпадать .datepest_counts

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)

Я предложу что-то вроде:

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
источник