数据库行为HAVING-SUM与WHERE / DISTINCT对比GROUP BY

时间:2010-12-17 04:55:05

标签: sql mysql performance group-by distinct

假设我有一个非常大的夏季表,我们保存活动点的总和,每个用户的行,每天和活动的总和 - 对于每个类型不同的列 - 用户当天所做的:

CREATE TABLE summry_data
(
    UserID INT NOT NULL,
    ActivityDate DATE,
    t1 INT NOT NULL,
    t2 INT NOT NULL,
    t3 INT NOT NULL,
    t4 INT NOT NULL,
    PRIMARY KEY(UserID, ActivityDate)
)

每天早上我们填充前一天的数据。我们为每个用户插入一行:

INSERT summery_data
SELECT UserID, '2010-12-16'
    , SUM(IF(TypeID = 1, Points, 0))
    , SUM(IF(TypeID = 2, Points, 0))
    , SUM(IF(TypeID = 3, Points, 0))
    , SUM(IF(TypeID = 4, Points, 0))
FROM activities
WHERE ActivityDate >= '2010-12-16' AND ActivityDate < '2010-12-17'
GROUP BY UserID

表格数据如下所示:

UserID  ActivityDate   t1   t2  t3  t4
1       2010-01-01      0   82  0   0
1       2010-01-02      100 1   12  0
2       2010-01-01      0   0   0   41
2       2010-01-02      0   0   0   1
3       2010-01-02      0   0   0   106
3       2010-01-03      2   5   0   4

表格非常大(10M +行),如果我想在任何一天获得一个用户ID列表,其中包含t1,t2或t3的任何活动点(但我们不想计算t4)。我的最终结果将包括UserID 1和3。

以下哪个查询更好:

SELECT DISTINCT UserID
FROM summery_data
WHERE t1 > 0 OR t2 > 0 OR t3 > 0

VS

SELECT UserID
FROM summery_data
GROUP BY UserID
HAVING SUM(t1) > 0 OR SUM(t2) > 0 OR SUM(t3) > 0

为了理解哪个更快,我对幕后发生的事情有疑问:

  1. 一个DISTINCT查询,数据库如何确保只将1个UserID添加到结果集中,它是否检查每个UserID以查看它是否已存在于集合中?或者由于该表是由UserID集群的,只需保留一个变量 - 在扫描行时 - 添加到结果集的最后一个UserID?

  2. 在DISTINCT查询中
  3. ,一旦数据库找到与当前UserID的条件匹配的单行,它是否会停止检查where子句中的谓词,直到它到达下一个UserID?

  4. 在GROUP BY查询中,在对t1列求和时,一旦数据库找到列t1&gt;的记录。 0,它将匹配HAVING,它是否停止对当前UserID的其他t1行求和(因为谓词是> 0,这已经是真的)?或者至少不对其他列(t2和t3)求和,因为不需要它?或者数据库首先在评估HAVING子句之前首先对t1,t2和t3 求和?

  5. 注意:我使用MySql作为数据库服务器,但是我想知道Sql Server或任何其他数据库系统是否会以不同的方式工作。

    非常感谢任何帮助。

2 个答案:

答案 0 :(得分:2)

您的许多具体问题都取决于实施。

SQL查询是声明性的。他们没有指定获得答案的方法,只是表明你在寻找什么。 DMBS(数据库管理系统)确定如何将这些实施。大多数SELECT查询都包含某种类型的表扫描迭代(除非通过相关字段上的索引克服此问题),但是在查询中间没有显式循环。

我最终可以向您推荐的是,如果您对总和的实际值不感兴趣,则不要使用总和等函数。如果您想要的是在任何行中的这三个字段中的任何一个中获得具有正值的UserId,请使用DISTINCT。这至少使DMBS有机会做正确的事情并优化该查询。

索引可能有助于此查询,但实际上并非如此。索引确实有帮助的地方就是在不同的表之间进行相等连接(这可能涉及m * n时间,当你将具有m行的表等同连接到具有n的表时)。在这里,只要这3个字段中的一个是正数,您只想过滤。在最坏的情况下,你会看到每一行。 UserId上的索引可以与DISTNCT一起使用您已经决定包含的用户排除检查行。

答案 1 :(得分:2)

如果您允许(t1,t2,t3,t4)中的任何一个为负数,则您的查询相同。 请考虑以下数据:

user_id   T1   T2   T3   T4
-------  ---  ---  ---  ---
   1      -2   0    0    0
   1       2   0    0    0
   2       1   0    0    0
   2       2   0    0    0

您的第一个查询(不同)将同时包含用户1和2,因为每个用户至少有一行,其中T1值为&gt; 0

第二个查询(gby)将排除用户1,因为T1值的总和为0(即使组内的值> 0)。这也是拥有和在哪里之间差异的一个很好的例子。 (WHERE在个别行上操作; HAVING在整个组中操作)。

答案的其余部分不仅依赖于供应商,而且从SQL角度来看也完全无关紧要,因为它是最终做出选择的数据库。话虽如此,通过了解它,您可以通过以某种方式编写查询来影响优化器。

问题1

我知道数据库可以使用三种stretegies来生成不同值的列表。使用哪一个将取决于使用该操作的估计成本。

<强>分拣即可。对结果集进行排序。运行排序结果,并跟踪以前的值。如果它无法适应内存,这可能非常昂贵(缓慢)。

<强>散列即可。散列函数应用于结果集中的所有行。结果存储在中间哈希表中。这通常比排序更快。

索引行走。这与排序基本相同,但由于索引已经排序,因此跳过该步骤。

问题2

数据库是否可以按任意顺序自由评估谓词。你不能轻易自己决定。优化程序可以使用启发式或统计信息来查找最佳评估顺序。它必须遵守与我们其他人相同的布尔原则。当(t1 = 1或t2 = 2或t3 = 3)中的任何一个为真时,我们可以停止评估其他的。

问题3

没有。我的上述例子在WHERE / HAVING中解释了这一点。