PAYATHON 2026

SQL Server 从 JSON 数组获取最早和最晚付款日期

支付小周

结论

bank_orders 和 itens 都是 JSON 数组,需要先用 OPENJSON 展开,再进行聚合。把 payment_date 转换为 datetime2(3) 后,可以用 MIN 和 MAX 分别取得最早和最晚的付款日期。

SELECT
    bp.id,
    d.first_payment_date,
    d.last_payment_date
FROM bank_payments AS bp
OUTER APPLY
(
    SELECT
        MIN(TRY_CONVERT(datetime2(3), JSON_VALUE(i.value, '$.payment_date')))
            AS first_payment_date,
        MAX(TRY_CONVERT(datetime2(3), JSON_VALUE(i.value, '$.payment_date')))
            AS last_payment_date
    FROM OPENJSON(bp.json_data, '$.bank_orders') AS bo
    CROSS APPLY OPENJSON(bo.value, '$.itens') AS i
) AS d;

请将 bp.id 替换为 bank_payments 表中实际使用的主键或业务标识列。

为什么原查询返回 NULL

原查询使用了以下路径:

$.bank_orders.itens.payment_date

但 bank_orders 和 itens 都是数组。JSON_VALUE 用于读取指定路径上的单个标量值,不会自动遍历数组,所以无法通过这条路径直接取得 payment_date。

如果只想读取第一个 bank_orders 中的第一条付款记录,可以明确指定数组下标:

SELECT
    TRY_CONVERT(
        datetime2(3),
        JSON_VALUE(json_data, '$.bank_orders[0].itens[0].payment_date')
    ) AS first_array_payment_date
FROM bank_payments;

这个查询取得的是数组中的第一项,不一定是时间最早的一项。

获取最早和最晚付款日期

如果一条 json_data 可能包含多个 bank_orders,而每个订单又可能包含多条 itens,可以依次展开这两层数组:

SELECT
    bp.id,
    MIN(p.payment_date) AS first_payment_date,
    MAX(p.payment_date) AS last_payment_date
FROM bank_payments AS bp
CROSS APPLY OPENJSON(bp.json_data, '$.bank_orders') AS bo
CROSS APPLY OPENJSON(bo.value, '$.itens')
WITH
(
    payment_date datetime2(3) '$.payment_date'
) AS p
GROUP BY bp.id;

对表中的每条记录,这个查询会返回:

  • first_payment_date:时间最早的付款日期;
  • last_payment_date:时间最晚的付款日期。

CROSS APPLY 会过滤掉没有 bank_orders 或没有 itens 的表记录。如果需要保留这些记录,并让日期显示为 NULL,可以使用前面的 OUTER APPLY 聚合写法。

“第一个日期”的两种含义

“第一个日期”可能有两种含义:

  1. 时间最早的日期,应使用 MIN(payment_date)。
  2. JSON 数组中的第一项,应使用 [0],例如:
JSON_VALUE(json_data, '$.bank_orders[0].itens[0].payment_date')

只有在生成数据时明确规定了 JSON 数组顺序的业务含义,才能把第一项当作第一笔付款。如果要查询最早的付款时间,应使用 MIN,不要依赖数组的排列顺序。

注意事项

比较日期前,应先把字符串转换为真正的日期类型:

TRY_CONVERT(datetime2(3), payment_date)

不要直接对日期字符串使用 MIN 或 MAX。示例中的日期格式按字典顺序比较通常也能得到正确结果,但数据格式一旦不统一,就可能出现错误。

遇到无效日期时,TRY_CONVERT 会返回 NULL,聚合函数则会忽略这些值。如果需要检查脏数据,可以单独查询转换失败的记录:

SELECT
    bp.id,
    JSON_VALUE(i.value, '$.payment_date') AS invalid_payment_date
FROM bank_payments AS bp
CROSS APPLY OPENJSON(bp.json_data, '$.bank_orders') AS bo
CROSS APPLY OPENJSON(bo.value, '$.itens') AS i
WHERE JSON_VALUE(i.value, '$.payment_date') IS NOT NULL
  AND TRY_CONVERT(
        datetime2(3),
        JSON_VALUE(i.value, '$.payment_date')
      ) IS NULL;

这些查询都要求 json_data 中保存的是完整且有效的 JSON。问题里的片段省略了数组和对象的结束符,实际存储的值必须包含完整的 ] 和 }。

备注:内容仅供参考。