blob: 7f9a7c73799bc328c443ee64c6bfb76642c08a82 [file] [view]
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# DuckDB TsFile 扩展
本文面向需要在 DuckDB 中分析或生成 Apache TsFile 的数据工程师、平台开发者和分析开发者。TsFile 扩展已作为 DuckDB Community Extension 发布,扩展把 TsFile 作为 SQL 数据源和 COPY 输出格式,形成“读取 → 查询 → 写回”的完整工作流。
**先记住两件事:** 读取使用 `read_tsfile(path, table_name)`;写入使用 `COPY ... TO ... (FORMAT tsfile)`。当前版本面向本地、表模型 TsFile,写入前请按 TAG 列和时间列排序。
## 快速开始
### 启动 DuckDB 并加载扩展
日常使用优先安装并加载 DuckDB 社区扩展:
```sql
INSTALL tsfile FROM community;
```
```sql
LOAD tsfile;
```
首次使用时安装社区扩展;之后每次启动 DuckDB 只需执行 `LOAD tsfile;`。
### 读取第一张 TsFile
```sql
SELECT *
FROM read_tsfile('/data/measurements.tsfile', 'sensors');
```
`read_tsfile` 的第一个参数是本地文件路径,第二个参数是 TsFile 中保存的表名。一次调用读取一个文件中的一个表模型表。
## 读取与查询
### 选择列
像查询普通 DuckDB 表一样选择需要的列。投影会尽量下推到 TsFile 扫描,减少读取的数据量:
```sql
SELECT time, device_id, temperature
FROM read_tsfile('/data/measurements.tsfile', 'sensors')
LIMIT 10;
```
### 时间过滤
全局 TsFile 时间轴在 DuckDB 中暴露为 `BIGINT`。时间单位由文件或协议约定,常见场景是毫秒。以下形式会下推到扫描器:`=`、`<`、`<=`、`>`、`>=` 和 `BETWEEN`。
```sql
SELECT time, device_id, temperature
FROM read_tsfile('/data/measurements.tsfile', 'sensors')
WHERE time BETWEEN 1700000000000 AND 1700003600000;
```
### TAG 过滤
字符串 TAG 列支持等值、范围、空值判断和 `AND`/`OR` 组合。支持的操作包括 `=`、`!=`、`<`、`<=`、`>`、`>=`、包含边界的 `BETWEEN`、`IS NULL` 和 `IS NOT NULL`。
```sql
SELECT time, device_id, temperature
FROM read_tsfile('/data/measurements.tsfile', 'sensors')
WHERE (device_id = 'device-01' OR device_id = 'device-02')
AND time BETWEEN 1700000000000 AND 1700003600000;
```
FIELD 过滤以及不支持下推的 TAG 表达式仍会由 DuckDB 在扫描后计算。任意形式的 `NOT (...)` 不会下推。
### 检查过滤是否下推
使用 `EXPLAIN` 查看执行计划。如果下推成功,可以在 `READ_TSFILE` 节点中看到 `Time Range` 和 `TAG Filter`:
```sql
EXPLAIN
SELECT time, device_id, temperature
FROM read_tsfile('/data/measurements.tsfile', 'sensors')
WHERE device_id = 'device-01'
AND time >= 1700000000000;
```
## 写入 TsFile
### 使用 COPY 输出
写入使用 DuckDB 标准 `COPY` 接口。建议先在子查询中完成清洗、类型转换和排序,再输出 TsFile:
```sql
COPY (
SELECT time, device_id, temperature, humidity
FROM measurements
ORDER BY device_id, time
)
TO '/data/measurements.tsfile'
(
FORMAT tsfile,
TABLE_NAME sensors,
TIME_COLUMN time,
TAG_COLUMNS (device_id)
);
```
`TIME_COLUMN` 标识时间轴;列在 `TAG_COLUMNS` 中的列会写成 TAG;其余列会写成 FIELD。`TABLE_NAME` 和 `TIME_COLUMN` 推荐使用标识符写法,需要特殊字符时使用双引号。
### 写入选项
|选项|<span style="white-space: nowrap;">是否必需</span>|说明|
|---|---|---|
|`FORMAT tsfile`|是|选择 TsFile COPY 写入器。|
|`TABLE_NAME`|否|输出文件中的本地表名,默认值为 `default_table`。|
|`TIME_COLUMN`|否|时间轴输入列,默认值为 `time`,类型必须是 `BIGINT`。|
|`TAG_COLUMNS`|否|TAG 列名列表;省略时所有非时间列都写成 FIELD。|
|`OVERWRITE true`|否|目标路径已存在且需要替换时使用。保留默认临时文件处理。|
### TAG\_COLUMNS 约束
`TAG_COLUMNS` 接受列名列表,而不是值列表:
```sql
TAG_COLUMNS (device_id, region)
```
- 列必须存在于输入查询中。
- 列类型必须是 `VARCHAR`。
- TAG 列不能同时是 `TIME_COLUMN`。
- 列名匹配不区分大小写。
如果不需要 TAG,可以只写时间列和 FIELD:
```sql
COPY (
SELECT time, temperature, humidity
FROM measurements
ORDER BY time
)
TO '/data/field-only.tsfile'
(
FORMAT tsfile,
TABLE_NAME sensors,
TIME_COLUMN time
);
```
### 类型映射与 NULL
|DuckDB 类型|TsFile 类型|说明|
|---|---|---|
|BOOLEAN|BOOLEAN|FIELD NULL 会保留。|
|INTEGER|INT32|FIELD NULL 会保留。|
|BIGINT|INT64|FIELD NULL 会保留;也用于时间轴。|
|FLOAT|FLOAT|FIELD NULL 会保留。|
|DOUBLE|DOUBLE|FIELD NULL 会保留。|
|VARCHAR|STRING|FIELD NULL 会保留。|
|BLOB|BLOB|FIELD NULL 会保留。|
|TIMESTAMP\_NS|TIMESTAMP|读取时对应 DuckDB `TIMESTAMP_NS`。|
## 端到端示例
下面的流程从已有 TsFile 读取设备 `a` 的一段时间数据,转换列名后写出新的 TsFile,再回读验证:
```sql
LOAD tsfile;
COPY (
SELECT time,
s0 AS device_id,
s2 AS value,
CAST(s8 AS VARCHAR) AS day
FROM read_tsfile('test/data/simple_table_t1.tsfile', 'test')
WHERE s0 = 'a'
AND time BETWEEN 1760106022000 AND 1760106024000
ORDER BY device_id, time
)
TO '/tmp/tsfile_subset.tsfile'
(
FORMAT tsfile,
TABLE_NAME subset,
TIME_COLUMN time,
TAG_COLUMNS (device_id)
);
SELECT time, device_id, value, day
FROM read_tsfile('/tmp/tsfile_subset.tsfile', 'subset')
ORDER BY device_id, time;
```
查询结果会返回 3 行,时间分别为 `1760106022000`、`1760106023000` 和 `1760106024000`。
## 当前限制与排错
### 写入前检查
1. 确认 `TIME_COLUMN` 存在且类型为 `BIGINT`。
2. 确认 TIME 列没有 NULL。
3. 确认输入按所有 TAG 列、再按时间列排序。
4. 目标文件已存在时,保留临时文件处理并设置 `OVERWRITE true`。
### 已知限制
- 当前版本支持一个本地 TsFile 文件中的一个表模型表。
- FIELD 过滤不会下推;任意 `NOT (...)` TAG 表达式不会下推。
- DATE FIELD 写入暂时关闭,以避免依赖本地时区的转换问题;已有 TsFile 中的 DATE 仍可读取。需要写出时可先转换为 `VARCHAR`。
- 使用 `USE_TMP_FILE false` 直接写入时,目标路径必须是新路径。
## 能力速查
|任务|入口|
|---|---|
|读取 TsFile|`read_tsfile('/path/file.tsfile', 'table')`|
|查看列与数据|`SELECT ... FROM read_tsfile(...)`|
|下推时间 / TAG 条件|在 `WHERE` 中使用支持的比较与逻辑组合|
|检查下推结果|`EXPLAIN SELECT ...`|
|写入 TsFile|`COPY (...) TO 'file.tsfile' (FORMAT tsfile, ...)`|
|替换已有文件|默认临时文件处理 \+ `OVERWRITE true`|