【问题标题】:mySQL WHERE IN from JSON Array来自 JSON 数组的 mySQL WHERE IN
【发布时间】:2016-11-20 20:00:04
【问题描述】:

我有一个包含 JSON 数据的表,以及一个为每一行提取 ID 数组的语句...

SELECT items.data->"$.matrix[*].id" as ids
FROM items

这会导致类似..

+------------+
|    ids     |
+------------+
| [1,2,3]    |
+------------+

接下来我想从另一个表中选择另一个表的 ID 在数组中,类似于 WHERE id IN ('1,2,3') 但使用 JSON 数组...

类似于...的东西

SELECT * FROM other_items 
WHERE id IN ( 
  SELECT items.data->"$.matrix[*].id" FROM items
);

但它需要一些 JSON 魔法,我无法解决...

【问题讨论】:

  • 这个[1,2,3]是怎么把数据存到那个对应列的?我的意思是逗号分隔的ids[] 括起来?
  • this post 相关
  • 结果数据是一个 mySQL JSON ARRAY
  • 感谢@ImranAli,但这似乎是关于从单个值中进行选择,我想要做的是选择值包含在 ARRAY 类型中的所有行
  • 有一个通配符可以选择多个值,请阅读this documentation中的搜索和修改JSON值部分

标签: mysql arrays json


【解决方案1】:

以下是完整的答案。您可能需要在脚本顶部使用'use <db_name>;' 语句。重点是表明 JSON_CONTAINS() 可用于实现所需的连接。

DROP TABLE IF EXISTS `tmp_items`;
DROP TABLE IF EXISTS `tmp_other_items`;

CREATE TABLE `tmp_items` (`id` int NOT NULL PRIMARY KEY AUTO_INCREMENT, `data` json NOT NULL);
CREATE TABLE `tmp_other_items` (`id` int NOT NULL, `text` nvarchar(30) NOT NULL);

INSERT INTO `tmp_items` (`data`) 
VALUES 
    ('{ "matrix": [ { "id": 11 }, { "id": 12 }, { "id": 13 } ] }')
,   ('{ "matrix": [ { "id": 21 }, { "id": 22 }, { "id": 23 }, { "id": 24 } ] }')
,   ('{ "matrix": [ { "id": 31 }, { "id": 32 }, { "id": 33 }, { "id": 34 }, { "id": 35 } ] }')
;

INSERT INTO `tmp_other_items` (`id`, `text`) 
VALUES 
    (11, 'text for 11')
,   (12, 'text for 12')
,   (13, 'text for 13')
,   (14, 'text for 14 - never retrieved')
,   (21, 'text for 21')
,   (22, 'text for 22')
-- etc...
;

-- Show join working:
SELECT 
    t1.`id` AS json_table_id
,   t2.`id` AS joined_table_id
,   t2.`text` AS joined_table_text
FROM 
    (SELECT st1.id, st1.data->'$.matrix[*].id' as ids FROM `tmp_items` st1) t1
INNER JOIN `tmp_other_items` t2 ON JSON_CONTAINS(t1.ids, CAST(t2.`id` as json), '$')

您应该会看到以下结果:

【讨论】:

【解决方案2】:

在 MySQL 中引入 JSON 之前,我使用这个:

  1. 你的原始数据:[1,2,3]

  2. 用']['替换逗号后:[1][2][3]

  3. 将你的 id 包装在 '[]' 中

  4. 然后使用 REVERSE LIKE 代替 IN:WHERE '[1][2][3]' LIKE '%[1]%'

回答你的问题:

SELECT * FROM other_items 
WHERE
    REPLACE(SELECT items.data->"$.matrix[*].id" FROM items, ',', '][')
    LIKE CONCAT('%', CONCAT('[', id, ']'), '%')

为什么要换成'[]'

'[12,23,34]' LIKE '%1%' --> true
'[12,23,34]' LIKE '%12%' --> true

如果换成'[]'

'[12][23][34]' LIKE '%[1]%' --> false
'[12][23][34]' LIKE '%[12]%' --> true

