Skip to content

ZH Queries

Alan Zhang edited this page Oct 7, 2026 · 7 revisions

Home · GitHub

English | 简体中文

当前版本为 2.1.2;本地及 GitHub CI 完整验证通过,仅通过 GitHub 分发;Maven Central 仍为 2.1.0。 使用这些修改须先从源码安装 2.1.2,见 2.1.2 发布说明。

2.1.2 的聚合输出名

结构化聚合的输出名在按所选后端规则规范化后必须唯一。检查集合包含分组 tag、每个聚合 alias,以及仅在 groupByTime 生成窗口列时加入的 window_start。重复 alias、重复分组 tag、tag 与 alias 冲突、生成窗口列冲突,均在 HTTP 请求或借用 session 前返回 ARGUMENT_ERROR。直接 TSDBAdapter.query/count、链式 list 和 offset 分页总数使用相同规则。

InfluxDB 1.x、InfluxDB 3 与 OpenGemini 保留带引号标识符的大小写,total 与 TOTAL 可以不同;IoTDB 使用小写物理列身份,因此二者冲突。没有生成窗口时,window_start 可以作为普通聚合 alias。已有不支持的游标、排序及时间区操作保留错误分类与优先级;本校验不增加这些能力,也不改写原生 SQL。对同一字段多次聚合时,应分别使用 temperature_avg、temperature_max 等唯一 alias。

本页涉及的输入校验、配置快照和初始化失败策略修复纳入 2.1.0;详见 2.1.0 变更记录。

InfluxDB 1.x 与 OpenGemini 的聚合响应还包含隐式物理 time 列,因此精确命名为 time 的聚合 alias 或分组 tag 在 I/O 前被拒绝;大小写不同的 Time 仍可使用。此协议专属保留名不扩大到 IoTDB 或 InfluxDB 3。

简单链式查询

TGTemplate.query(Class<T>) 返回 com.alandevise.tsgate.core.TGQueryBuilder<T>。示例使用已注入的 TGTemplate 变量 tgTemplate;每次查询创建新的 builder,该对象可变,不应跨线程共享。

import com.alandevise.tsgate.core.TGQueryBuilder;

TGQueryBuilder<AccrueRecord> query = tgTemplate.query(AccrueRecord.class);
List<AccrueRecord> rows = tgTemplate.query(AccrueRecord.class)
        .database("tsdb")
        .whereTag("device_code", "device_001")
        .timeRange(startTime, endTime)
        .orderByTimeDesc()
        .limit(20)
        .list();

直接调用 adapter 的查询校验

四个后端的 TSDBAdapter.query(database, query) 都执行相同的基础校验,包括调用方自行构造的 TSDBQuery。query 为 null、显式 limit <= 0、offset < 0 或 startTime > endTime 时,在数据库 I/O 前返回 ARGUMENT_ERROR,不会静默丢弃非法分页参数。limit 为 null 表示不在 SQL 中分页,后端结果限额仍然生效;offset 为零、起止时间相等均合法。原生 executeQuery(sql) 保留独立的 SQL 与结果限额契约。

count() 仍先忽略分页和游标设置再计数,但拒绝逆序时间范围。公共 SPI 默认 count(null) 返回 ARGUMENT_ERROR,包括使用该默认实现的第三方 adapter,不再产生未经统一分类的空指针错误。链式页大小、单条分页探测额度及后端专有的不支持操作语义保持原有契约。

OpenGemini 适配仅将服务端精确的查询错误 measurement not found 在明确指定 measurement 的结构化统一 query(...) 中转为空结果,在 count(...) 中转为零;database、retention policy、权限及其他查询错误保留。原生 executeQuery(...) 的单条语句可能涉及多个 measurement 或子查询,因此保留查询错误;直接调用借用的兼容 org.influxdb.InfluxDB 时,也保留服务端错误 DTO。写入确认后,新 measurement 或 tag series 可能暂未对查询可见;业务在读侧轮询时应设置明确截止时间,并匹配具体期望数据点。每次 adapter 查询仍只发送一次请求。详见 OpenGemini 接入指南。

字段子集查询

public class ValueStatusResult {
    private Double value;
    private Long status;
}

List<ValueStatusResult> rows = tgTemplate.query(AccrueRecord.class)
        .database("tsdb")
        .select("value", "status")
        .whereTag("source", "annotation-pojo")
        .timeRange(startTime, endTime)
        .orderByTimeAsc()
        .limit(100)
        .list(ValueStatusResult.class);

FIELD 与多字段排序

以下 FIELD 排序功能适用于 IoTDB 和 InfluxDB 3;InfluxDB 1.x 只支持按时间排序。FIELD 可以作为第一排序列,也可以追加在时间列之后组成多字段排序。排序声明按调用顺序生成 SQL ORDER BY ;后一个排序列只在前面的排序列值相同时参与比较。

List<AccrueRecord> rows = tgTemplate.query(AccrueRecord.class)
        .database("tsdb")
        .timeRange(startTime, endTime)
        .orderByTimeAsc()
        .thenByFieldDesc("value")
        .thenByFieldAsc("status")
        .limit(100)
        .list();

