Как агрегировать данные за несколько лет по MM-DD, игнорируя год - PullRequest
0 голосов
/ 16 мая 2018

Postgres версия 9.4.18, PostGIS версия 2.2.

Вот таблицы, с которыми я работаю (и вряд ли может внести существенные изменения в структуру таблицы):

Таблица ltg_data (с 1988 по 2018 годы):

 Column   |           Type           | Modifiers 
----------+--------------------------+-----------
intensity | integer                  | not null
time      | timestamp with time zone | not null
lon       | numeric(9,6)             | not null
lat       | numeric(8,6)             | not null
ltg_geom  | geometry(Point,4269)     | 
Indexes:
"ltg_data2_ltg_geom_idx" gist (ltg_geom)
"ltg_data2_time_idx" btree ("time")

Размер ltg_data (~ 800M строк):

ltg=# select pg_relation_size('ltg_data');
pg_relation_size 
------------------
 149729288192

Таблица counties:

  Column   |            Type             |                       Modifiers                      
-----------+-----------------------------+--------------------------------- -----------------------
gid        | integer                     | not null default nextval('counties_gid_seq'::regclass)
objectid_1 | integer                     | 
objectid   | integer                     | 
state      | character varying(2)        | 
cwa        | character varying(9)        | 
countyname | character varying(24)       | 
fips       | character varying(5)        | 
time_zone  | character varying(2)        | 
fe_area    | character varying(2)        | 
lon        | double precision            | 
lat        | double precision            | 
the_geom   | geometry(MultiPolygon,4269) | 
Indexes:
"counties_pkey" PRIMARY KEY, btree (gid)
"counties_gix" gist (the_geom)
"county_cwa_idx" btree (cwa)
"countyname_cwa_idx" btree (countyname)

Желаемый результат: Я хочу временной ряд с одной строкой для каждого дня года в формате 'MM-DD', игнорируя год: 01-01, 01-02, 01-03, ..., 12-31 .И количество строк в таблице ltg_data на каждый день года.Я также в конечном итоге хочу, чтобы одно и то же происходило с каждым часом каждого дня в году ('MM-DD-HH').

A group by оператор должен выполнить это, но мне трудно присоединиться"большая" таблица с днями, сгенерированными с помощью generate_series().

MM-DD  | total_count   
-------+------------
12-22  |       9
12-23  |       0
12-24  |       0
12-25  |       0
12-26  |      23
12-27  |       0
12-28  |       5
12-29  |       0
12-30  |       0
12-31  |       0

Некоторые из моих многочисленных попыток:

SELECT date_trunc('day', d),
   count(a.lat) AS strikes
FROM generate_series('2017-01-01', '2018-12-31', interval '1 day') AS d
LEFT JOIN
(SELECT date_trunc('day', TIME) AS day_of_year,
      ltg_data.lat
 FROM ltg_data
 JOIN counties ON ST_contains(counties.the_geom, ltg_data.ltg_geom)
 WHERE cwa = 'MFR' ) AS a ON d = day_of_year
GROUP BY d
ORDER BY d ASC;

Но это не такигнорировать годЯ не должен удивляться, потому что «день» в date_trunc все еще учитывает год, который я предполагаю.

2017-12-27 00:00:00-08 |       0
2017-12-28 00:00:00-08 |       0
2017-12-29 00:00:00-08 |       0
2017-12-30 00:00:00-08 |       0
2017-12-31 00:00:00-08 |       0
2018-01-01 00:00:00-08 |       0
2018-01-02 00:00:00-08 |       12
2018-01-03 00:00:00-08 |       0

И этот запрос, в котором я пытаюсь преобразовать данные из generate_series() в text в формате 'DD-MM' для присоединения к таблице ltg_data в формате text.Говорит, что типы данных не совпадают.Я также пробовал extract, так как это могло бы обеспечить "doy" и "hour", которые сработали бы, но я также не могу сопоставить типы данных в этом запросе.Трудно сделать так, чтобы «generate_series» имел двойную точность.

SELECT to_char(d, 'MM-DD') AS DAY,
   count(a.lat) AS strikes
