[英]MySQL: index json arrays of variable length?
我想制作一個json
類型的tags
列:
例如,
id | tags
=========================================
1 | '["tag1", "tag2", "tag3"]'
2 | '["tag1", "tag3", "tag5", "tag7"]'
3 | '["tag2", "tag5"]'
我想索引數組中的每個tag
,而不知道數組的長度(可變長度)。
因此,如果我查詢包含tag2
行,它應該返回第 1、3 行。
https://dev.mysql.com/doc/refman/5.7/en/json.html
無法為 JSON 列編制索引。 您可以通過在從 JSON 列中提取標量值的生成列上創建索引來解決此限制
通過“提取標量值”,這是否意味着我必須單獨提取和索引數組中的每個項目(意味着我必須知道數組的最大長度才能將它們全部索引)? 如何索引可變長度數組?
通過“提取標量值”,這是否意味着我必須單獨提取和索引數組中的每個項目 [...]?
您可以根據需要提取任意數量的項目。 它們將存儲為標量(例如字符串),而不是復合值(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"] |
+----+----------------------------------+
讓我們創建一個僅包含一個項目的索引(來自 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" |
+----+----------------------------------+-------------+
現在您在 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 |
+----+----------------------------------+-------------+
正如您已經想象的那樣,生成的列可以包含來自 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"] |
+----+----------------------------------+--------------------------+
或者使用任何其他有效的表達式從 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 |
+----+----------------------------------+------------------------+
以這種方式或另一種方式,您最終會得到一個 VARCHAR 或 TEXT 列,您可以在其中應用最適用的索引結構( 一些選項)。
進一步閱讀:
現在可以使用 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)) )
);
以這種方式使用它:
SELECT * FROM posts
WHERE JSON_CONTAINS(tags, CAST('[tag1, tag2]' AS JSON));
更多細節和示例: https : //dev.mysql.com/doc/refman/8.0/en/json.html
在 MySQL 中用 JSON 索引數組是不切實際的。
您可以使用生成的列將數組的每個元素提取到單獨的標量列中,並對每個生成的列進行索引。 但是您需要多少列? 您如何知道哪一列包含您要搜索的值?
您可以使用@bostaf 的答案所示的生成列,提取多個數組值並制作逗號分隔的字符串。 您不能使用普通索引在此字符串中搜索可能出現在中間的單詞。 也許您可以使用全文索引,但這僅在數組元素都是單個單詞時才有效。
2018 年 4 月,我做了一個關於在 MySQL 中使用 JSON 的這種弱點的演講: How to Use JSON in MySQL Wrong 。
多值屬性的更好解決方案是以數據庫規范化所禁止的方式將它們存儲在依賴表中。 然后這些值出現在多行中的單個列中,您可以以更直接的方式對其進行索引。
回復您的評論:
我想出了一個在 JSON 數組上強制執行唯一性的解決方案,但這取決於數組元素的排序順序。
mysql> create table mytags ( tags json );
mysql> insert into mytags values ('["tag1", "tag3", "tag5", "tag7"]');
JSON_UNQUOTE()函數以字符串形式返回 JSON。
mysql> select json_unquote(tags) from mytags;
+----------------------------------+
| json_unquote(tags) |
+----------------------------------+
| ["tag1", "tag3", "tag5", "tag7"] |
+----------------------------------+
現在我們知道如何根據它創建一個生成列,然后在生成的列上創建一個 UNIQUE KEY 索引。 這適用於 MySQL 5.7 及更高版本。
mysql> alter table mytags
add column j varchar(768) as (json_unquote(tags)),
add unique index (j);
現在嘗試在 JSON 列中插入相同的值數組失敗:
mysql> insert into mytags (tags) values ('["tag1", "tag3", "tag5", "tag7"]');
ERROR 1062 (23000): Duplicate entry '["tag1", "tag3", "tag5", "tag7"]' for key 'j'
不幸的是,沒有好的方法可以確保 JSON 數組已排序。 請參閱已排序的 json 數組字段因此,由您來設計應用程序代碼,以便它始終在插入或更新之前對 JSON 數組中的值進行預排序。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.