You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

QuestDB中JSON数组格式数据的Unpivot/展开转换实现方案咨询

QuestDB中JSON数组格式数据的Unpivot/展开转换实现方案咨询

确实,目前QuestDB还没有正式推出UNPIVOT功能(官方已经在 roadmap 里了,期待后续更新!),不过针对你这种把预测数据存在JSON数组里、需要转成长格式的需求,有两个比较实用的临时替代方案,我之前处理类似场景的时候用过,分享给你:


方案一:利用unnest展开原生数组(推荐,适合可变长度数组)

QuestDB支持将JSON字符串转成原生数组,再通过unnest函数将数组展开为多行,而且可以同时展开多个长度相同的数组,自动配对对应位置的元素,完美匹配你需要的“每个数组位置对应一行”的需求。

具体SQL示例

假设你的表名为forecast_data,字段分别是timestamp(原始预测生成时间)、forecast_timestamps(存JSON数组的字符串,比如"[2,3]")、forecasts(存预测值JSON数组的字符串,比如"[3,4]"):

WITH expanded_forecasts AS (
  -- 先将JSON数组转成QuestDB原生数组,再展开
  SELECT
    unnest(JSON_TO_ARRAY(fd.forecast_timestamps)) AS timestamp,
    unnest(JSON_TO_ARRAY(fd.forecasts)) AS forecast,
    -- 计算每个预测在数组中的位置(从1开始计数)
    row_number() OVER (PARTITION BY fd.timestamp ORDER BY unnest(JSON_TO_ARRAY(fd.forecast_timestamps))) AS position
  FROM forecast_data fd
),
all_timestamps AS (
  -- 收集所有需要出现在结果中的时间戳:原始生成时间+所有预测时间
  SELECT timestamp FROM forecast_data
  UNION
  SELECT unnest(JSON_TO_ARRAY(forecast_timestamps)) FROM forecast_data
)
-- 左连接补全无预测的时间戳(比如你的示例中timestamp=1的NULL行)
SELECT
  at.timestamp,
  ef.forecast,
  ef.position
FROM all_timestamps at
LEFT JOIN expanded_forecasts ef ON at.timestamp = ef.timestamp
ORDER BY at.timestamp;

注意事项

  1. 这个方法要求每一行的forecast_timestampsforecasts数组长度必须一致(你的场景里刚好满足),否则unnest会报错;
  2. 如果JSON数组的格式有问题(比如语法错误),JSON_TO_ARRAY会返回错误,建议提前用JSON_VALID校验数据;
  3. 性能表现很好,因为只需要扫描一次原始表,展开操作是内存级别的,适合大数据量场景。

方案二:用UNION ALL手动展开固定/有限长度数组

如果你的数组长度变化范围不大(比如最多5个元素),可以通过手动写UNION ALL分支来提取每个索引位置的元素,适合对SQL兼容性要求更高或者不想用数组函数的场景。

具体SQL示例

WITH raw_data AS (
  SELECT
    timestamp AS source_ts,
    forecast_timestamps,
    forecasts,
    JSON_LENGTH(forecast_timestamps) AS arr_len
  FROM forecast_data
),
-- 手动提取每个索引位置的元素,每个分支对应一个数组位置
unpivoted AS (
  -- 提取第1个元素(数组索引从0开始)
  SELECT
    JSON_EXTRACT_SCALAR(forecast_timestamps, '$[0]') AS timestamp,
    JSON_EXTRACT_SCALAR(forecasts, '$[0]') AS forecast,
    1 AS position
  FROM raw_data
  WHERE arr_len >= 1

  UNION ALL

  -- 提取第2个元素
  SELECT
    JSON_EXTRACT_SCALAR(forecast_timestamps, '$[1]') AS timestamp,
    JSON_EXTRACT_SCALAR(forecasts, '$[1]') AS forecast,
    2 AS position
  FROM raw_data
  WHERE arr_len >= 2

  -- 可以继续添加更多分支,覆盖你可能遇到的最大数组长度
  UNION ALL

  -- 补全无预测的原始时间戳行
  SELECT
    source_ts AS timestamp,
    NULL AS forecast,
    NULL AS position
  FROM raw_data
  WHERE NOT EXISTS (
    SELECT 1 FROM raw_data rd
    WHERE JSON_CONTAINS(rd.forecast_timestamps, CAST(raw_data.source_ts AS JSON))
  )
)
SELECT * FROM unpivoted ORDER BY timestamp;

优缺点

  • 优点:不需要依赖数组展开函数,逻辑更直观,适合数组长度固定的场景;
  • 缺点:需要预先知道最大数组长度,写多个UNION ALL分支,数据量很大时性能不如方案一(因为会多次扫描原始表)。

额外建议

  1. 如果后续官方推出UNPIVOT,直接替换成官方语法即可,这两个方案都是临时过渡;
  2. 如果你后续有机会调整写入流程,建议直接将预测数据以长格式写入QuestDB(即每个预测时间对应一行),这样后续分析和查询会更高效,不需要每次都做展开转换;
  3. 关于NULL处理,你可以根据需求用COALESCE将NULL替换成默认值,或者在WHERE子句中过滤掉不需要的NULL行。

希望这些方案能帮到你!如果有其他细节需要调整(比如数据类型、特殊NULL场景),可以再补充说明~

火山引擎 最新活动