跳到主要内容

Interval 类型

INTERVAL 表示可加到或减去 DATETIMESTAMPTIMESTAMPTZTIME 值的时间段。

名称描述
INTERVAL时间段

INTERVAL 可通过“数值 + 单位”的方式构造。 凡不是 monthsdaysmicroseconds 的单位,都会被换算为这三种基础单位中更细一级的等价值。

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 当与单位关键字一起使用时,小数值会被截断为整数(除非单位是 SECONDSMILLISECONDS)。

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 组件不会继续拆分。为说明这一点,下列查询先将三个基础单位的随机值求和生成名为 periodINTERVAL,再提取上述六个部分并重新相加,结果始终与原始 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

部分公式
centurydatepart('year', interval) // 100
decadedatepart('year', interval) // 10
quarterdatepart('month', interval) // 3 + 1
seconddatepart('microsecond', interval) // 1_000_000
milliseconddatepart('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 可通过 +- 运算符与 TIMESTAMPTIMESTAMPTZDATETIME 进行加减。

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'
;

DATEINTERVAL 的结果是 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 从两个 TIMESTAMPINTERVAL 差值中提取某一部分,并不等价于 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 运算符页面