上述查询会生成 ORDER BY time ASC, value DESC, status ASC。如果 FIELD 需要作为第一排序列,使用 orderByFieldAsc(field) 或 orderByFieldDesc(field);orderBy... 会替换已有排序,thenByField... 会追加或更新次级排序。

FIELD 排序支持普通 list()、page(pageNum, pageSize) 的 limit/offset 分页和 strictCursorPage(); 单字段时间游标 page() 以及聚合查询不支持 FIELD 排序,并会明确抛出不支持操作异常。 严格游标会按照每个 SortSpec 的方向生成对应的字典序条件,因此支持诸如 ORDER BY time DESC, device_code ASC 的混合排序。严格分页会自动在自定义排序末尾补齐缺失的 time + 全部 @TGTag 列, 补充列沿用第一项排序的方向;已经指定的列保留其方向。POJO 必须声明该表完整的 tag 键,排序与游标列当前仍要求非空。

聚合查询

public class AccrueAggregateResult {
    private Long windowStart;
    private String deviceCode;
    private Double avgValue;
    private Double maxValue;
    private Long sampleCount;
}

List<AccrueAggregateResult> rows = tgTemplate.query(AccrueRecord.class)
        .database("tsdb")
        .whereTagIn("device_code", List.of("device_001", "device_002"))
        .where("value", OperatorEnum.GE, 0)
        .timeRange(startTime, endTime)
        .groupByTime("5m")
        .groupByTag("device_code")
        .aggregate("value", AggregationFunctionEnum.AVG, "avg_value")
        .aggregate("value", AggregationFunctionEnum.MAX, "max_value")
        .aggregate("value", AggregationFunctionEnum.COUNT, "sample_count")
        .timeZone("+08:00")
        .orderByTimeAsc()
        .limit(100)
        .list(AccrueAggregateResult.class);

groupByTime 只接受正整数及 ms/s/m/h/d 单位(例如 5m),纯数字按毫秒解释;小数、负数、零、复合表达式和超出 long 毫秒范围的窗口会在生成 SQL 前拒绝。

链式窗口聚合返回的 window_start 统一为毫秒时间戳,可使用 Long、Instant 或 Date 承接。 InfluxDB 返回不带时区后缀的窗口时间时,适配器按 UTC 解析,不受应用 JVM 默认时区影响。

IoTDB / InfluxDB 3 窗口时区遵循以下规则(InfluxDB 1.x 的限制见后端能力表):

  • 默认 UTC。UTC、+08:00 等固定偏移使用固定时长窗口,可不设置时间范围。
  • America/New_York、Asia/Shanghai 等地区时区需要显式且有界的 timeRange(start, end)。
  • 地区时区的 1d 按查询日期的本地午夜切分;跨夏令时的一天可以是 23 或 25 小时。Nd 从本地 1970-01-01 日期按 N 天对齐,边界使用各自日期的真实偏移。
  • 为兼容两种后端,适配层按日历计算边界并生成有界 CASE 分组;不修改借出的 IoTDB Session 时区,也不依赖某个新版数据库特有函数。最多生成 10000 个窗口,超出时报 ARGUMENT_ERROR。
  • 地区时区的 ms/s/m/h 窗口使用查询开始时的偏移;若时间范围跨越偏移变化则明确报 UNSUPPORTED_OPERATION。需要这类跨越查询时,可使用 UTC / 固定偏移,或改用自然日窗口。
  • 查询范围仍按 start <= time <= end 过滤,窗口边界采用左闭右开。若不希望包含下一日午夜,应将结束值设为下一日午夜前 1 毫秒。

例如 .timeRange(start, end).groupByTime("1d").timeZone("America/New_York") 在 2026-03-08 和 2026-11-01 分别使用 23 小时和 25 小时自然日,不再套用 1970 年的固定偏移。

链式查询注意事项

  • 链式方法主要是收集查询条件,整体没有严格顺序限制;list()、page()、page(pageNum, pageSize) 会真正执行查询,应放在最后调用。
  • database、select、timeRange、limit、orderByTimeAsc/Desc 等配置类方法后调用会覆盖前调用。
  • where、whereTag、whereTagIn、groupByTag、aggregate 等条件类方法会累加;当前普通条件之间是 AND 关系。
  • 时间游标分页使用 page(),页大小来自 limit();传统 offset 分页使用 page(pageNum, pageSize),不建议再混用 limit()、 offset()。
  • 每次查询建议重新调用 tgTemplate.query(...) 创建 builder,不跨线程或跨请求复用。

稳定的默认聚合别名

省略聚合别名时,使用字段名、下划线和经 Locale.ROOT 转小写的函数名,例如 MIN(value) 默认别名始终为 value_min,不随 JVM 默认语言环境变化。业务显式指定的别名保持不变。


← 写入、模型与错误处理 · 分页与原生查询 →

2.1.0 中的 OpenGemini

TsGate 2.1.0 包含独立的 OpenGemini adapter 与 starter。配置、默认引擎边界及准确版本测试见 OpenGemini 接入指南,发行说明见 2.1.0 发布说明。

Clone this wiki locally