繁体   English   中英

将 MySQL 中的 JSON 数组转换为行

[英]Convert JSON array in MySQL to rows

更新:现在可以通过 JSON_TABLE 函数在 MySQL 8 中实现: https ://dev.mysql.com/doc/refman/8.0/en/json-table-functions.html

我很喜欢 MySQL 5.7 中的新 JSON 函数,但是遇到了试图将 JSON 中的值合并到普通表结构中的块。

抓取 JSON、操作和从中提取数组等很简单。 JSON_EXTRACT 一路。 但是反过来呢,从 JSON 数组到行呢? 也许我对现有的 MySQL JSON 功能很熟悉,但我一直无法弄清楚。

例如,假设我有一个 JSON 数组并且想要为数组中的每个元素插入一行及其值? 我发现的唯一方法是编写一堆 JSON_EXTRACT(... '$[0]') JSON_EXTRACT(... '$[1]') 等并将它们结合在一起。

或者,假设我有一个 JSON 数组并且想要 GROUP_CONCAT() 将它转换为一个逗号分隔的字符串?

换句话说,我知道我可以这样做:

SET @j = '[1, 2, 3]';
SELECT GROUP_CONCAT(JSON_EXTRACT(@j, CONCAT('$[', x.n, ']'))) AS val
  FROM   
  (    
    SELECT 0 AS n    
    UNION    
    SELECT 1 AS n    
    UNION    
    SELECT 2 AS n    
    UNION    
    SELECT 3 AS n    
    UNION    
    SELECT 4 AS n    
    UNION    
    SELECT 5 AS n    
  ) x
WHERE x.n < JSON_LENGTH(@j);

但这伤害了我的眼睛。 还有我的心。

我该怎么做:

SET @j = '[1, 2, 3]';
SELECT GROUP_CONCAT(JSON_EXTRACT(@j, '$[ * ]'))

...并将数组中的值与 JSON 数组本身连接在一起?

我想我在这里寻找的是某种 JSON_SPLIT 沿着以下几行:

SET @j = '[1, 2, 3]';

SELECT GROUP_CONCAT(val)
FROM
  JSON_SPLIT(JSON_EXTRACT(@j, '$[ * ]'), '$')

如果 MySQL 有一个正确的 STRING_SPLIT(val, 'separator') 表返回函数,我可以破解它(该死的逃避),但这也不可用。

确实,非规范化为 JSON 并不是一个好主意,但有时您需要处理 JSON 数据,并且有一种方法可以将 JSON 数组提取到查询中的行中。

诀窍是对临时或内联索引表执行连接,这为 JSON 数组中的每个非空值提供一行。 即,如果您有一个包含值 0、1 和 2 的表,您将其连接到具有两个条目的 JSON 数组“fish”,则 fish[0] 匹配 0,结果为一行,而 fish 1匹配 1,结果为第二行,但 fish[2] 为空,因此它与 2 不匹配,并且不会在连接中生成一行。 您需要索引表中的数字与 JSON 数据中任何数组的最大长度一样多。 这有点像 hack,它和 OP 的例子一样痛苦,但它非常方便。

示例(需要 MySQL 5.7.8 或更高版本):

CREATE TABLE t1 (rec_num INT, jdoc JSON);
INSERT INTO t1 VALUES 
  (1, '{"fish": ["red", "blue"]}'), 
  (2, '{"fish": ["one", "two", "three"]}');

SELECT
  rec_num,
  idx,
  JSON_EXTRACT(jdoc, CONCAT('$.fish[', idx, ']')) AS fishes
FROM t1
  -- Inline table of sequential values to index into JSON array
JOIN ( 
  SELECT  0 AS idx UNION
  SELECT  1 AS idx UNION
  SELECT  2 AS idx UNION
  -- ... continue as needed to max length of JSON array
  SELECT  3
  ) AS indexes
WHERE JSON_EXTRACT(jdoc, CONCAT('$.fish[', idx, ']')) IS NOT NULL
ORDER BY rec_num, idx;

结果是:

+---------+-----+---------+
| rec_num | idx | fishes  |
+---------+-----+---------+
|       1 |   0 | "red"   |
|       1 |   1 | "blue"  |
|       2 |   0 | "one"   |
|       2 |   1 | "two"   |
|       2 |   2 | "three" |
+---------+-----+---------+

