MySQL:索引可变长度的json数组?

Sun*_*nny 12 mysql indexing json

我想创建一个tags类型的列json:

例如,

id  |  tags
=========================================
1   |  '["tag1", "tag2", "tag3"]'
2   |  '["tag1", "tag3", "tag5", "tag7"]'
3   |  '["tag2", "tag5"]'
Run Code Online (Sandbox Code Playgroud)

我想tag在数组中索引每个数据,而不知道数组的长度(可变长度).

那么如果我查询包含的行tag2,它应该返回行1,3.

https://dev.mysql.com/doc/refman/5.7/en/json.html

无法为JSON列编制索引.您可以通过在生成的列上创建索引来解决此限制,该列从JSON列中提取标量值

通过"提取标量值",这是否意味着我必须单独提取和索引数组中的每个项目(这意味着我必须知道数组的最大长度以将它们全部索引)?如何索引可变长度数组?

hja*_*han 7

现在可以使用 MySQL 8.0.17+

像这样的东西(未测试)

CREATE TABLE posts (
    id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    tags JSON,
    INDEX tags( (CAST(tags AS VARCHAR(32) ARRAY)) )
    );
Run Code Online (Sandbox Code Playgroud)

以这种方式使用它:

SELECT * FROM posts 
         WHERE JSON_CONTAINS(tags, CAST('[tag1, tag2]' AS JSON));
Run Code Online (Sandbox Code Playgroud)

更多细节和示例:https : //dev.mysql.com/doc/refman/8.0/en/json.html

  • 不支持“VARCHAR”,应为“CHAR(32)”,https://dev.mysql.com/doc/refman/8.0/en/cast-functions.html#function_cast (3认同)
  • 您好,是否可以在字符串数组上创建多值索引。我尝试了类似的操作: CREATE INDEX products ON Components ( (CAST(data->'$.products' AS VARCHAR(255) ARRAY)) ) 但它给了我错误。有什么办法可以做到这一点吗? (2认同)

Taz*_*Taz 5

通过“提取标量值”,这是否意味着我必须分别提取并索引数组中的每个项目[...]?

您可以提取任意数量的项目。它们将被存储为标量(例如字符串),而不是复合值(JSON是)。

CREATE TABLE mytags (
    id INT NOT NULL AUTO_INCREMENT,
    tags JSON,
    PRIMARY KEY (id)
);

INSERT INTO mytags (tags) VALUES
    ('["tag1", "tag2", "tag3"]'),
    ('["tag1", "tag3", "tag5", "tag7"]'),
    ('["tag2", "tag5"]');

SELECT * FROM mytags;

+----+----------------------------------+
| id | tags                             |
+----+----------------------------------+
|  1 | ["tag1", "tag2", "tag3"]         |
|  2 | ["tag1", "tag3", "tag5", "tag7"] |
|  3 | ["tag2", "tag5"]                 |
+----+----------------------------------+
Run Code Online (Sandbox Code Playgroud)

让我们创建一个仅包含一项的索引(JSON对象的第一个值):

ALTER TABLE mytags
    ADD COLUMN tags_scalar VARCHAR(255) GENERATED ALWAYS AS (json_extract(tags, '$[0]')),
    ADD INDEX tags_index (tags_scalar);

SELECT * FROM mytags;

+----+----------------------------------+-------------+
| id | tags                             | tags_scalar |
+----+----------------------------------+-------------+
|  1 | ["tag1", "tag2", "tag3"]         | "tag1"      |
|  2 | ["tag1", "tag3", "tag5", "tag7"] | "tag1"      |
|  3 | ["tag2", "tag5"]                 | "tag2"      |
+----+----------------------------------+-------------+
Run Code Online (Sandbox Code Playgroud)

现在,您在VARCHAR列上有了一个索引tags_scalar。该值包含引号,也可以跳过引号:

ALTER TABLE mytags DROP COLUMN tags_scalar, DROP INDEX tags_index;

