- 安装
- 文档
- 入门
- 连接
- 数据导入与导出
- 湖仓格式
- 客户端 API
- 概览
- 第三方客户端
- ADBC
- C
- C++
- CLI
- Dart
- Go
- Java (JDBC)
- Julia
- Node.js (已弃用)
- Node.js (Neo)
- ODBC
- PHP
- Python
- R
- Rust
- Swift
- Wasm
- SQL
- 介绍
- 语句
- 概览
- ANALYZE
- ALTER TABLE
- ALTER VIEW
- ATTACH 和 DETACH
- CALL
- CHECKPOINT
- COMMENT ON
- COPY
- CREATE INDEX
- CREATE MACRO
- CREATE SCHEMA
- CREATE SECRET
- CREATE SEQUENCE
- CREATE TABLE
- CREATE VIEW
- CREATE TYPE
- DELETE
- DESCRIBE
- DROP
- EXPORT 和 IMPORT DATABASE
- INSERT
- LOAD / INSTALL
- MERGE INTO
- PIVOT
- 性能分析
- SELECT
- SET / RESET
- SET VARIABLE
- SHOW 与 SHOW DATABASES
- SUMMARIZE
- 事务管理
- UNPIVOT
- UPDATE
- USE
- VACUUM
- 查询语法
- SELECT
- FROM 和 JOIN
- WHERE
- GROUP BY
- GROUPING SETS
- HAVING
- ORDER BY
- LIMIT 和 OFFSET
- SAMPLE
- 展开嵌套
- WITH
- WINDOW
- QUALIFY
- VALUES
- FILTER
- 集合操作
- 预处理语句
- 数据类型
- 表达式
- 函数
- 概览
- 聚合函数
- 数组函数
- 位字符串函数
- Blob 函数
- 日期格式化函数
- 日期函数
- 日期部分函数
- 枚举函数
- 间隔函数
- Lambda 函数
- 列表函数
- 映射函数
- 嵌套函数
- 数值函数
- 模式匹配
- 正则表达式
- 结构体函数
- 文本函数
- 时间函数
- 时间戳函数
- 带时区时间戳函数
- 联合函数
- 实用函数
- 窗口函数
- 约束
- 索引
- 元查询
- DuckDB 的 SQL 方言
- 示例
- 配置
- 扩展
- 核心扩展
- 概览
- 自动补全
- Avro
- AWS
- Azure
- Delta
- DuckLake
- 编码
- Excel
- 全文搜索
- httpfs (HTTP 和 S3)
- Iceberg
- ICU
- inet
- jemalloc
- Lance
- MySQL
- PostgreSQL
- 空间
- SQLite
- TPC-DS
- TPC-H
- UI
- Unity Catalog
- Vortex
- VSS
- 指南
- 概览
- 数据查看器
- 数据库集成
- 文件格式
- 概览
- CSV 导入
- CSV 导出
- 直接读取文件
- Excel 导入
- Excel 导出
- JSON 导入
- JSON 导出
- Parquet 导入
- Parquet 导出
- 查询 Parquet 文件
- 使用 file: 协议访问文件
- 网络和云存储
- 概览
- HTTP Parquet 导入
- S3 Parquet 导入
- S3 Parquet 导出
- S3 Iceberg 导入
- S3 Express One
- GCS 导入
- Cloudflare R2 导入
- 通过 HTTPS / S3 使用 DuckDB
- Fastly 对象存储导入
- 元查询
- ODBC
- 性能
- Python
- 安装
- 执行 SQL
- Jupyter Notebooks
- marimo Notebooks
- Pandas 上的 SQL
- 从 Pandas 导入
- 导出到 Pandas
- 从 Numpy 导入
- 导出到 Numpy
- Arrow 上的 SQL
- 从 Arrow 导入
- 导出到 Arrow
- Pandas 上的关系型 API
- 多个 Python 线程
- 与 Ibis 集成
- 与 Polars 集成
- 使用 fsspec 文件系统
- SQL 编辑器
- SQL 功能
- 代码片段
- 故障排除
- 术语表
- 离线浏览
- 操作手册
- 概览
- DuckDB 的占用空间
- 安装 DuckDB
- 日志
- 保护 DuckDB 安全
- 非确定性行为
- 限制
- DuckDB Docker 容器
- 开发
- 内部结构
- 站点地图
- 在线演示
时间戳代表时间点。因此,它们结合了 DATE(日期)和 TIME(时间)信息。它们可以使用类型名称后跟符合 ISO 8601 格式的字符串来创建,格式为 YYYY-MM-DD hh:mm:ss[.zzzzzzzzz][+-TT[:tt]],这也是我们在本文档中使用的格式。超出支持精度的小数位将被忽略。
时间戳类型
| 名称 | 别名 | 描述 |
|---|---|---|
TIMESTAMP_NS |
纳秒精度的朴素时间戳 (Naive timestamp) | |
TIMESTAMP |
DATETIME, TIMESTAMP WITHOUT TIME ZONE |
微秒精度的朴素时间戳 |
TIMESTAMP_MS |
毫秒精度的朴素时间戳 | |
TIMESTAMP_S |
秒精度的朴素时间戳 | |
TIMESTAMPTZ |
TIMESTAMP WITH TIME ZONE |
带时区信息的微秒精度时间戳 |
警告:由于目前尚无
TIMESTAMP_NS WITH TIME ZONE数据类型,带有纳秒精度和WITH TIME ZONE语义的外部列(例如 设置了 isAdjustedToUTC=true 的 Parquet 时间戳列)在被 DuckDB 读取时会转换为TIMESTAMP WITH TIME ZONE,从而丢失精度。
SELECT TIMESTAMP_NS '1992-09-20 11:30:00.123456789';
1992-09-20 11:30:00.123456789
SELECT TIMESTAMP '1992-09-20 11:30:00.123456789';
1992-09-20 11:30:00.123456
SELECT TIMESTAMP_MS '1992-09-20 11:30:00.123456789';
1992-09-20 11:30:00.123
SELECT TIMESTAMP_S '1992-09-20 11:30:00.123456789';
1992-09-20 11:30:00
SELECT TIMESTAMPTZ '1992-09-20 11:30:00.123456789';
1992-09-20 11:30:00.123456+00
SELECT TIMESTAMPTZ '1992-09-20 12:30:00.123456789+01:00';
1992-09-20 11:30:00.123456+00
DuckDB 区分 WITHOUT TIME ZONE(不带时区)和 WITH TIME ZONE(带时区)的时间戳(目前该类唯一的代表是 TIMESTAMP WITH TIME ZONE)。
尽管名称如此,TIMESTAMP WITH TIME ZONE 实际上并不存储时区信息。相反,它只存储从 Unix 纪元 1970-01-01 00:00:00+00 开始的非闰微秒数的 INT64 值,因此它能明确标识一个绝对时间点,或称 瞬时 (instant)。之所以称为“时区感知 (time zone aware)”和 WITH TIME ZONE,是因为该类型的时间戳算术、分箱 (binning) 以及字符串格式化都是在配置的时区中执行的,默认为系统时区,在上述示例中即为 UTC+00:00。
相应的 TIMESTAMP WITHOUT TIME ZONE 存储相同的 INT64 数值,但其算术运算、分箱和字符串格式化遵循协调世界时 (UTC) 的直接规则,不含偏移量或时区。因此,TIMESTAMP 可以被解释为 UTC 时间戳,但更常见的情况是,它们用于表示在未指定时区下记录的本地时间观测值,对这些类型的操作可以理解为遵循名义时间逻辑的元组字段处理。将此类观测值(也可能存储在没有时区说明或 UTC 偏移量的原始字符串中)消除歧义并转换为明确的 TIMESTAMP WITH TIME ZONE 瞬时值,是一个常见的数据清洗问题。一种解决方案是在字符串后附加 UTC 偏移量,然后显式转换为 TIMESTAMP WITH TIME ZONE。或者,也可以先创建 TIMESTAMP WITHOUT TIME ZONE,然后结合时区说明来获取时区感知的 TIMESTAMP WITH TIME ZONE。
字符串与朴素/时区感知时间戳之间的转换
在不带 UTC 偏移量或 IANA 时区名称的字符串与 WITHOUT TIME ZONE 类型之间的转换是明确且直接的。在带有 UTC 偏移量或时区名称的字符串与 WITH TIME ZONE 类型之间的转换也是明确的,但需要 ICU 扩展来处理时区名称。
当不带 UTC 偏移量或时区名称的字符串转换为 WITH TIME ZONE 类型时,该字符串会根据配置的时区进行解释。当带有 UTC 偏移量的字符串传递给 WITHOUT TIME ZONE 类型时,偏移量或时区说明会被忽略。当带有非 UTC 时区名称的字符串传递给 WITHOUT TIME ZONE 类型时,会抛出错误。
最后,当 WITH TIME ZONE 和 WITHOUT TIME ZONE 类型通过显式或隐式转换相互转换时,转换过程将使用配置的时区。若要使用其他时区,可以使用 ICU 扩展提供的 timezone 函数。
SELECT
timezone('America/Denver', TIMESTAMP '2001-02-16 20:38:40') AS aware1,
timezone('America/Denver', TIMESTAMPTZ '2001-02-16 04:38:40') AS naive1,
timezone('UTC', TIMESTAMP '2001-02-16 20:38:40+00:00') AS aware2,
timezone('UTC', TIMESTAMPTZ '2001-02-16 04:38:40 Europe/Berlin') AS naive2;
| aware1 | naive1 | aware2 | naive2 |
|---|---|---|---|
| 2001-02-17 04:38:40+01 | 2001-02-15 20:38:40 | 2001-02-16 21:38:40+01 | 2001-02-16 03:38:40 |
请注意,TIMESTAMP 在结果显示时不带时区说明(遵循 ISO 8601 本地时间规则),而时区感知的 TIMESTAMPTZ 则会显示所配置时区(示例中为 'Europe/Berlin')的 UTC 偏移量。'America/Denver' 和 'Europe/Berlin' 在所涉所有瞬时的 UTC 偏移量分别为 -07:00 和 +01:00。
特殊值
可以使用三个特殊字符串来创建时间戳
| 输入字符串 | 描述 |
|---|---|
epoch |
1970-01-01 00:00:00[+00] (Unix 系统时间零点) |
infinity |
晚于所有其他时间戳 |
-infinity |
早于所有其他时间戳 |
值 infinity 和 -infinity 为特殊情况,显示时保持不变,而 epoch 仅作为一种记号简写,在读取时会转换为相应的时间戳值。
SELECT '-infinity'::TIMESTAMP, 'epoch'::TIMESTAMP, 'infinity'::TIMESTAMP;
| 负 | Epoch(纪元) | 正 |
|---|---|---|
| -infinity | 1970-01-01 00:00:00 | infinity |
函数
请参阅 时间戳函数。
时区
要理解时区和 WITH TIME ZONE 类型,最好从两个概念入手:瞬时 (instants) 和 时间分箱 (temporal binning)。
瞬时 (Instants)
瞬时是指绝对时间中的一个点,通常表示为从固定时间点(称为 epoch,纪元)开始的某个时间增量的计数值。这类似于地球表面位置的使用方式,即相对于赤道和格林威治子午线的经纬度。在 DuckDB 中,该固定点是 Unix 纪元 1970-01-01 00:00:00+00:00,增量单位取决于具体的数据类型(秒、毫秒、微秒或纳秒)。
时间分箱 (Temporal Binning)
分箱是连续数据处理中的一种常用做法:将一系列可能的值划分为连续的子集,分箱操作将实际值映射到它们所属的 箱 (bin) 中。时间分箱仅仅是将这种做法应用于瞬时;例如,将瞬时分箱为年、月和日。
时间分箱规则非常复杂,通常分为两类:时区和历法。对于大多数任务,历法通常是广泛使用的公历,但时区应用了特定区域的规则,差异很大。例如,下面是 'America/Los_Angeles' 时区在纪元附近的分箱情况:
最常见的时间分箱问题发生在夏令时转换时。下例包含一次夏令时转换,其中“小时”箱长为两小时。为了区分这两个小时,需要另一个包含 UTC 偏移量的箱范围:
时区支持
TIMESTAMPTZ 类型可以使用合适的扩展分箱为历法和时钟箱。内置的 ICU 扩展使用 International Components for Unicode (ICU) 时区和历法函数实现了所有的分箱和算术函数。
要设置使用的时区,请先加载 ICU 扩展。ICU 扩展随多个 DuckDB 客户端(包括 Python、R、JDBC 和 ODBC)预先打包,因此在这些情况下可以跳过此步骤。在其他情况下,您可能需要先安装并加载 ICU 扩展。
INSTALL icu;
LOAD icu;
接下来,使用 SET TimeZone 命令
SET TimeZone = 'America/Los_Angeles';
之后,TIMESTAMPTZ 的时间分箱操作将使用指定的时区实现。
可用时区列表可以从 pg_timezone_names() 表函数获取
SELECT
name,
abbrev,
utc_offset
FROM pg_timezone_names()
ORDER BY
name;
您还可以找到一份可用时区的参考表。
历法支持
ICU 扩展还支持使用 SET Calendar 命令的非公历历法。请注意,仅当 DuckDB 客户端未打包 ICU 扩展时,才需要执行 INSTALL 和 LOAD 步骤。
INSTALL icu;
LOAD icu;
SET Calendar = 'japanese';
之后,TIMESTAMPTZ 的时间分箱操作将使用指定的历法实现。在此示例中,era(纪元)部分现在将报告日本天皇纪元编号。
可用历法列表可以从 icu_calendar_names() 表函数获取
SELECT name
FROM icu_calendar_names()
ORDER BY 1;
设置
TimeZone 和 Calendar 设置的当前值由 ICU 在启动时确定。它们可以从 duckdb_settings() 表函数中查询
SELECT *
FROM duckdb_settings()
WHERE name = 'TimeZone';
| name | value | description | 输入类型 |
|---|---|---|---|
| TimeZone | 欧洲/阿姆斯特丹 | 当前时区 | VARCHAR |
SELECT *
FROM duckdb_settings()
WHERE name = 'Calendar';
| name | value | description | 输入类型 |
|---|---|---|---|
| Calendar | gregorian(公历) | 当前历法 | VARCHAR |
如果您发现分箱操作的表现与预期不符,请检查
TimeZone和Calendar的值,并根据需要进行调整。