来自JSON数组的mySQL WHERE IN

时间:2016-07-18 09:15:54

标签: mysql arrays json

我有一个包含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魔法,我无法解决它......

4 个答案:

答案 0 :(得分:2)

以下是完整的答案。您可能需要在脚本顶部添加'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), '$')

您应该看到以下结果:

Results

答案 1 :(得分:2)

在将JSON引入MySQL之前,我使用以下方法:

  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

答案 2 :(得分:1)

请注意,可接受的答案不会使用<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script> <ul id="top-menu"> <li class="active"> <a href="#">Top</a> </li> <li> <a href="#foo">Foo</a> </li> <li> <a href="#bar">Bar</a> </li> <li> <a href="#baz">Baz</a> </li> </ul> <a id="foo">Foo</a> <a id="bar">Bar</a> <a id="baz">Baz</a>上的索引,这会导致较大表的性能下降。

在这种情况下,我通常使用一个tmp_other_items表,该表包含从0到任意固定数字N(大约一百万以下)的整数,并且我加入该整数表以获取第n个JSON元素:

integers

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

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

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

-- 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), '$')
;

最里面的-- 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 ; 检索JSON ID列表及其长度(用于外部联接)。

第二个内部SELECT接受此ID列表,然后对整数进行JOIN检索每个JSON列表的第n个ID,从而生成一个ID表(而不是JSON表)。

最外面的SELECT现在只需将此id表与包含所需数据的表连接起来。

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

SELECT

答案 3 :(得分:0)

从MySQL 8.0.13开始,有MEMBER OF运算符,它完全可以满足您的需求。

尽管如此,查询应该以{{1​​}}的形式重写:

JOIN

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


在以下示例中,可以更清楚地说明SELECT o.* FROM other_items o JOIN items i ON o.id MEMBER OF(i.data->>'$.id') 的使用:

MEMBER OF()

这就是找出值是否存在于JSON数组中的方法:

CREATE TABLE items ( data JSON );

INSERT INTO items
SET data = '{"id":[1,2,3]}';
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";