如何在 MySQL 8 中通过 int 而不是通过名称从嵌套 JSON 获取

How to get from nested JSON by int rather then by name in MySQL 8

所以我目前正在使用 MySQL 的 JSON 字段来存储一些数据。

所以 'reports' table 看起来像这样:

id | stock_id | type             | doc                          |
1  | 5        | Income_Statement | https://pastebin.com/bj1hdK0S|

pastebin 是 json 字段的内容

我想要做的是从 JSON 中每年 (2018-12-31) 下的第一个对象获取一个数字 (ebit),然后使用它进行 WHERE 查询,以便它例如,仅 returns 其中 ebit > 50000000。问题是每年以下的日期不标准(即一个可能是 2018-12-31,另一个可能是 2018-12-15)。所以基本上我想要一种使用整数索引而不是对象的实际名称来获取数据的方法,比如 yearly.[0].ebit。

我如何在 MySQL 中执行此操作?或者,如果在 MySQL 中不可能,在 PostgeSQL 或 Mongo 中是否可能?如果是这样,你能给我举个例子吗?大多数数据都适合 MySQL 只有这个 table 有一个 JSON 列,这就是为什么我从 MySQL.

开始

所以 Whosebug 不会让我的 link 在没有一些代码的情况下进入 pastebin,所以这里有一些随机代码:

if(dog == "poodle") {
    print "test"
}

我不知道 MySQL 或 MongoDB,但这是 PostgreSQL JSONB 类型的简单版本:

SELECT (doc->'yearly'-> max(years) -> 'ebit')::numeric AS ebit
FROM reports, jsonb_object_keys(doc->'yearly') AS years
GROUP BY reports.doc;

...简单的测试数据:

WITH reports(doc) AS (
    SELECT '{"yearly":{"2018-12-31":{"ebit":123},"2017-12-31":{"ebit":1.23}}}'::jsonb
)
SELECT (doc->'yearly'-> max(years) -> 'ebit')::numeric AS ebit
FROM reports, jsonb_object_keys(doc->'yearly') AS years
GROUP BY reports.doc;

...给出:

 ebit 
------
  123
(1 row)

所以我基本上 select 编辑了 "yearly" 下的 latest 条目,但不知道实际值,但假设关键日期格式将允许排序顺序(在这种情况下它似乎符合 ISO-8601)。

使用数据类型 JSON 而不是 JSONB 将保留对象键顺序,但在未来的 PostgreSQL 中效率不高,在这里也无济于事。

如果你想 select 只有那些 reports 最新 ebit 大于特定值的条目,只需将其打包到子 select 或CTE。我通常更喜欢 CTE,因为它们更易读,所以我们开始吧:

WITH
    reports (id, doc) AS (
        VALUES
        (1, '{"yearly":{"2018-12-31":{"ebit":123},"2017-12-31":{"ebit":1.23}}}'::jsonb),
        (2, '{"yearly":{"2018-12-23":{"ebit":50},"2017-12-22":{"ebit":"1200.00"}}}'::jsonb)
    ),
    r_ebit (id, ebit) AS (
        SELECT reports.id, (reports.doc->'yearly'-> max(years) -> 'ebit')::numeric AS ebit
        FROM reports, jsonb_object_keys(doc->'yearly') AS years
        GROUP BY reports.id, reports.doc
    )
SELECT id, ebit
FROM r_ebit
WHERE ebit > 100;

但是,如您所见,无法使用此策略过滤原始行。预处理步骤在这里很有意义,因此 JSON 格式实际上是过滤友好的。

附录

要增加 select 计算第 n 个完整财政年度的值的可能性,我们需要求助于 window 函数,我们还需要将结果集减少到每个实际组仅 ​​return 一行(在演示案例中:reports.id):

WITH reports(id, doc) AS (VALUES
    (1, '{"yearly":{"2018-12-31":{"ebit":123},"2017-12-31":{"ebit":1.23},"2016-12-31":{"ebit":"23.42"}}}'::jsonb),
    (2, '{"yearly":{"2018-12-23":{"ebit":50},"2017-12-22":{"ebit":"1200.00"}}}'::jsonb)
)
SELECT DISTINCT ON (1) reports.id, (reports.doc->'yearly'-> (lead(years, 0) over (partition by reports.doc order by years desc nulls last)) ->>'ebit')::numeric AS ebit
FROM reports, jsonb_object_keys(doc->'yearly') AS years
GROUP BY 1, reports.doc, years.years ORDER BY 1;

...将与之前使用 max 聚合函数的行为完全相同。增加 lead(years, <offset>) 函数中的偏移参数都会 select 第 n 年倒退(因为 window 分区的降序)。

DISTINCT ON (1) 子句是将结果减少到每个不同列值(第一列 = reports.id)的单行的魔法。这就是 NULLS LAST 在 window OVER 子句中非常重要的原因。

这是不同偏移量的结果(我为第一个 id 添加了第三个历史条目,但没有为第二个添加历史条目,以显示它如何处理缺失的条目):

N = 0:

 id | ebit 
----+------
  1 |  123
  2 |   50

N=1

 id |  ebit   
----+---------
  1 |    1.23
  2 | 1200.00

N=2

 id | ebit  
----+-------
  1 | 23.42
  2 |

...这意味着缺少条目只会导致 NULL 值。