看起来 MySQL 团队可能会在 MySQL 8 中添加一个JSON_TABLE函数来JSON_TABLE这一切。 http://mysqlserverteam.com/mysql-8-0-labs-json-aggregation-functions/ (MySQL的团队增加了JSON_TABLE功能。)

以下是在 MySQL 8+ 中使用JSON_TABLE执行此操作的方法

SELECT *
     FROM
       JSON_TABLE(
         '[5, 6, 7]',
         "$[*]"
         COLUMNS(
           Value INT PATH "$"
         )
       ) data;

您还可以将它用作 MySQL 缺少的通用字符串拆分函数(类似于 PG 的 regexp_split_to_table 或 MSSQL 的 STRING_SPLIT),方法是将分隔字符串转换为 JSON 字符串:

set @delimited = 'a,b,c';

SELECT *
     FROM
       JSON_TABLE(
         CONCAT('["', REPLACE(@delimited, ',', '", "'), '"]'),
         "$[*]"
         COLUMNS(
           Value varchar(50) PATH "$"
         )
       ) data;

在 2018 年。我为这个案子做了什么。

  1. 准备一个表格,行中只有连续编号。

     CREATE TABLE `t_list_row` ( `_row` int(10) unsigned NOT NULL, PRIMARY KEY (`_row`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1; INSERT t_list_row VALUES (0), (1), (2) .... (65535) big enough;
  2. 将来享受简单的 JSON 数组到行。

     SET @j = '[1, 2, 3]'; SELECT JSON_EXTRACT(@j, CONCAT('$[', B._row, ']')) FROM (SELECT @j AS B) AS A INNER JOIN t_list_row AS B ON B._row < JSON_LENGTH(@j);

对于这种方式。 有点像“克里斯·海因斯”的方式。 但您不需要知道数组大小。

优点:清晰、简短、简单的代码,不需要知道数组大小,没有循环,没有调用其他函数会很快。

不好:您还需要一张有足够行数的表。

对于 MySQL 8+,请参阅此答案

对于旧版本,我是这样做的:

  1. 使用从 0 到 99 的值创建一个新表pseudo_rows - 这些将用作键(如果您的数组有一百多个值,则将更多值添加到pseudo_rows )。

注意:如果您正在运行 MariaDB,则可以跳过此步骤而只需使用伪序列表(例如seq_0_to_99 )。

CREATE TABLE `pseudo_rows` (
  `row` int(10) unsigned NOT NULL,
  PRIMARY KEY (`row`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT pseudo_rows VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12), (13), (14), (15), (16), (17), (18), (19), (20), (21), (22), (23), (24), (25), (26), (27), (28), (29), (30), (31), (32), (33), (34), (35), (36), (37), (38), (39), (40), (41), (42), (43), (44), (45), (46), (47), (48), (49), (50), (51), (52), (53), (54), (55), (56), (57), (58), (59), (60), (61), (62), (63), (64), (65), (66), (67), (68), (69), (70), (71), (72), (73), (74), (75), (76), (77), (78), (79), (80), (81), (82), (83), (84), (85), (86), (87), (88), (89), (90), (91), (92), (93), (94), (95), (96), (97), (98), (99)
  1. 在这个例子中,我将使用一个表events来存储艺术家组:
CREATE TABLE `events` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `artists` json DEFAULT NOT NULL,
  PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

INSERT INTO `events` (`id`, `artists`) VALUES ('1', '[{\"id\": 123, \"name\": \"Pink Floyd\"}]');
INSERT INTO `events` (`id`, `artists`) VALUES ('2', '[{\"id\": 456, \"name\": \"Nirvana\"}, {\"id\": 789, \"name\": \"Eminem\"}]');

获取所有艺术家(每行一个)的查询如下:

SELECT 
    JSON_UNQUOTE(JSON_EXTRACT(events.artists, CONCAT('$[', pseudo_rows.row, '].name'))) AS performer
FROM events
JOIN pseudo_rows
HAVING performer IS NOT NULL

结果集是:

performer
---------
Pink Floyd
Nirvana
Eminem

在我的案例中, JSON函数不可用,所以我使用了 hack。 正如 Chris MYSQL 所提到的,没有STRING_SPLIT但它有substring_index

对于输入

{
    "requestId":"BARBH17319901529",
    "van":"0xxxxx91317508",
    "source":"AxxxS",
    "txnTime":"15-11-2017 14:08:22"
}

您可以使用:

trim(
    replace(
        substring_index(
            substring(input, 
                locate('requestid',input) 
                    + length('requestid') 
                    + 2), ',', 1), '"', '')
) as Requestid`

输出将是:

BARBH17319901529

您可以根据您的要求进行修改。

简单的例子:

select subtotal, sku
from t1,
     json_table(t1.refund_line_items,
                '$[*]' columns (
                    subtotal double path '$.subtotal',
                    sku char(50) path '$.line_item.sku'
                    )
         ) refunds

如果您不能使用 JSON_TABLE 函数,但可以使用递归 CTE,则可以执行以下操作:

SET @j = '[1, 2, 3]';
WITH RECURSIVE x AS (
    /* Anchor, start at -1 in case empty array */
    SELECT -1 AS n

    UNION

    /* Append indexes up to the length of the array */
    SELECT x.n + 1
    FROM x
    WHERE x.n < JSON_LENGTH(@j) - 1
)
/* Use the table of indexes to extract each item and do your GROUP_CONCAT */ 
SELECT GROUP_CONCAT(JSON_EXTRACT(@j, CONCAT('$[', x.n, ']')))
FROM x
/* This prevents selecting from empty array */
WHERE x.n >= 0

这将为每个数组项生成一个顺序索引表,您可以使用它来使用 JSON_EXTRACT 获取值。

我正在处理一份报告,其中一列中有一个很大的 json 数组列表。 我修改了数据模型来存储 1 到 * 的关系,而不是将所有内容都存储在一个列中。 为了执行此过程,我不得不在存储过程中使用一段时间,因为我不知道最大大小:

DROP PROCEDURE IF EXISTS `test`;

DELIMITER #

CREATE PROCEDURE `test`()
PROC_MAIN:BEGIN
DECLARE numNotes int;
DECLARE c int;
DECLARE pos varchar(10);

SET c = 0;
SET numNotes = (SELECT 
ROUND (   
        (
            LENGTH(debtor_master_notes)
            - LENGTH( REPLACE ( debtor_master_notes, "Id", "") ) 
        ) / LENGTH("Id")        
    ) AS countt FROM debtor_master
order by countt desc Limit 1);

DROP TEMPORARY TABLE IF EXISTS debtorTable;
CREATE TEMPORARY TABLE debtorTable(debtor_master_id int(11), json longtext, note int);
WHILE(c <numNotes) DO
SET pos = CONCAT('$[', c, ']');
INSERT INTO debtorTable(debtor_master_id, json, note)
SELECT debtor_master_id, JSON_EXTRACT(debtor_master_notes, pos), c+1
FROM debtor_master
WHERE debtor_master_notes IS NOT NULL AND debtor_master_notes like '%[%' AND JSON_EXTRACT(debtor_master_notes, pos) IS NOT NULL AND JSON_EXTRACT(debtor_master_notes, pos) IS NOT NULL;
SET c = c + 1;
END WHILE;
SELECT * FROM debtorTable;
END proc_main #

DELIMITER ;

在此处使用此参考https://dba.stackexchange.com/questions/190527/list-json-array-in-mysql-as-rows/243671#243671

在我的 MySQL 表Customers有一个 JSON 类型的列AddressIdentifiers ,数据示例如下所示:

[
  {
    "code": "123",
    "identifier": "0219d5780f6b",
    "type": "BILLING",
    "info": null
  },
  {
    "code": "240",
    "identifier": "c81aaf2c5a1f",
    "type": "DELIVERY",
    "info": null
  }
]

有这样的输出

Identifier   AddressType
------------------------
0219d5780f6b  BILLING
c81aaf2c5a1f  DELIVERY

此解决方案适用于 MySQL 5.7,您必须手动完成该工作。 在 MySQL 8.0+ 的情况下,您可以简单地使用JSON_TABLE

SELECT
    JSON_EXTRACT(C.AddressIdentifiers, CONCAT('$[', Numbers.N - 1, '].Identifier')) AS Identifier,
    JSON_EXTRACT(C.AddressIdentifiers, CONCAT('$[', Numbers.N - 1, '].AddressType')) AS AddressType,
FROM
(
    SELECT @row := @row + 1 AS N FROM 
    (SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) T2,
    (SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) T1, 
    (SELECT @row:=0) T0
) Numbers -- Natural numbers from 1 to 100
INNER JOIN Customers C ON Numbers.N < JSON_LENGTH(C.AddressIdentifiers)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM