选择MySQL表中JSON类型值的计数
假设我们有下表选择MySQL表中JSON类型值的计数,mysql,sql,json,Mysql,Sql,Json,假设我们有下表 +-----+------------+ | id | categories | +-----+------------+ | id1 | [20,25] | | id2 | [25] | | id3 | [20,25,28] | | id4 | [28,25] | | id5 | [20,25] | +-----+------------+ 字段categories为JSON类型。它只包含已知且有限的整数列表,如20、25、28。 所以,我需要
+-----+------------+
| id | categories |
+-----+------------+
| id1 | [20,25] |
| id2 | [25] |
| id3 | [20,25,28] |
| id4 | [28,25] |
| id5 | [20,25] |
+-----+------------+
字段categories
为JSON类型。它只包含已知且有限的整数列表,如20、25、28。
所以,我需要以如下方式计算所有这些值的所有包含项:
+-------+--------+
| count | number |
+-------+--------+
| 20 | 3 |
| 25 | 5 |
| 28 | 2 |
+-------+--------+
SELECT category_id, COUNT(*) as count
FROM things_have_categories
GROUP BY category_id
主要问题是使用单个请求来实现这一点,而不需要在服务器代码或过程调用中循环类别编号
直接决定如下
SELECT
COUNT(id) AS 'count', '20' AS number
FROM
ml_categories
WHERE
JSON_CONTAINS(categories, '20')
UNION SELECT
COUNT(id) AS 'count', '25' AS number
FROM
ml_categories
WHERE
JSON_CONTAINS(categories, '25')
UNION SELECT
COUNT(id) AS 'count', '28' AS number
FROM
ml_categories
WHERE
JSON_CONTAINS(categories, '28')
但是这个解决方案有O(n)复杂度,代码本身不够好。例如,对于我的硬件上的一个类别,循环大约500K条记录大约需要1秒,因此计算10个类别大约需要10秒。不好的。
有没有办法优化这样的查询
Thx事先,存储逗号分隔列表并不是一种关系策略。它是非规范化的。与所有优化一样,非规范化会以牺牲其他类型查询为代价优化一种类型的查询 因此,在优化其他类型的查询时遇到困难也就不足为奇了 优化此查询的方法是避免将多值属性存储在逗号分隔的列表(或逻辑上等效的JSON)中。相反,将多值属性按行存储,每行一个值,而不是以逗号分隔的列表或JSON对象。换句话说,规范化您的数据 为“事物”(无论它们是什么)和类别之间的多对多关系创建一个表:
CREATE TABLE things_have_categories (
thing_id VARCHAR(10),
category_id INT,
PRIMARY KEY (category_id, thing_id)
);
INSERT INTO things_have_categories VALUES
('id1', 20),
('id1', 25),
('id2', 25),
('id3', 20),
('id3', 25),
('id3', 28),
('id4', 28),
('id4', 25),
('id5', 20),
('id5', 25);
然后,您可以编写一个更简单、优化的查询,如下所示:
+-------+--------+
| count | number |
+-------+--------+
| 20 | 3 |
| 25 | 5 |
| 28 | 2 |
+-------+--------+
SELECT category_id, COUNT(*) as count
FROM things_have_categories
GROUP BY category_id
输出:
+-------------+-------+
| category_id | count |
+-------------+-------+
| 20 | 3 |
| 25 | 5 |
| 28 | 2 |
+-------------+-------+
你可能也喜欢
您可能会回答,“但我无法更改此表的存储方式。”
我以前听说过。如果这是约束条件,那么就不能优化查询。它必须是O(n)。您可以对数组所有三个组件的每个值(
20
、25
和28
)使用JSON_EXTRACT()
函数,然后使用Conditional Aggregation
,然后应用UNION all
组合所有此类查询:
SELECT 20 as count, sum(case when 20 in (comp1,comp2,comp3) then 1 end) as number
FROM
(SELECT JSON_EXTRACT(categories, '$[0]') as comp1,
JSON_EXTRACT(categories, '$[1]') as comp2,
JSON_EXTRACT(categories, '$[2]') as comp3
FROM ml_categories ) q1
UNION ALL
SELECT 25 as count, sum(case when 25 in (comp1,comp2,comp3) then 1 end) as number
FROM
(SELECT JSON_EXTRACT(categories, '$[0]') as comp1,
JSON_EXTRACT(categories, '$[1]') as comp2,
JSON_EXTRACT(categories, '$[2]') as comp3
FROM ml_categories ) q2
UNION ALL
SELECT 28 as count, sum(case when 28 in (comp1,comp2,comp3) then 1 end) as number
FROM
(SELECT JSON_EXTRACT(categories, '$[0]') as comp1,
JSON_EXTRACT(categories, '$[1]') as comp2,
JSON_EXTRACT(categories, '$[2]') as comp3
FROM ml_categories ) q3
但是这个解决方案有O(n)复杂性
我不确定你的情况是什么。但我相信你不会找到比O(n)更好的解决方案
假设以下数字:
- n:项目数(“ml\U类别”表中的行)
- m:所有类别的编号
- a:每个项目的平均类别数
category_id
)
如果有一个包含所有类别的categories
表,则可以使用以下查询:
select c.id as category, count(*)
from ml_categories i
join categories c on json_contains(i.categories, cast(c.id as json))
group by c.id
它将返回与联合查询相同的结果:
| category | count(*) |
| -------- | -------- |
| 20 | 3 |
| 25 | 5 |
| 28 | 2 |
而且因为没有索引可以用于连接,所以它可能和查询一样快或一样慢(在最好的情况下,如果可以通过避免文件排序将索引用于组)
如果您使用MySQL 8(至少8.0.4),您可以利用:
JOIN with JSON_表将把JSON列中的类别“解包”成行。如果删除GROUPBY子句,则将得到一个(动态)规范化的表。这应该与O(n*a)成比例。但是,由于表是动态创建的,因此没有支持GROUPBY子句的索引。因此必须首先对结果进行排序,结果的复杂度为O(n*a*log(n*a))。这比O(n*m)的伸缩性好(如果m增长而a不增长)。但是,如果m(类别数)足够小,您的查询可能仍然是使用给定架构所能做的最好的查询。您的表不是标准的,如果您可以查看表,输入每个行点的值,通常都是表扫描,除非您限制要搜索的类别id的值。如果category\u id上的索引包含category\u id的每个不同值的条目计数,则理论上可能是
O(a)
。。如果没有WHERE(或等效)条件,就无法击败O(n)。但是,如果您想要获取特定类别的所有项,那么规范化模式是更优的(O(log(n))vs O(n))。我想没有一个问题可以用O(a)来解决。如果你想让结果包含所有类别,你不能得到比O(m)更好的结果(例如:从“缓存”表中读取计数)。不管怎样,我得到的印象是OP正在寻找一个代码更简单、计算复杂度最高的查询。太棒了!这就是我要找的。还开始使用cast
切换类型来构建这样的查询。性能上的一点改进也是可以测量的。Stackoverflow很棒!一如既往:)谢谢。我知道您的解决方案通过设计本身更可行、更好,但使用JSON类型只是一种实验——包括性能问题。这就是为什么我的问题对我来说也有点奇怪的原因——知道表本身应该设计得更好。好吧,你的实验结果是:JSON使插入复杂数据变得更容易(在本例中,每件事情都有一个类别id的列表),但查询该数据更难。不。我的问题是如何摆脱多个语句,并可能提高性能。保罗·斯皮格尔认为是最好的。是的,我刚刚看到@Paul Spiegel的案子是最好的。