查询嵌套JSON
Telemetry 将嵌套的 JSON 对象转换为可查询的点状字段路径。这样可以在摄取时将相关上下文保持在一起,同时仍然使各个值可供 SQL 使用。
本指南涵盖了从事件契约到过滤器、聚合、架构更改和故障排除的完整路径。在复制查询之前检查表架构:确切的标识符拼写和类型来自您发送的事件。
设计一个稳定的嵌套事件
当字段形成一个持久概念时,请使用嵌套对象。保持值的类型,省略敏感的有效负载,并避免将频繁更改的结构放入数组中。
{
"event_name": "tool_call_completed",
"event_id": "evt_7f31",
"account_id": "acct_8f31",
"release": "2026.07.3",
"workflow": {
"name": "answer_question",
"version": "v2"
},
"tool": {
"name": "inventory_lookup",
"outcome": "success",
"duration_ms": 184,
"usage": {
"input_units": 820,
"output_units": 244
}
}
}
行粒度是一次完成的工具调用。 tool.duration_ms 始终是数字,tool.outcome 来自受控集,并且标识符是假名的。请求参数、模型提示、生成的内容、凭据和原始错误消息故意不存在。
通过SDK发送对象:
await telemetry.log("tool_call_completed", event);
日志 API 添加查询中使用的托管事件时间,并递归删除空值、空对象和空数组。在使用空值作为业务状态之前,请先阅读 事件数据类型与可空性。
聚合前检查
从有界样本开始。根据表模式,嵌套路径可以显示为复合标识符或需要一个双引号点标识符:
SELECT
timestamp_utc,
event_id,
workflow.name,
tool.name,
tool.outcome,
tool.duration_ms
FROM tool_call_completed
WHERE timestamp_utc >= now() - INTERVAL '1 hour'
ORDER BY timestamp_utc DESC
LIMIT 50;
如果架构公开了文字点分列名称,请引用完整路径:
SELECT
"workflow.name",
"tool.name",
"tool.duration_ms"
FROM tool_call_completed
LIMIT 50;
不要通过猜测在带引号和不带引号的形式之间切换。检查表架构,运行一个小样本,并使用与存储字段匹配的表单。
过滤和聚合嵌套字段
嵌套字段适用于过滤器、组、计算和排序。此查询在完整的有界窗口上比较工具数量、故障和 p95 持续时间:
SELECT
tool.name AS tool_name,
COUNT(*) AS calls,
SUM(CASE WHEN tool.outcome = 'error' THEN 1 ELSE 0 END) AS errors,
100.0 * SUM(CASE WHEN tool.outcome = 'error' THEN 1 ELSE 0 END)
/ NULLIF(COUNT(*), 0) AS error_rate_pct,
approx_percentile_cont(tool.duration_ms, 0.95) AS p95_duration_ms
FROM tool_call_completed
WHERE timestamp_utc >= now() - INTERVAL '7 days'
AND workflow.name = 'answer_question'
GROUP BY tool.name
HAVING COUNT(*) >= 20
ORDER BY error_rate_pct DESC, calls DESC;
如果您的表使用带引号的点标识符,请在同一查询中引用每个完整路径:
SELECT
"tool.name" AS tool_name,
approx_percentile_cont("tool.duration_ms", 0.95) AS p95_duration_ms
FROM tool_call_completed
WHERE "workflow.name" = 'answer_question'
GROUP BY "tool.name";
故意处理缺失的路径
在引入 tool.usage.output_units 之前创建的旧行将没有该字段。具有 null、空对象或空数组值的新事件在规范化后也不会存储该路径的值。
在依赖新字段之前使用 IS NULL 测量覆盖范围:
SELECT
release,
COUNT(*) AS calls,
SUM(CASE WHEN tool.usage.output_units IS NULL THEN 1 ELSE 0 END)
AS missing_output_units,
100.0 * SUM(CASE WHEN tool.usage.output_units IS NULL THEN 1 ELSE 0 END)
/ NULLIF(COUNT(*), 0) AS missing_rate_pct
FROM tool_call_completed
WHERE timestamp_utc >= now() - INTERVAL '24 hours'
GROUP BY release
ORDER BY release;
请勿用零替换缺失的数字,除非零是正确的业务含义。 “未报告”、“不适用”和实际测量为零是不同的状态。
安全地演化嵌套路径
将每个点路径视为模式契约:
| 改变 | 效果 | 更安全的推出 |
|---|---|---|
添加tool.usage.cache_hit |
现有行没有值 | 添加键入的字段,测量覆盖范围,然后更新消费者 |
重命名tool.name |
现有查询仍然读取旧路径 | 迁移时双写新旧路径 |
将 tool.duration_ms 从数字更改为字符串 |
类型冲突可能会拒绝摄取 | 添加新的数字字段并迁移 |
将 tool.outcome 移动到另一个对象 |
创建新路径,而不是原地移动 | 对合约进行版本控制并暂时支持这两种路径 |
| 更改数组元素形状 | 产生不稳定的分析契约 | 每个持久结果发出一行或使用固定命名字段 |
在每个深度都保持相同的含义和类型。添加可选的嵌套字段通常是兼容的;改变路径的类型或重新使用它以获得新的含义则不是。
决定何时展平
嵌套对象对于稳定的命名空间非常有用,例如 tool、workflow 或 billing。当平面字段用于几乎每个查询或仪表板时,它会更好。
在以下情况下更喜欢平坦或单独发射的场:
- 该值定义了行粒度或主要事件结果;
- 操作员几乎在每次调查中都必须对其进行扫描;
- 多个生产者无法就一种嵌套结构达成一致;
- 数组实际上代表了多个独立的结果。
稍后从嵌套更改为平面是架构迁移。根据问题和所有权边界进行选择,而不是根据有效负载的美观进行选择。
解决嵌套字段查询问题
如果查询找不到嵌套路径:
- 使用
LIMIT 50查询最近的原始样本。 - 检查表架构中是否有准确的点名称和类型。
- 尝试模式的带引号的标识符形式,而不是添加来自另一种 SQL 方言的 JSON 提取语法。
- 确认生产者确实发送了一个非空、非空值。
- 按
release或生产者版本对缺失值进行分组。 - 检查新旧生产者之间的类型变化。
- 在恢复连接或聚合之前,将查询减少到一个字段和一个最近的时间窗口。
Telemetry 使用 DataFusion SQL,因此从其他系统复制的 PostgreSQL、BigQuery、Snowflake 或 MySQL JSON 函数可能不适用。使用 DataFusion SQL 参考 中练习的语法。
生产清单
- 赋予每个嵌套对象一个持久的意义和所有者。
- 保持每条路径的类型、单位和控制值稳定。
- 排除机密、用户内容、原始负载和无限制的错误文本。
- 测试成功、失败、缺场和旧版本装置。
- 在仪表板或告警依赖新领域之前先衡量新领域的采用情况。
- 记录行粒度、保留需求和迁移计划。