mysql - MySQL 中的异国 GROUP BY
问题描述
考虑 SQL 中典型的 GROUP BY 语句:您有一个类似的表
+------+-------+
| Name | Value |
+------+-------+
| A | 1 |
| B | 2 |
| A | 3 |
| B | 4 |
+------+-------+
你要求
SELECT Name, SUM(Value) as Value
FROM table
GROUP BY Name
你会收到
+------+-------+
| Name | Value |
+------+-------+
| A | 4 |
| B | 6 |
+------+-------+
在您的脑海中,您可以想象 SQL 生成一个中间排序表,例如
+------+-------+
| Name | Value |
+------+-------+
| A | 1 |
| A | 3 |
| B | 2 |
| B | 4 |
+------+-------+
然后将连续的行聚合在一起:“值”列已被赋予一个聚合器(在本例中为 SUM),因此很容易聚合。“名称”列没有被赋予聚合器,因此使用您可能称之为“琐碎的部分聚合器”:给定两个相同的事物(例如 A 和 A),它将它们聚合到其中一个的单个副本中输入(在本例中为 A)。给定任何其他输入,它不知道该做什么,并被迫重新开始聚合(这次“名称”列等于 B)。
我想做一种更奇特的聚合。我的桌子看起来像
+------+-------+
| Name | Value |
+------+-------+
| A | 1 |
| BC | 2 |
| AY | 3 |
| AZ | 4 |
| B | 5 |
| BCR | 6 |
+------+-------+
预期的输出是
+------+-------+
| Name | Value |
+------+-------+
| A | 8 |
| B | 13 |
+------+-------+
这是从哪里来的?A 和 B 是这组名称的“最小前缀”:它们出现在数据集中,每个名称都有其中一个作为前缀。当它们的名称具有相同的最小前缀时,我想通过将行分组在一起来聚合数据(当然还要添加值)。
在之前的玩具分组模型中,中间排序表将是
+------+-------+
| Name | Value |
+------+-------+
| A | 1 |
| AY | 3 |
| AZ | 4 |
| B | 5 |
| BC | 2 |
| BCR | 6 |
+------+-------+
如果 X 是 Y 的前缀,我们将使用可以将 X 和 Y 聚合在一起的名称,而不是使用“简单的部分聚合器”;在这种情况下,它返回 X。因此,前三行将被聚合成一行,其中 (Name, Value) = (A, 8),然后聚合器将看到 A 和 B 无法聚合并继续到要聚合的新“块”行。
棘手的是我们分组的值是“非本地的”:如果 A 不是数据集中的名称,那么 AY 和 AZ 都将是一个最小前缀。事实证明,AY 和 AZ 行在最终输出中聚合到同一行中,但仅通过单独查看它们是无法知道的。
神奇的是,在我的用例中,可以在不参考数据集中其他任何内容的情况下确定字符串的最小前缀。(假设我的每个名字都是字符串“hello”、“world”和“bar”之一,后跟任意数量的 z。我想将所有具有相同“base”单词的 Names 组合在一起。)
正如我所看到的,我有两个选择:
1)简单的选项:计算每一行的前缀并直接按该值分组。不幸的是,我在名称上有一个索引,并且计算最小前缀(其长度取决于名称本身)阻止我使用该索引。这会强制进行全表扫描,这非常慢。
2) 复杂的选项:以某种方式说服 MySQL 使用“部分前缀聚合器”作为名称。这遇到了上面的“非局部性”问题,但是只要我们根据我的 Name 索引扫描表就可以了,因为这样每个最小前缀都会在它作为前缀的任何其他字符串之前遇到;如果 A 在数据集中,我们永远不会尝试将 AY 和 AZ 聚合在一起。
在声明性编程语言中,#2 相当简单:一次提取一行,按字母顺序,跟踪当前前缀。如果您的新行的名称具有前缀,它将进入您当前使用的存储桶。否则,以它作为前缀启动一个新存储桶。在 MySQL 中,我不知道该怎么做。请注意,最小前缀的集合事先是未知的。
解决方案
编辑 2
我突然想到,如果表格是按 排序的Name
,这会更容易(而且更快)。由于我不知道您的数据是否已排序,因此我在此查询中包含了排序,但如果数据已排序,您可以删除(SELECT * FROM table1 ORDER BY Name) t1
并使用FROM table1
SELECT prefix, SUM(`Value`)
FROM (SELECT Name, Value, @prefix:=IF(Name NOT LIKE CONCAT(@prefix, '_%'), Name, @prefix) AS prefix
FROM (SELECT * FROM table1 ORDER BY Name) t1
JOIN (SELECT @prefix := '~') p
) t2
GROUP BY prefix
编辑
解决了这个问题,我意识到没有必要这样做,只需要在 JOINed 表上IN
有一个子句就足够了:WHERE NOT EXISTS
SELECT t1.Name, SUM(t2.Value) AS `Value`
FROM table1 t1
JOIN table1 t2 ON t2.Name LIKE CONCAT(t1.Name, '%')
WHERE NOT EXISTS (SELECT *
FROM table1 t3
WHERE t1.Name LIKE CONCAT(t3.Name, '_%')
)
GROUP BY t1.Name
更新了解释(Name
更改为UNIQUE
key from PRIMARY
)
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 index Name Name 11 NULL 6 Using where; Using index; Using temporary; Using filesort
1 PRIMARY t2 ALL NULL NULL NULL NULL 6 Using where; Using join buffer (Block Nested Loop)
3 DEPENDENT SUBQUERY t3 index NULL Name 11 NULL 6 Using where; Using index
原始答案
这是您可以做到的一种方法。首先,您需要找到表中的所有唯一前缀。您可以通过查找Name
它看起来不像另一个值的所有值来做到这一点Name
,最后是其他字符。这可以通过以下查询来完成:
SELECT Name
FROM table1 t1
WHERE NOT EXISTS (SELECT *
FROM table1 t2
WHERE t1.Name LIKE CONCAT(t2.Name, '_%')
)
对于您的示例数据,这将给出
Name
A
B
现在,您可以对 Name 以这些前缀之一开头的所有值求和。请注意,我们更改了LIKE
此查询中的模式,使其也与前缀匹配,否则我们不会在您的示例中计算A
and的值:B
SELECT t1.Name, SUM(t2.Value) AS `Value`
FROM table1 t1
JOIN table1 t2 ON t2.Name LIKE CONCAT(t1.Name, '%')
WHERE t1.Name IN (SELECT Name
FROM table1 t3
WHERE NOT EXISTS (SELECT *
FROM table1 t4
WHERE t3.Name LIKE CONCAT(t4.Name, '_%')
)
)
GROUP BY t1.Name
输出:
Name Value
A 8
B 13
AnEXPLAIN
说这两个查询都使用 上的索引Name
,因此应该相当有效。这是我的 MySQL 5.6 服务器上的解释结果:
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 index PRIMARY PRIMARY 11 NULL 6 Using index; Using temporary; Using filesort
1 PRIMARY t3 eq_ref PRIMARY PRIMARY 11 test.t1.Name 1 Using where; Using index
1 PRIMARY t2 ALL NULL NULL NULL NULL 6 Using where; Using join buffer (Block Nested Loop)
3 DEPENDENT SUBQUERY t4 index NULL PRIMARY 11 NULL 6 Using where; Using index
推荐阅读
- django - 无法使用 DRF 在 Django 模板中显示图像
- wpf - 在本地/直接设置控件的(触发)属性时,ControlTemplate/Style 触发器不起作用
- javascript - Vue.js - 在另一个数组中显示嵌套数组的详细信息
- c - 如何反转C中的句子?
- paypal - 什么是 Paypal 的定期付款配置文件 RPSTATE 参数?
- postgresql - 更改 client_min_messages 和 log_min_messages
- python-3.x - 您如何比较包含不可散列对象的字典?
- c - 字符串计数问题、浮点整数、基于字母、单词和句子数量的评分公式,在 C 中
- jquery - 选中复选框时选择特定的单选选项jQuery
- c# - 如何声明一个完全限定的路径来实例化一个对象?