在 SQL 中选择具有 MIN(计算排名)和 GROUPed BY 不同列的行时性能不佳

Posted

技术标签:

【中文标题】在 SQL 中选择具有 MIN(计算排名)和 GROUPed BY 不同列的行时性能不佳【英文标题】:Bad performance when SELECT rows with MIN(calculated rank) and GROUPed BY different column in SQL 【发布时间】:2016-09-08 23:06:07 【问题描述】:

我的问题与以下类似:How can I SELECT rows with MAX(Column value), DISTINCT by another column in SQL?,但我有一些具体问题。

假设我们有这样的表:

| title_id |  title   | type_id | crit_id |
+----------+----------+---------+---------+
|    1     | title_A1 |     0   |   1111  |
|    2     | title_A2 |     1   |   1111  |
|    3     | title_B1 |    50   |   2222  |  
|    4     | title_B2 |    50   |   2222  |
|    5     | title_C1 |    72   |   3333  |
|    6     | title_C2 |     1   |   3333  |
|    7     | title_C4 |     0   |   3333  |

“title_id”是唯一的并被索引,“title”和“crit_id”被索引。

因此,我希望只有按“crit_id”分组的行,保留根据 type_id 计算的自定义排名(优先级)的最小值。 对于等式type_id 的排名如下所示:

type_id = 0 - rank = 10
type_id = 50 -rank = 11
type_id = 1 - rank = 15
type_id = 72- rank = 35
etc...

最后,所有内容都应按“标题”的字母顺序排列 根据要求结果应该是:

| title_id |  title   | type_id | crit_id | rank |
+----------+----------+---------+---------+------+
|     1    | title_A1 |    0    |  1111   |  10  | 
|     3    | title_B1 |   50    |  2222   |  11  |
|     7    | title_C4 |    0    |  3333   |  10  |

我正在使用 SQLITE。我可以通过查询获得所需的结果:

SELECT *, MIN(CASE WHEN type_id = 0 THEN 10
                   WHEN type_id = 1 THEN 11
                   WHEN type_id = 50 THEN 15
                   WHEN type_id = 72 THEN 35
                   ELSE 1000 END) as rank
FROM titles WHERE ... GROUP BY crit_id ORDER BY title

这个查询的性能真的很差。在 1 000 000 条记录上,它的执行时间超过 10 秒。

这里有几个问题:

    我们有大约 60% 的记录的 type_id == 0。在这种情况下,我们执行了大约 600 000 次 MIN 和 CASE 子句。由于排名是计算出来的,我们不能在这里使用索引。我想要一些如何最小化它的执行。 对如此大量的数据使用 GROUP BY 会带来非常糟糕的性能。在阅读Selecting records holding group-wise maximum (on a unique column) 之后,我不确定它是否总是有正确的行为。希望有另一种方式可以做与 Group By 类似的事情。

PS: 我在嵌入式设备上运行这个,内存卡很慢,因此访问数据库很慢。

我不是 SQL 专家,所以如果有任何解决方案,我将不胜感激。 提前致谢。

忘了说,我们可以LIMIT 应该返回的结果数量。对于等式LIMIT 500.

【问题讨论】:

select *group by 几乎从不符合作者的意图。 是要在表中排名的 typeid 映射吗? 尝试:(1)使用其他CASE语法,您首先指定要测试的值,然后使用不同的WHEN子句,然后(2)移动MIN CASE 内部的函数,因此得到:CASE MIN(type_id) WHEN 0 THEN 10 WHEN ... 当相同的crit_id 值有不同的值时,title_id 和其他列应该给出什么?在 SQL 标准中(默认在 mysql 5.7 中应用)你需要以某种方式聚合title_id(例如min(title_id)),或者也按它分组。对于其他在功能上不是由crit_id 确定的列也是如此。 另外,在 10 秒内从存储卡中查询嵌入式设备上的 1MM 记录听起来并不“非常糟糕”。如果 I/O 或 CPU 是您的瓶颈,那么更改查询可能根本无济于事。 【参考方案1】:

也许您可以通过使用union all 拆分查询来利用(type_id, crit_id) 上的复合索引:

select crit_id, min(rank) from (
   select distinct crit_id, 10 as rank
   from titles where type_id = 0
   union all
   select distinct crit_id, 11
   from titles where type_id = 1
   union all
   select distinct crit_id, 15
   from titles where type_id = 50
   union all
   select distinct crit_id, 35
   from titles where type_id = 72
   union all
   select distinct crit_id, 1000
   from titles where type_id not in (0,1,50,72)
) t group by crit_id

【讨论】:

以上是关于在 SQL 中选择具有 MIN(计算排名)和 GROUPed BY 不同列的行时性能不佳的主要内容,如果未能解决你的问题,请参考以下文章

选择 SQL Server 中排名第二的行

sql - 为具有最低值的每个组选择单个 ID

SQL - GROUP BY和ORDER BY MIN

在 SQL Server 中使用 Dense_Rank 对具有排名的列进行排名组合

如何获取 SQL 中具有 MAX 和 MIN 值的行的 ID

计算具有特定日期的 1 和 0 的数量