How to get values from MySQL(5.6) column if that contains json document as string

前端 未结 10 690
萌比男神i
萌比男神i 2020-12-02 18:48

How to get values from MySQL(5.6) column if that contains JSON document as a string

For example, if we have a table - employee in that we have three columns id, nam

10条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2020-12-02 19:19

    Here are my 3 SQL stored functions that I use for JSON extracting. They handle nested objects, but only care about the key name. The keys must be strings, the values can be strings, numeric or boolean values. Arrays are not treated well, only the first value is picked. They return NULL if no value found.

    The first one, json_extract_1 picks only the first value if there are multiple keys with the same name. If you leave the LIMIT 1 clause, it throws 'Subquery returns more than 1 row' if more keys found (safe mode).

    The second, json_extract_m collects all values with the same key in a comma-separated list.

    The third one, json_extract_c is the slowest one, but it treats values with commas also correctly. Use it if it's absolutely necessary, e.g textual descriptions.

    For all three the limitation is 999 keys. You can speed up if you prepare a table for the numbers subselect.

    DELIMITER $$
    
    /*
     * Single-value JSON extract - picks the first value
     */
    DROP FUNCTION IF EXISTS `json_extract_1`$$
    CREATE FUNCTION `json_extract_1`(json_txt TEXT, search_key VARCHAR (255)) 
        RETURNS TEXT
    BEGIN
        RETURN (SELECT TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING_INDEX(txt,':',-1), '"', 2), '"', -1)) AS val
        FROM (
            SELECT TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING_INDEX( SUBSTRING_INDEX(json_txt , ',', n), ',',  -1 ), '}', 1), '{', -1)) AS txt
            FROM (SELECT t1.v + t2.v*10 + t3.v*100 AS n
                FROM (SELECT 0 AS v UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t1,
                (SELECT 0 AS v UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t2,
                (SELECT 0 AS v UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t3) numbers
            WHERE CHAR_LENGTH(json_txt ) - CHAR_LENGTH(REPLACE(json_txt , ',', '')) >= n - 1
            AND n>0 ) sp
        WHERE TRIM(SUBSTRING_INDEX(txt,':',1)) = CONCAT('"',search_key,'"')
        LIMIT 1   -- comment out for safe mode
        );
    END$$
    
    /*
     * Multi-value JSON extract - collects all values, group_concats them with comma
     */
    DROP FUNCTION IF EXISTS `json_extract_m`$$
    CREATE FUNCTION `json_extract_m`(json_txt TEXT, search_key VARCHAR (255)) 
        RETURNS TEXT
    BEGIN
        RETURN (SELECT GROUP_CONCAT(TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING_INDEX(txt,':',-1), '"', 2), '"', -1))) AS val
        FROM (
            SELECT TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING_INDEX( SUBSTRING_INDEX(json_txt , ',', n), ',',  -1 ), '}', 1), '{', -1)) AS txt
            FROM (SELECT t1.v + t2.v*10 + t3.v*100 AS n
                FROM (SELECT 0 AS v UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t1,
                (SELECT 0 AS v UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t2,
                (SELECT 0 AS v UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t3) numbers
            WHERE CHAR_LENGTH(json_txt ) - CHAR_LENGTH(REPLACE(json_txt , ',', '')) >= n - 1
            AND n>0 ) sp
        WHERE TRIM(SUBSTRING_INDEX(txt,':',1)) = CONCAT('"',search_key,'"'));
    END$$
    
    /*
     * Comma-safe JSON extract - treats values with commas correctly, but slow like hell
     */
    DROP FUNCTION IF EXISTS `json_extract_c`$$
    CREATE FUNCTION `json_extract_c`(json_txt TEXT, search_key VARCHAR (255)) 
        RETURNS TEXT
    BEGIN
        DROP TEMPORARY TABLE IF EXISTS json_parts;
        DROP TEMPORARY TABLE IF EXISTS json_parts2;
        DROP TEMPORARY TABLE IF EXISTS json_indexes;
    
        CREATE TEMPORARY TABLE json_parts AS
        SELECT n, IF(INSTR(txt,':')>0 AND (INSTR(txt,',')+INSTR(txt,'{')>0),1,0) AS this_val, IF(INSTR(txt,':')>0 AND (INSTR(txt,',')+INSTR(txt,'{')=0),1,0) AS next_val, IF(INSTR(txt,',')+INSTR(txt,'{')>0,1,0) AS next_key, txt
        FROM (SELECT n, IF(n%2,txt,REPLACE(txt,',','|')) AS txt 
        FROM (SELECT n, TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(json_txt , '"', n), '"',  -1 )) AS txt
            FROM (SELECT t1.v + t2.v*10 + t3.v*100 AS n
            FROM (SELECT 0 AS v UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t1,
            (SELECT 0 AS v UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t2,
            (SELECT 0 AS v UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t3) numbers
            WHERE CHAR_LENGTH(json_txt ) - CHAR_LENGTH(REPLACE(json_txt , '"', '')) >= n - 1
            AND n>0) v
            ) v2;
        CREATE TEMPORARY TABLE json_parts2 AS SELECT * FROM json_parts;
    
        CREATE TEMPORARY TABLE json_indexes AS
        SELECT p1.n, p1.n+1 AS key_idx, MIN(GREATEST(IF(p2.this_val,p2.n,0), IF(p2.next_val,p2.n+1,0))) AS val_idx, p2.this_val AS trim_val
        FROM json_parts p1
        JOIN json_parts2 p2 ON (p1.n < p2.n AND (p2.this_val OR p2.next_val)) 
        WHERE p1.next_key
        GROUP BY p1.n;  
    
        RETURN (SELECT json_values.v 
            FROM (SELECT p1.txt AS k, REPLACE(IF(i.trim_val, regexp_replace(regexp_replace(p2.txt,'^[: {]+',''),'[, }]+$',''), p2.txt), '|', ',') AS v
                FROM json_indexes i
                JOIN json_parts p1 ON (i.key_idx = p1.n)
                JOIN json_parts2 p2 ON (i.val_idx = p2.n)) json_values
            WHERE json_values.k = search_key);
    END$$
    
    DELIMITER ;
    

    Yep, and if you have the chance, try to upgrade to MySQL 5.7, the built-in functions work much more efficiently.

提交回复
热议问题