标记不连续的日期范围
Posted
技术标签:
【中文标题】标记不连续的日期范围【英文标题】:Mark non-contiguous date ranges 【发布时间】:2011-08-19 18:57:14 【问题描述】:背景(输入)
Global Historical Climatology Network 在其收集的天气测量数据中标记了无效或错误的数据。删除这些元素后,有大量数据不再具有连续的日期部分。数据类似于:
"2007-12-01";14 -- Start of December
"2007-12-29";8
"2007-12-30";11
"2007-12-31";7
"2008-01-01";8 -- Start of January
"2008-01-02";12
"2008-01-29";0
"2008-01-31";7
"2008-02-01";4 -- Start of February
... entire month is complete ...
"2008-02-29";12
"2008-03-01";14 -- Start of March
"2008-03-02";17
"2008-03-05";17
问题(输出)
虽然可以推断缺失数据(例如,通过其他年份的平均值)以提供连续范围,但为了简化系统,我想根据是否有连续的日期范围来标记非连续段来填充月份:
D;"2007-12-01";14 -- Start of December
D;"2007-12-29";8
D;"2007-12-30";11
D;"2007-12-31";7
D;"2008-01-01";8 -- Start of January
D;"2008-01-02";12
D;"2008-01-29";0
D;"2008-01-31";7
"2008-02-01";4 -- Start of February
... entire month is complete ...
"2008-02-29";12
D;"2008-03-01";14 -- Start of March
D;"2008-03-02";17
D;"2008-03-05";17
一些测量是在 1843 年进行的。
问题
对于所有气象站,您将如何标记月份中缺少一天或多天的所有天?
源代码
选择数据的代码类似于:
select
m.id,
m.taken,
m.station_id,
m.amount
from
climate.measurement
相关想法
生成一个包含连续日期的表格,并将它们与测量的数据日期进行比较。
What is the most straightforward way to pad empty dates in sql results (on either mysql or perl end)? How do I group on continuous ranges http://msdn.microsoft.com/en-us/library/aa175780%28v=sql.80%29.aspx更新
可以使用本节中的 SQL 重新创建问题。
表格
创建表如下:
CREATE TABLE climate.calendar
(
id serial NOT NULL,
n character varying(2) NOT NULL,
d date NOT NULL,
"valid" boolean NOT NULL DEFAULT true,
CONSTRAINT calendar_pk PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);
生成数据
以下 SQL 将数据插入到表中(id
[int]、n
ame [varchar]、d
ate [date]、valid
[boolean]):
insert into climate.calendar (n, d)
select 'A', (date('1982-01-1') + (n || ' days')::interval)::date cal_date
from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n
insert into climate.calendar (n, d)
select 'B', (date('1982-01-1') + (n || ' days')::interval)::date cal_date
from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n
insert into climate.calendar (n, d)
select 'C', (date('1982-01-1') + (n || ' days')::interval)::date cal_date
from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n
insert into climate.calendar (n, d)
select 'D', (date('1982-01-1') + (n || ' days')::interval)::date cal_date
from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n
insert into climate.calendar (n, d)
select 'E', (date('1982-01-1') + (n || ' days')::interval)::date cal_date
from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n
insert into climate.calendar (n, d)
select 'F', (date('1982-01-1') + (n || ' days')::interval)::date cal_date
from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n
'A'
到 'F'
的值表示在特定日期进行测量的气象站的名称。
删除随机行
删除部分行如下:
delete from climate.calendar where id in (select id from climate.calendar order by random() limit 5000);
尝试 #1
对于一个月中缺少一天或多天的所有天数,以下内容不会将 valid
标志切换为 false
:
UPDATE climate.calendar
SET valid = false
WHERE date_trunc('month', d) IN (
SELECT DISTINCT date_trunc('month', d)
FROM climate.calendar A
WHERE NOT EXISTS (
SELECT 1
FROM climate.calendar B
WHERE A.d - 1 = B.d
)
);
尝试 #2
以下 SQL 产生一个空结果集:
with gen_calendar as (
select (date('1982-01-1') + (n || ' days')::interval)::date cal_date
from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n
)
select gc.cal_date
from gen_calendar gc
left join climate.calendar c on c.d = gc.cal_date
where c.d is null;
尝试 #3
以下 SQL 生成所有可能的站名和日期组合:
select
distinct( cc.n ), t.d
from
climate.calendar cc,
(
select (date('1982-01-1') + (n || ' days')::interval)::date d
from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n
) t
order by
cc.n
但是,在实际数据中,有数百个站点,并且日期可以追溯到 1800 年代中期,因此所有站点的所有日期的笛卡尔坐标都太大。如果有足够的时间,这种方法可能会奏效……一定有更快的方法。
尝试 #4
PostgreSQL 有窗口函数。
How to select specific changes using windowing functions in postgres
谢谢!
【问题讨论】:
这是表示您想找出有间隔的月份的一种方式吗? @Catcall:是的。这是一种复杂的说法。考虑到每月的天数、闰年等等。 +1 表示编辑,这让我更清楚问题。 【参考方案1】:generate_series()
PostgreSQL 的generate_series()
函数可以创建一个包含连续日期列表的视图:
with calendar as (
select ((select min(date) from test)::date + (n || ' days')::interval)::date cal_date
from generate_series(0, (select max(date) - min(date) from test)) n
)
select cal_date
from calendar c
left join test t on t.date = c.cal_date
where t.date is null;
表达式select max(date) - min(date) from test
可能差一。
计算每月的天数
识别无效月份的一种方法是创建两个视图。第一个计算每个站点每个月应该产生的每日读数的数量。 (注意climate.calendar
被翻译成climate_calendar
。)第二个返回每个站点每月产生的实际每日读数。
每个站点每月的最大天数
此视图将返回每个站点每月的实际天数。 (例如,二月总是有 28 或 29 天。)
create view count_max_station_calendar_days as
with calendar as (
select ((select min(d) from climate_calendar)::date + (n || ' days')::interval)::date cal_date
from generate_series(0, (select max(d) - min(d) from climate_calendar)) n
)
select n, extract(year from cal_date) yr, extract(month from cal_date) mo, count(*) num_days
from stations cross join calendar
group by n, yr, mo
order by n, yr, mo
每个站点每月的实际天数
返回的总天数将少于总数。 (例如,一月总是有 31 天或更少。)
create view count_actual_station_calendar_days as
select n, extract(year from d) yr, extract(month from d) mo, count(*) num_days
from climate_calendar
group by n, yr, mo
order by n, yr, mo;
在生产环境中删除 ORDER BY
子句(它们有助于开发)。
比较视图
将两个视图加入到新视图中,识别需要标记的电台和月份:
create view invalid_station_months as
select m.n, m.yr, m.mo, m.num_days - a.num_days num_days_missing
from count_max_station_calendar_days m
inner join count_actual_station_calendar_days a
on (m.n = a.n and m.yr = a.yr and m.mo = a.mo and m.num_days <> a.num_days)
n yr mo num_days_missing
--
A 1982 1 1
E 2007 3 1
num_days_missing
列不是必需的,但很有用。
这些是需要更新的行:
select cc.*
from climate_calendar cc
inner join invalid_station_months im
on (cc.n = im.n and
extract(year from cc.d) = im.yr and
extract(month from cc.d) = im.mo)
where valid = true
更新数据库
要更新它们,id
键很方便。
update climate_calendar
set valid = false
where id in (
select id
from climate_calendar cc
inner join invalid_station_months im
on (cc.n = im.n and
extract(year from cc.d) = im.yr and
extract(month from cc.d) = im.mo)
where valid = true
);
【讨论】:
@Dave Jarvis:我根据对您问题的编辑添加了很多内容。它包括三个有用的视图和一个有效的 UPDATE 语句。 计算天数很有见地。再次感谢您。 查询成功返回:498215579行受影响,173600096毫秒执行时间。 ;-) 需要 96 GB 的临时空间。 @Dave Jarvis:无论如何你都需要休息一下,对吧?你是在一次交易中做到这一点的吗?【参考方案2】:假设每天不能超过一行,这应该返回行数不等于该月天数的所有月份。
SELECT station_id, DATE_TRUNC('month', d)
FROM climate.calendar
GROUP BY station_id, DATE_TRUNC('month', d)
HAVING COUNT(*) <>
DATE_PART('month',
DATE_TRUNC('month', d) + INTERVAL '1 month' - INTERVAL '1 day')
【讨论】:
谢谢。不幸的是,它每年每个月都会返回,因为它没有考虑每个站点的日期分布。【参考方案3】:假设您有一个名为 is_contiguous 的 BOOLEAN 字段,这是您可以做到的一种方法。根据需要修改:
UPDATE measurement
SET is_contiguous = FALSE
WHERE NOT EXISTS (
SELECT 1
FROM measurement B
WHERE measurement.taken - 1 = B.taken
);
编辑:
我相信我误解了您的要求。我以为您想标记不连续的单个日期。但显然,如果缺少任意天数,您希望将整个月的日期标记为不连续。
编辑 2:
这是我的原始(不正确)查询的修改版本,它选择了缺少任何日期的不同月份:
UPDATE measurement
SET is_contiguous = FALSE
WHERE date_trunc('month', taken) IN (
SELECT DISTINCT date_trunc('month', taken)
FROM measurement A
WHERE NOT EXISTS (
SELECT 1
FROM measurement B
WHERE A.taken - 1 = B.taken
)
);
【讨论】:
以上是关于标记不连续的日期范围的主要内容,如果未能解决你的问题,请参考以下文章