kha*_*jlk 4 postgresql datetime set-returning-functions
作为 Postgres 的新手用户,ad我的 PostgreSQL 9.5 (x64) 数据库中有一个 87 行的表。除了其他列之外,它还有两列“开始”和“结束”,其日期时间范围如下:
ID Start End
1 2003-06-07 00:00:00 2004-09-30 23:59:59
Run Code Online (Sandbox Code Playgroud)
我需要将范围拆分为存储在数据库中的单独行(从开始年份到间隔的最后一年)中的一年窗口,如下所示:
ID Start
1_2003 2003-06-07 00:00:00 2003-12-31 23:59:59
1_2004 2004-01-01 00:00:00 2004-09-30 23:59:59
Run Code Online (Sandbox Code Playgroud)
使用运算符||'_'||和Extract()函数,我可以将 ID 与年份连接起来。此外,这个问题解决了如何分割的间隔周,这个节目怎么好几天做相同的,但他们没有解决如何在专门年分裂的间隔。
我避免了这个问题,因为我不想采用基于存储过程的方法。我知道generate_series()从开始和停止参数返回一个系列,但实际上我正在努力打破一年的最后一天的间隔,然后从下一行的第一天重新开始。如果有人能指导我做到这一点,我将不胜感激?
几个建议:
使用合法的、小写的、不带引号的标识符可以避免很多混乱。end(并且,在较小程度上start)是保留字。
您的id列似乎是数字类型integer,而不是text。
由于您不需要start_dayand 中的时间组件end_day,因此适当的数据类型是date, nottimestamp。
为什么要连接id新的“ID”和年份?year如果 PK 需要它,请添加第二列。或者根本不添加额外的列。它可以廉价地从新的动态中提取start_day。通常,如果可以避免,请不要冗余存储数据。
通常,timestamp范围使用包含下限和不包含上限。由于时间戳可以有更清晰的小数位数(在 Postgres 中最多 6 位)。您的输入2003-12-31 23:59:59将失败2003-12-31 23:59:59.123。
因此,您的表格可能如下所示:
CREATE TABLE ad (
id int PRIMARY KEY
, start_day date NOT NULL -- *inclusive* lower bound
, end_day date NOT NULL -- *exclusive* upper bound
CHECK (end_day > start_day) -- enforce legal input
);
Run Code Online (Sandbox Code Playgroud)
正确的测试值:
INSERT INTO ad(id, start_day, end_day)
VALUES
(1, '2003-06-07', '2004-10-01') -- span 2 years (your example)
, (2, '2003-06-07', '2003-06-08') -- 1 day in same year
, (3, '2003-06-07', '2003-10-01') -- span 1 year
, (4, '2003-06-07', '2006-10-01'); -- span many years
Run Code Online (Sandbox Code Playgroud)
generate_series()在LATERALjoin 中使用,基于开始和结束日期,用 截断到年份date_trunc()。这会以新的开始日期每年产生一行。添加一年,您就有了新的结束日期。除了第一行和最后一行 per id,您分别用GREATEST和LEAST替换了正确的开始/结束。瞧。
-- CREATE TABLE ad_year AS
SELECT ad.id
, extract('year' FROM y)::int AS year
, GREATEST(y , ad.start_day) AS start_day
, LEAST (y + interval '1 year', ad.end_day) AS end_day
FROM ad
, generate_series(date_trunc('year', start_day::timestamp) -- cast to ts here!
, date_trunc('year', end_day::timestamp)
, interval '1 year') y;
Run Code Online (Sandbox Code Playgroud)
需要注意的是date_trunc()回报率timestamptz的timestamptz输入和timestamp对timestamp输入。对于date输入,它默认为timestamptz。既然你似乎忽略了时区,铸就了date以timestamp明确的(start_day::timestamp)。
结果:
CREATE TABLE ad (
id int PRIMARY KEY
, start_day date NOT NULL -- *inclusive* lower bound
, end_day date NOT NULL -- *exclusive* upper bound
CHECK (end_day > start_day) -- enforce legal input
);
Run Code Online (Sandbox Code Playgroud)
如果根据结果创建一个新表,我建议将其(id, year)作为主键。
旁白:这不是接线员:(||'_'||也不是可爱的小脸)。它是 2 个连接运算符||和一个字符串文字'_'。