如何在 MM-DD 上汇总多年的数据,忽略年份
How to aggregate data from multiple years on MM-DD, ignoring year
Postgres 版本 9.4.18,PostGIS 版本 2.2。
这是我正在使用的 tables(并且不太可能对 table 结构进行重大更改):
Table 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
Table 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。以及一年中每一天 table ltg_data
中的行数。我最终也希望在一年中的每一天的每个小时都做同样的事情 ('MM-DD-HH')。
一个 group by
语句应该完成这个,但是我很难加入 "big" table 和 generate_series()
.[=29 生成的天数=]
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 中的 "day" 仍在考虑我猜的年份。
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()
转换为 'DD-MM' 格式的 text
以加入 ltg_data
table 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'(忽略年份)分组的每日和每小时总计数,查询结果显示 all days/hours 即使它们为零.
稍后我还将尝试找出几天和几小时的平均值和百分位数,所以如果您对此有任何建议,我会洗耳恭听。但我目前的问题是如何获得完整的总计结果。
基本上,要截断年份,to_char(time, 'MMDD')
就像您已经尝试过的那样。您只是忘记了 also 将其应用于 generate_series()
before 生成的时间戳。以及其他一些小细节。
为了简化并提高性能和方便性,我建议使用这个简单的函数来根据给定 timestamp
的模式 'MMDD' 计算 integer
。
CREATE FUNCTION f_mmdd(date) RETURNS int LANGUAGE sql IMMUTABLE AS
'SELECT (EXTRACT(month FROM ) * 100 + EXTRACT(day FROM ))::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
取决于您当前会话的时区设置。 ("Days" 由您所在的时区定义。)要获得不可变(但速度较慢)的结果,请使用带有时区 name 的 AT TIME ZONE
构造,例如:
SELECT f_mmdd((time AT TIME ZONE 'Europe/Vienna')::date) ...
详情:
- Ignoring time zones altogether in Rails and PostgreSQL
按照您喜欢的方式设置 mmdd
格式。
转换为 integer
对于此特定查询而言是可选的。但是由于您计划执行各种查询,因此您最终需要表达式的索引:
CREATE INDEX ltg_data_mmdd_idx ON event(f_mmdd(time));
(此 查询不需要。)
integer
为此目的要快一点。
你需要(否则可选)函数包装器,因为 to_char()
仅定义 STABLE
,但我们需要 IMMUTABLE
作为索引。更新后的表达式 (EXTRACT(month FROM ) * 100 + EXTRACT(day FROM ))::int
为 IMMUTABLE
,但函数包装器仍然很方便。
相关:
- How do you do date math that ignores the year?
- Generating time series between two dates in PostgreSQL
Postgres 版本 9.4.18,PostGIS 版本 2.2。
这是我正在使用的 tables(并且不太可能对 table 结构进行重大更改):
Table 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
Table 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。以及一年中每一天 table ltg_data
中的行数。我最终也希望在一年中的每一天的每个小时都做同样的事情 ('MM-DD-HH')。
一个 group by
语句应该完成这个,但是我很难加入 "big" table 和 generate_series()
.[=29 生成的天数=]
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 中的 "day" 仍在考虑我猜的年份。
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()
转换为 'DD-MM' 格式的 text
以加入 ltg_data
table 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'(忽略年份)分组的每日和每小时总计数,查询结果显示 all days/hours 即使它们为零.
稍后我还将尝试找出几天和几小时的平均值和百分位数,所以如果您对此有任何建议,我会洗耳恭听。但我目前的问题是如何获得完整的总计结果。
基本上,要截断年份,to_char(time, 'MMDD')
就像您已经尝试过的那样。您只是忘记了 also 将其应用于 generate_series()
before 生成的时间戳。以及其他一些小细节。
为了简化并提高性能和方便性,我建议使用这个简单的函数来根据给定 timestamp
的模式 'MMDD' 计算 integer
。
CREATE FUNCTION f_mmdd(date) RETURNS int LANGUAGE sql IMMUTABLE AS
'SELECT (EXTRACT(month FROM ) * 100 + EXTRACT(day FROM ))::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
取决于您当前会话的时区设置。 ("Days" 由您所在的时区定义。)要获得不可变(但速度较慢)的结果,请使用带有时区 name 的 AT TIME ZONE
构造,例如:
SELECT f_mmdd((time AT TIME ZONE 'Europe/Vienna')::date) ...
详情:
- Ignoring time zones altogether in Rails and PostgreSQL
按照您喜欢的方式设置 mmdd
格式。
转换为 integer
对于此特定查询而言是可选的。但是由于您计划执行各种查询,因此您最终需要表达式的索引:
CREATE INDEX ltg_data_mmdd_idx ON event(f_mmdd(time));
(此 查询不需要。)
integer
为此目的要快一点。
你需要(否则可选)函数包装器,因为 更新后的表达式 to_char()
仅定义 STABLE
,但我们需要 IMMUTABLE
作为索引。(EXTRACT(month FROM ) * 100 + EXTRACT(day FROM ))::int
为 IMMUTABLE
,但函数包装器仍然很方便。
相关:
- How do you do date math that ignores the year?
- Generating time series between two dates in PostgreSQL