web-dev-qa-db-fra.com

Convertir un tableau JSON dans MySQL en lignes

UPDATE: Ceci est maintenant possible dans MySQL 8 via la fonction JSON_TABLE: https://dev.mysql.com/doc/refman/8.0/en/json-table-functions.html

J'adore les nouvelles fonctions JSON dans MySQL 5.7, mais je rencontre un bloc qui tente de fusionner les valeurs de JSON dans une structure de table normale.

Saisir JSON, en manipuler et en extraire des tableaux, etc. est simple. JSON_EXTRACT jusqu'au bout. Mais qu'en est-il de l'inverse, passer d'un tableau JSON à des lignes? Peut-être que je suis dense sur la fonctionnalité JQL MySQL existante, mais je n'ai pas été capable de le comprendre.

Par exemple, disons que j'ai un tableau JSON et que je veux insérer une ligne pour chaque élément du tableau avec sa valeur? La seule façon que j'ai trouvée est d'écrire un groupe de JSON_EXTRACT (... '$ [0]') JSON_EXTRACT (... '$ [1]'), etc. et de les unir.

Ou, disons que j'ai un tableau JSON et que je veux GROUP_CONCAT () le transformer en une seule chaîne séparée par des virgules?

En d'autres termes, je sais que je peux faire ceci:

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

Mais ça me fait mal aux yeux. Et mon coeur.

Comment puis-je faire quelque chose comme:

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

... et qu'il concatène ensemble les valeurs du tableau et le tableau JSON lui-même?

Je suppose que ce que je recherche ici est une sorte de JSON_SPLIT du type:

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

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

Si MySQL avait une fonction de retour de table STRING_SPLIT (val, 'separator') appropriée, je pourrais le pirater (échapper à la damnation), mais ce n'est pas disponible non plus.

14
Chris Hynes

Il est vrai que dénormaliser en JSON n’est pas une bonne idée, mais vous devez parfois traiter des données JSON et il existe un moyen d’extraire un tableau JSON en rangées dans une requête.

L'astuce consiste à effectuer une jointure sur une table d'index temporaire ou en ligne, ce qui vous donne une ligne pour chaque valeur non nulle dans un tableau JSON. Par exemple, si vous avez une table avec les valeurs 0, 1 et 2 que vous joignez à un tableau JSON «poisson» avec deux entrées, puis poisson [0] correspond à 0, résultant en une ligne, et poisson [1] correspond à 1, Il en résulte une deuxième ligne, mais fish [2] est nul; il ne correspond donc pas à 2 et ne produit pas de ligne dans la jointure. Vous avez besoin d'autant de nombres dans la table d'index que la longueur maximale de tout tableau dans vos données JSON. C'est un peu un bidouillage, et c'est à peu près aussi douloureux que l'exemple du PO, mais c'est très pratique.

Exemple (requiert MySQL 5.7.8 ou une version ultérieure):

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;

Le résultat est:

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

Il semble que l’équipe MySQL puisse ajouter une fonction JSON_TABLE dans MySQL 8 pour faciliter tout cela. ( http://mysqlserverteam.com/mysql-8-0-labs-json-aggregation-functions/ )

17
JimTheFrog

En 2018. Ce que je fais pour ce cas.

  1. Préparez une table avec un nombre continuellement en ligne.

    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. Profitez d'un tableau JSON facile pour les lignes à l'avenir.

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

Pour cette façon. est un peu comme Chris Hynes. mais vous n'avez pas besoin de connaître la taille du tableau.

Bon: code clair, court, facile, pas besoin de connaître la taille du tableau, pas de boucle, pas d’invocation d’autre fonction qui sera rapide.

Mauvais: vous avez besoin d'une table de plus avec suffisamment de lignes.

1
wa56

Dans My Case, JSON La fonction n'étant pas disponible, j'ai utilisé un hack . Comme l'a mentionné Chris MYSQL, STRING_SPLIT n'est pas disponible, mais substring_index

Pour l'entrée

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

Vous pouvez utiliser:

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

La sortie sera:

BARBH17319901529

Vous pouvez modifier selon vos besoins.

1
Vishal Gupta

Je travaillais dans un rapport où il y avait une grande liste de tableau JSON dans une colonne. J'ai modifié le modèle de données pour stocker la relation 1 en * au lieu de tout stocker dans une seule colonne. Pour effectuer ce processus, j'ai dû utiliser un certain temps dans une procédure stockée car je ne connaissais pas la taille maximale:

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