Interval 类型
INTERVAL 表示可加到或减去 DATE、TIMESTAMP、TIMESTAMPTZ 或 TIME 值的时间段。
| 名称 | 描述 |
|---|---|
INTERVAL | 时间段 |
INTERVAL 可通过“数值 + 单位”的方式构造。
凡不是 months、days、microseconds 的单位,都会被换算为这三种基础单位中更细一级的等价值。
SELECT
INTERVAL 1 YEAR, -- single unit using YEAR keyword; stored as 12 months
INTERVAL (random() * 10) YEAR, -- parentheses necessary for variable amounts;
-- stored as integer number of months
INTERVAL '1 month 1 day', -- string type necessary for multiple units; stored as (1 month, 1 day)
'16 months'::INTERVAL, -- string cast supported; stored as 16 months
'48:00:00'::INTERVAL, -- HH::MM::SS string supported; stored as (48 * 60 * 60 * 1e6 microseconds)
;
Warning 当与单位关键字一起使用时,小数值会被截断为整数(除非单位是
SECONDS或MILLISECONDS)。SELECT INTERVAL '1.5' YEARS;
-- Returns 12 months; equivalent to `to_years(CAST(trunc(1.5) AS INTEGER))`若需更高精度,请将单位写入字符串,或使用更细粒度单位;例如
INTERVAL '1.5 years'或INTERVAL 18 MONTHS。
之所以需要三个相互独立的基础单位,是因为一个月并不对应固定天数(2 月天数少于 3 月),而一天也不对应固定微秒数(由于夏令时,一天可能是 25 小时或 23 小时)。
按组件拆分后,INTERVAL 类型非常适合在日期上加减指定时间单位。例如,我们可以使用下列 SQL 生成每个月第一天的表:
SELECT DATE '2000-01-01' + INTERVAL (i) MONTH
FROM range(12) t(i);
当通过 datepart 函数拆解 INTERVAL 时,months 组件会进一步拆成 year 与 month,microseconds 组件会拆成 hour、minute 与 microsecond;days 组件不会继续拆分。为说明这一点,下列查询先将三个基础单位的随机值求和生成名为 period 的 INTERVAL,再提取上述六个部分并重新相加,结果始终与原始 period 相等。
SELECT
period = list_reduce(
[INTERVAL (datepart(part, period) || part) FOR part IN
['year', 'month', 'day', 'hour', 'minute', 'microsecond']
],
(i1, i2) -> i1 + i2
) -- always true
FROM (
VALUES (
INTERVAL (random() * 123_456_789_123) MICROSECONDS
+ INTERVAL (random() * 12_345) DAYS
+ INTERVAL (random() * 12_345) MONTHS
)
) _(period);
Warning microseconds 组件只会拆分为小时、分钟和微秒,而不是小时、分钟、秒 和微秒。
下表给出了 datepart 如何基于三个基础单位提取这些部分的公式。
| 部分 | 公式 |
|---|---|
year | #months // 12 |
month | #months % 12 |
day | #days |
hour | #microseconds // (60 * 60 * 1_000_000) |
minute | (#microseconds // (60 * 1_000_000)) % 60 |
microsecond | #microseconds % (60 * 1_000_000) |
此外,datepart 还可从 INTERVAL 中提取 century、decade、quarter、second 与 millisecond。但这些部分在重组原始 INTERVAL 时并非必需。事实上,若在前述查询中额外提取这些部分,提取值之和通常会大于原始 period。
| 部分 | 公式 |
|---|---|
century | datepart('year', interval) // 100 |
decade | datepart('year', interval) // 10 |
quarter | datepart('month', interval) // 3 + 1 |
second | datepart('microsecond', interval) // 1_000_000 |
millisecond | datepart('microsecond', interval) // 1_000 |
所有单位都使用从 0 开始的索引,只有 quarter 使用从 1 开始的索引。
例如:
SELECT
datepart('decade', INTERVAL 12 YEARS), -- returns 1
datepart('year', INTERVAL 12 YEARS), -- returns 12
datepart('second', INTERVAL 1_234 MILLISECONDS), -- returns 1
datepart('microsecond', INTERVAL 1_234 MILLISECONDS), -- returns 1_234_000
;
与时间戳、日期和 Interval 的算术
INTERVAL 可通过 + 和 - 运算符与 TIMESTAMP、TIMESTAMPTZ、DATE 和 TIME 进行加减。
SELECT
DATE '2000-01-01' + INTERVAL 1 YEAR,
TIMESTAMP '2000-01-01 01:33:30' - INTERVAL '1 month 13 hours',
TIME '02:00:00' - INTERVAL '3 days 23 hours', -- wraps; equals TIME '03:00:00'
;
DATE加INTERVAL的结果是TIMESTAMP,即使该INTERVAL不含微秒组件。其结果等价于先将DATE转为TIMESTAMP(时间部分设为00:00:00)再相加。
反过来,两个 TIMESTAMP 或两个 TIMESTAMPTZ 相减会生成一个只包含 days 和 microseconds 组件的 INTERVAL,用于描述时间差。例如:
SELECT
TIMESTAMP '2000-02-06 12:00:00' - TIMESTAMP '2000-01-01 11:00:00', -- 36 days 1 hour
TIMESTAMP '2000-02-01' + (TIMESTAMP '2000-02-01' - TIMESTAMP '2000-01-01'), -- '2000-03-03', NOT '2000-03-01'
;
两个 DATE 相减不会生成 INTERVAL,而是返回两个日期之间相隔的天数(整数值)。
Warning 从两个
TIMESTAMP的INTERVAL差值中提取某一部分,并不等价于datediff函数按该单位计算两者之间分区边界数量:SELECT
datediff('day', TIMESTAMP '2020-01-01 01:00:00', TIMESTAMP '2020-01-02 00:00:00'), -- 1
datepart('day', TIMESTAMP '2020-01-02 00:00:00' - TIMESTAMP '2020-01-01 01:00:00'), -- 0
;
相等性与比较
仅在相等和排序比较时,系统会将 INTERVAL 的总微秒数按如下方式计算:把 days 基础单位换算为 24 * 60 * 60 * 1e6 微秒,把 months 基础单位按 30 天换算,即 30 * 24 * 60 * 60 * 1e6 微秒。
因此,即使两个 INTERVAL 在功能上不同,也可能比较为相等;并且 INTERVAL 的排序在加到日期或时间戳后也不总能保持。
例如:
INTERVAL 30 DAYS = INTERVAL 1 MONTH- 但
DATE '2020-01-01' + INTERVAL 30 DAYS != DATE '2020-01-01' + INTERVAL 1 MONTH。
以及
INTERVAL '30 days 12 hours' > INTERVAL 1 MONTH- 但
DATE '2020-01-01' + INTERVAL '30 days 12 hours' < DATE '2020-01-01' + INTERVAL 1 MONTH。
函数
可用日期部分列表参见日期部分函数页面,可用于 INTERVAL。
针对 interval 运算的函数参见Interval 运算符页面。