【讨论】:

    【解决方案3】:

    请注意,接受的答案不会在 tmp_other_items 上使用索引,这会导致较大表的性能下降。

    在这种情况下,我通常使用integers 表,其中包含从 0 到任意固定数 N(低于,大约 100 万)的整数,然后我加入该整数表以获取第 n 个 JSON 元素:

    DROP TABLE IF EXISTS `integers`;
    DROP TABLE IF EXISTS `tmp_items`;
    DROP TABLE IF EXISTS `tmp_other_items`;
    
    CREATE TABLE `integers` (`n` int NOT NULL PRIMARY KEY);
    CREATE TABLE `tmp_items` (`id` int NOT NULL PRIMARY KEY AUTO_INCREMENT, `data` json NOT NULL);
    CREATE TABLE `tmp_other_items` (`id` int NOT NULL PRIMARY KEY, `text` nvarchar(30) NOT NULL);
    
    INSERT INTO `tmp_items` (`data`) 
    VALUES 
        ('{ "matrix": [ { "id": 11 }, { "id": 12 }, { "id": 13 } ] }'),
       ('{ "matrix": [ { "id": 21 }, { "id": 22 }, { "id": 23 }, { "id": 24 } ] }'),
       ('{ "matrix": [ { "id": 31 }, { "id": 32 }, { "id": 33 }, { "id": 34 }, { "id": 35 } ] }')
    ;
    
    -- Put a lot of rows in integers (~1M)
    INSERT INTO `integers` (`n`) 
    (
        SELECT 
            a.X
            + (b.X << 1)
            + (c.X << 2)
            + (d.X << 3)
            + (e.X << 4)
            + (f.X << 5)
            + (g.X << 6)
            + (h.X << 7)
            + (i.X << 8)
            + (j.X << 9)
            + (k.X << 10)
            + (l.X << 11)
            + (m.X << 12)
            + (n.X << 13)
            + (o.X << 14)
            + (p.X << 15)
            + (q.X << 16)
            + (r.X << 17)
            + (s.X << 18)
            + (t.X << 19) AS i
        FROM (SELECT 0 AS x UNION SELECT 1) AS a
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS b ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS c ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS d ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS e ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS f ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS g ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS h ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS i ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS j ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS k ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS l ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS m ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS n ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS o ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS p ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS q ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS r ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS s ON TRUE
            INNER JOIN (SELECT 0 AS x UNION SELECT 1) AS t ON TRUE)
    ;
    
    -- Insert normal rows (a lot!)
    INSERT INTO `tmp_other_items` (`id`, `text`) 
        (SELECT n, CONCAT('text for ', n) FROM integers);
    

    现在您可以再次尝试接受答案的查询,这需要大约 11 秒才能运行(但很简单):

    -- Show join working (slow)
    SELECT 
        t1.`id` AS json_table_id
    ,   t2.`id` AS joined_table_id
    ,   t2.`text` AS joined_table_text
    FROM 
        (SELECT st1.id, st1.data->'$.matrix[*].id' as ids FROM `tmp_items` st1) t1
    INNER JOIN `tmp_other_items` t2 ON JSON_CONTAINS(t1.ids, CAST(t2.`id` as JSON), '$')
    ;
    

    并将其与将 JSON 转换为(临时)id 表,然后对其进行 JOIN 的更快方法进行比较(根据 heidiSQL,这会导致 即时结果,0.000 秒) :

    -- Fast
    SELECT
        i.json_table_id,
        t2.id AS joined_table_id,
        t2.`text` AS joined_table_text
    FROM (
        SELECT 
            j.json_table_id,
            -- Don't forget to CAST if needed, so the column type matches the index type
            -- Do an "EXPLAIN" and check its warnings if needed
            CAST(JSON_EXTRACT(j.ids, CONCAT('$[', i.n - 1, ']')) AS UNSIGNED) AS id
        FROM (
            SELECT 
                st1.id AS json_table_id,
                st1.data->'$.matrix[*].id' as ids,
                JSON_LENGTH(st1.data->'$.matrix[*].id') AS len
            FROM `tmp_items` AS st1) AS j
            INNER JOIN integers AS i ON i.n BETWEEN 1 AND len) AS i
        INNER JOIN tmp_other_items AS t2 ON t2.id = i.id
        ;
    

    最内部的SELECT 检索 JSON id 列表及其长度(用于外部连接)。

    第二个内部 SELECT 采用这个 id 列表,并在整数上进行 JOIN 以检索每个 JSON 列表的第 n 个 id,从而生成一个 id 表(而不是一个 json 表)。

    最外层的 SELECT 现在只需要将这个 id 表与包含您想要的数据的表连接起来。

    下面是使用 WHERE IN 的相同查询,以匹配问题标题:

    -- Fast (using WHERE IN)
    SELECT t2.*
    FROM tmp_other_items AS t2
    WHERE t2.id IN (
        SELECT 
            CAST(JSON_EXTRACT(j.ids, CONCAT('$[', i.n - 1, ']')) AS UNSIGNED) AS id
        FROM (
            SELECT 
                st1.data->'$.matrix[*].id' as ids, 
                JSON_LENGTH(st1.data->'$.matrix[*].id') AS len
            FROM `tmp_items` AS st1) AS j
            INNER JOIN integers AS i ON i.n BETWEEN 1 AND len)
        ;
    

    【讨论】:

    • 嘿,这看起来非常巧妙,JSON_EXTRACT 基于长度的技巧非常聪明!但我无法理解 1m 整数表:为什么这么多?你真的希望matrix 密钥包含这么多的 id 吗?我错过了什么明显的东西吗?谢谢!
    • @Gruber 谢谢!整数表有 1M 行没有具体原因:在我的情况下它有 1M 行,因为我也将它用于需要 100K+ 行的其他一些东西。您只需要一个“在您的应用程序生命周期内有足够的行数大于您的 JSON_LENGTH”的表
    • 感谢您的回答!抱歉,我是一个 SQL 新手,我注意到使用 1m 行 ints 表在我的机器上查询可能会有点慢,@ 987654321@ 带有解释的要点。 ints 表中扫描的行数为 530258,约为总数的一半。这是因为between 1 and j.len。我注意到像between 1 and 10 这样的硬编码 int 查询是即时的,也将 ints 总行数减少到 10k 将使查询即时,但仍然会扫描大约 5k 行。这是为什么?再次感谢您!
    • 附言。如果这有什么不同,我正在使用 MariaDB 10.3.18
    • integers 表在其n 列上有一个索引,因此行数(简而言之)并不重要根本(即使是 10 亿行也会快到 100 行)。性能取决于矩阵 JSON 数组中的元素数量(如果您有 500k 个元素,则将表切割为 10K 不会产生所有元素!:))。不要忘记也索引您的 tmp_other_items。我不知道 mysql/maridb 的区别;只要您使用最新版本,就可以了。
    【解决方案4】:

    从 MySQL 8.0.13 开始,有 MEMBER OF 运算符,它完全符合您的要求。

    查询应该以JOIN的形式重写,但是:

    SELECT o.* FROM other_items o
    JOIN items i ON o.id MEMBER OF(i.data->>'$.id')
    

    如果您希望查询具有更好的性能,请考虑在 JSON 列上使用 multi-valued indexes


    MEMBER OF() 的使用可以在以下示例中更清楚地解释:

    CREATE TABLE items ( data JSON );
    
    INSERT INTO items
    SET data = '{"id":[1,2,3]}';
    

    这就是你如何找出值是否存在于 JSON 数组中:

    SELECT * FROM items
    WHERE 3 MEMBER OF(data->>'$.id');
    
    +-------------------+
    | data              |
    +-------------------+
    | {"id": [1, 2, 3]} |
    +-------------------+
    1 row in set (0.00 sec)
    

    请注意,值的类型在这种情况下很重要,这与常规比较不同。如果以字符串的形式传递,则不会匹配:

    SELECT * FROM items
    WHERE "3" MEMBER OF(data->>'$.id');
    

    Empty set (0.00 sec)

    虽然常规比较会返回 1:

    SELECT 3 = "3";
    
    +---------+
    | 3 = "3" |
    +---------+
    |       1 |
    +---------+
    1 row in set (0.00 sec)
    

    【讨论】:

      猜你喜欢
      • 2020-02-14
      • 2012-08-27
      • 2014-05-09
      • 1970-01-01
      • 2016-07-07
      • 1970-01-01
      • 1970-01-01
      • 2011-09-30
      • 2010-09-15
      相关资源
      最近更新 更多