sql - 如何在 MM-DD 上汇总多年的数据,忽略年份

标签 sql postgresql datetime date-arithmetic generate-series

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 的大小(约 8 亿行):

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”成为 double 。

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”(忽略年份)分组的每日和每小时总计数,查询结果显示所有天/小时,即使它们为零

稍后我还会尝试找出几天和几小时的平均值和百分位数,所以如果您对此有任何建议,我会洗耳恭听。但我目前的问题是如何获得完整的总计结果。

最佳答案

基本上,要截断年份,to_char(time, 'MMDD') 就像您已经尝试过的那样。您只是忘记将它应用于generate_series()加入之前生成的时间戳。以及其他一些小细节。

为了简化并提高性能和方便性,我建议使用这个简单的函数来根据给定 timestamp 的模式“MMDD”计算一个integer

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'),但后来改用上面的表达式,结果在各种测试中证明是最快的。

db<> fiddle here

它可以在表达式索引中使用,因为它被定义为 IMMUTABLE。它仍然允许 function inlining因为它仅使用 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 取决于时区当前 session 的设置。 (“天数”由您所在的时区定义。)要获得不可变(但速度较慢)的结果,请使用带有时区 nameAT TIME ZONE 构造,例如:

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))::intIMMUTABLE,但是函数包装器仍然很方便。

相关:

关于sql - 如何在 MM-DD 上汇总多年的数据,忽略年份,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50374136/

相关文章:

ruby-on-rails - Rails 4 - 将日期时间转换为单独的日期和时间字段

SQL:多边形的并集

mysql - 查找表中的所有重复行

python - 用于打印日期的 django 时区

postgresql - 如何使用 jdbc API 在 java 中为 postgresql 数据库执行 sql gzipped 脚本?

node.js - knex 查询不断要求安装 sqlite 模块

excel - 数据图表在 Excel 中显示一天中的时间

mysql - 如何通过连接到索引较差的表来改进查询?

sql - 如何对数据集进行密集排名

sql - 具有绑定(bind)参数的用户定义函数