如何使用 MySQL 8 在 JSON 数组上创建索引

How to create an index on a JSON array using MySQL 8

我在 mysql 8 中有一个 JSON 字段,如下所示:

[{"key": "apples", "string_values": ["red"]}, {"key": "oranges", "string_values": ["orange"]}]

有没有办法为字符串值创建索引?我希望我能做这样的事情:

ALTER TABLE mytable ADD INDEX myindex( 
    (CAST(metadata->'$[*].string_values' AS UNSIGNED ARRAY)) 
);

但是returns出现以下错误:无法在索引的标量键部分存储数组或对象

Fiddle 例如:https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=ae36a5169094ae67a290febf09f49f0c

编辑:

我开始认为我能做到这一点的唯一方法是使用生成的列:

下面的select现在将命中指数

ALTER TABLE mytable ADD string_values JSON AS (JSON_EXTRACT(metadata, '$[*].string_values[0]'));

ALTER TABLE mytable ADD INDEX idx_string_values( 
    (CAST(string_values->'$' AS CHAR(100) ARRAY))
);

SELECT * FROM mytable WHERE 'red' MEMBER OF(string_values->'$');

在回答之前我应该​​做一个简短的免责声明。在 SQL 数据库中存储 JSON 数据实际上违背了基本的 SQL 设计理念。 SQL,顾名思义,用于结构化,即强类型数据。如果您将数据作为 JSON 传递给 SQL 数据库,您实际上是在丢弃其结构并将其视为字符串。

因此,遇到这种情况我会首先考虑以下两种方案:

(A) 我可以将此数据视为结构化数据吗?在这种情况下,我会创建一个 table,其中的列对应于 JSON 字段,然后像其他任何列一样创建一个索引。

(B) 我的数据基本上 是非结构化的吗?在这种情况下,我会尝试将其建模为 SQL 中的键值对,或者使用非 SQL 数据库,例如 CouchDB 或 Elasticsearch。

使用基于键值的方法,您可以将有问题的 属性 分成单独的 table(对于数组 key/index/value),然后您可以将其用于索引。这当然会增加一些开销,根据具体情况,在您的情况下可能会或可能不会接受table。

但是,仍然有很多合理的理由可以说明某些东西可能无法归类为 A 或 B。如果您已经考虑并尝试过这两种方法,并且您可以清楚地证明您的案例是例外的:

(C) 阅读 the MySQL documentation about indexing generated columns

即便如此,您可能仍需要重构 JSON 数据,例如将数组转换为单独的条目 (id/index/value)。

有 2 个不同的问题。


首先是可以解决的。下一个fiddle描述一下:

CREATE TABLE mytable (metadata JSON);
INSERT INTO mytable VALUES ('[{"key": "apples", "string_values": ["red"]}, {"key": "oranges", "string_values": ["orange"]}]');
-- you try to index arrays
SELECT metadata->'$[*].string_values' FROM mytable;
-- but you need in scalar values
SELECT metadata->'$[*].string_values[0]' FROM mytable;
| metadata->'$[*].string_values' |
| :----------------------------- |
| [["red"], ["orange"]]          |

| metadata->'$[*].string_values[0]' |
| :-------------------------------- |
| ["red", "orange"]                 |

db<>fiddle here


第二题无解

仔细查看您尝试创建索引时使用的语句:

ALTER TABLE mytable ADD INDEX myindex((CAST(metadata->'$[*].string_values' AS UNSIGNED ARRAY))) ;

数组 ["red", "orange"] 中的值不是数字。