FROM
(SELECT generate_series('2017-01-01', '2018-12-31', interval '1 day') AS d) 
AS f
LEFT JOIN
(SELECT to_char(TIME, 'MM-DD') AS day_of_year,
      ltg_data.lat
FROM ltg_data
JOIN counties ON ST_contains(counties.the_geom, ltg_data.ltg_geom)
WHERE cwa = 'MFR' ) AS a ON f = day_of_year
GROUP BY d
ORDER BY d ASC;

Результат:

ERROR:  operator does not exist: record = text
LINE 4: ON f = day_of_year group by d order by d asc;
         ^
HINT:  No operator matches the given name and argument type(s). You might 
need to add explicit type casts.

Вывод: Я стремлюсь получить дневную и почасовую суммуотсчеты, которые охватывают многие годы, но группируются по 'MM-DD' и 'MM-DD-HH' (игнорируя год), при этом результаты запроса показывают все дни / часы, даже если они равны нулю .

Позже я также попытаюсь найти средние и процентили по дням и часам, так что если у вас есть какие-либо советы по этому поводу, я весь слух.Но моя текущая проблема заключается в получении полного результата для итогов.

1 Ответ

0 голосов
/ 16 мая 2018

По сути, чтобы сократить год, to_char(time, 'MMDD'), как вы уже пробовали, выполняет свою работу. Вы просто забыли также применить его к временным меткам, сгенерированным с помощью generate_series() до присоединения . И некоторые другие мелкие детали.

Чтобы упростить, а также повысить производительность и удобство, я предлагаю эту простую функцию для вычисления integer по шаблону 'MMDD' данного timestamp.

CREATE FUNCTION f_mmdd(date) RETURNS int LANGUAGE sql IMMUTABLE AS
'SELECT (EXTRACT(month FROM $1) * 100 + EXTRACT(day FROM $1))::int';

Сначала я использовал to_char(time, 'MMDD'), но переключился на приведенное выше выражение, которое оказалось самым быстрым в различных тестах.

дБ <> скрипка здесь

Может использоваться в индексах выражений, поскольку оно определено IMMUTABLE. И он по-прежнему допускает встраивание функции , поскольку он использует только EXTRACT (xyz FROM date) - что реализовано с помощью функции IMMUTABLE date_part(text, date) для внутреннего использования. (Обратите внимание, что datepart(text, timestamptz) - это всего лишь STABLE).

Тогда этот тип запроса выполняет свою работу:

SELECT d.mmdd, COALESCE(ct.ct, 0) AS total_count
FROM  (
   SELECT f_mmdd(d::date) AS mmdd  -- ignoring the year
   FROM   generate_series(timestamp '2018-01-01'  -- any dummy year
                        , timestamp '2018-12-31'
                        , interval '1 day') d
   ) d
LEFT  JOIN (
   SELECT f_mmdd(time::date) AS mmdd, count(*) AS ct
   FROM   counties c
   JOIN   ltg_data d ON ST_contains(c.the_geom, d.ltg_geom)
   WHERE  cwa = 'MFR'
   GROUP  BY 1
   ) ct USING (mmdd)
ORDER  BY 1;

Поскольку time (я бы использовал другое имя столбца) относится к типу данных timestamptz приведение time::date зависит от настройки часового пояса текущего сеанса. («Дни» определяются часовым поясом, в котором вы находитесь.) Чтобы получить неизменяемые (но более медленные) результаты, используйте конструкцию AT TIME ZONE с часовым поясом name например:

SELECT f_mmdd((time AT TIME ZONE 'Europe/Vienna')::date) ...

подробности:

Формат mmdd любым способом для отображения.

Приведение к integer является необязательным для целей данного конкретного запроса. Но так как вы планируете выполнять все виды запросов, вам понадобится индекс для выражения:

CREATE INDEX ltg_data_mmdd_idx ON event(f_mmdd(time));

(Не требуется для этого запроса.)
integer немного быстрее для этой цели. И для этого вам нужна (иначе необязательная) оболочка функции, поскольку to_char() определяется только STABLE, но нам нужно IMMUTABLE для индекса. Обновленное выражение (EXTRACT(month FROM $1) * 100 + EXTRACT(day FROM $1))::int равно IMMUTABLE, но функция-обёртка всё ещё удобна.

Связанный:

...