Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/80.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
选择MySQL表中JSON类型值的计数_Mysql_Sql_Json - Fatal编程技术网

选择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:每个项目的平均类别数
您的查询的复杂性为O(n*m)。即使是Bill Karwins的解决方案(我认为是最优的)也有O(n*a)的复杂性(假设GROUP BY子句的索引位于
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的案子是最好的。