web-dev-qa-db-ja.com

MySQLのJSON配列を行に変換します

UPDATE:これは、JSON_TABLE関数を介してMySQL 8で可能になりました: https://dev.mysql.com/doc/refman/ 8.0/en/json-table-functions.html

私はMySQL 5.7の新しいJSON関数が大好きですが、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')テーブルを返す関数があれば、それをハックすることもできます(エスケープされることはありません)が、それも利用できません。

18
Chris Hynes

MySQL 8+で JSON_TABLE を使用してこれを行う方法は次のとおりです。

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

また、区切り文字列を取得してJSON文字列に変換することで、MySQLにはない一般的な文字列分割関数としても使用できます(PGのregexp_split_to_tableまたはMSSQLのSTRING_SPLITと同様)。

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

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

JSONに非正規化するのは得策ではありませんが、JSONデータを処理する必要がある場合があり、クエリの行にJSON配列を抽出する方法があります。

トリックは、インデックスの一時テーブルまたはインラインテーブルで結合を実行することです。これにより、JSON配列のnull以外の各値に対して行が提供されます。つまり、2つのエントリを持つJSON配列「fish」に結合する値0、1、および2のテーブルがある場合、fish [0]は0に一致し、1行になり、fish [1]は1に一致します2番目の行になりますが、fish [2]はnullであるため、2に一致せず、結合で行を生成しません。インデックステーブルには、JSONデータの配列の最大長と同じ数の数値が必要です。これはちょっとしたハックであり、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関数を追加するようです。 ( http://mysqlserverteam.com/mysql-8-0-labs-json-aggregation-functions/

25
JimTheFrog

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);
    

このように。 「クリス・ハインズ」のようなものです。ただし、配列のサイズを知る必要はありません。

良い:明確で、短く、簡単なコード、配列サイズを知る必要がない、ループがない、他の関数を呼び出す必要がない、高速。

悪い:十分な行があるテーブルがもう1つ必要です。

4
wa56

私の場合、JSON関数は使用できなかったため、ハックを使用しました。 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

要件に応じて変更できます。

1
Vishal Gupta

1つの列に大きなjson配列リストがあるレポートで作業していました。データモデルを変更して、すべてを1つの列に格納する代わりに、リレーションシップ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 ;
0
Exec21