ALTER TABLE mytags
    ADD COLUMN tags_scalar VARCHAR(255) GENERATED ALWAYS AS (json_unquote(json_extract(tags, '$[0]'))),
    ADD INDEX tags_index (tags_scalar);

SELECT * FROM mytags;

+----+----------------------------------+-------------+
| id | tags                             | tags_scalar |
+----+----------------------------------+-------------+
|  1 | ["tag1", "tag2", "tag3"]         | tag1        |
|  2 | ["tag1", "tag3", "tag5", "tag7"] | tag1        |
|  3 | ["tag2", "tag5"]                 | tag2        |
+----+----------------------------------+-------------+
Run Code Online (Sandbox Code Playgroud)

您已经可以想象到,生成的列可以包含JSON中的更多项:

ALTER TABLE mytags DROP COLUMN tags_scalar, DROP INDEX tags_index;

ALTER TABLE mytags
    ADD COLUMN tags_scalar VARCHAR(255) GENERATED ALWAYS AS (json_extract(tags, '$[0]', '$[1]', '$[2]')),
    ADD INDEX tags_index (tags_scalar);

SELECT * from mytags;

+----+----------------------------------+--------------------------+
| id | tags                             | tags_scalar              |
+----+----------------------------------+--------------------------+
|  1 | ["tag1", "tag2", "tag3"]         | ["tag1", "tag2", "tag3"] |
|  2 | ["tag1", "tag3", "tag5", "tag7"] | ["tag1", "tag3", "tag5"] |
|  3 | ["tag2", "tag5"]                 | ["tag2", "tag5"]         |
+----+----------------------------------+--------------------------+
Run Code Online (Sandbox Code Playgroud)

或使用任何其他有效表达式从JSON结构中自动生成字符串,以获取可以轻松索引和搜索的内容,例如“ tag1tag3tag5tag7”。

[...](这意味着我必须知道数组的最大长度才能对它们全部进行索引)?

如上所述,您不需要知道-可以使用任何有效的表达式来跳过NULL值。但是,当然总要知道。
现在有体系结构决策:JSON数据类型最适合实现目标吗?要解决这个特定问题?JSON是这里的正确工具吗?它会加快搜索速度吗?

如何索引可变长度数组?

如果您坚持,则强制转换字符串:

ALTER TABLE mytags DROP COLUMN tags_scalar, DROP INDEX tags_index;

ALTER TABLE mytags
    ADD COLUMN tags_scalar VARCHAR(255) GENERATED ALWAYS AS (replace(replace(replace(cast(tags as char), '"', ''), '[', ''), ']', '')),
    ADD INDEX tags_index (tags_scalar);

SELECT * from mytags;

+----+----------------------------------+------------------------+
| id | tags                             | tags_scalar            |
+----+----------------------------------+------------------------+
|  1 | ["tag1", "tag2", "tag3"]         | tag1, tag2, tag3       |
|  2 | ["tag1", "tag3", "tag5", "tag7"] | tag1, tag3, tag5, tag7 |
|  3 | ["tag2", "tag5"]                 | tag2, tag5             |
+----+----------------------------------+------------------------+
Run Code Online (Sandbox Code Playgroud)

这样或那样,您最终将得到VARCHAR或TEXT列,在其中应用最适用的索引结构(某些选项)。

进一步阅读:


Bil*_*win 5

在MySQL中以JSON索引数组不切实际。

您可以使用生成的列将数组的每个元素提取到单独的标量列中,并为每个生成的列建立索引。但是您需要多少列?您如何知道哪一列包含要搜索的值?

您可以使用生成的列,如@bostaf的答案所示,提取多个数组值并制作一个逗号分隔的字符串。您不能使用普通索引在此字符串中搜索中间可能出现的单词。也许您可以使用全文索引,但这仅在数组元素均为单个单词的情况下有效。

在2018年4月,我做了一个关于在MySQL中使用JSON的弱点的演讲:如何在MySQL 中使用JSON Wrong

对于多值属性,更好的解决方案是按照数据库规范化的方式将它们存储在从属表中。然后,这些值将显示在一行中的多行中,您可以以更直接的方式对其进行索引。