有没有办法让SQL NOT IN查询更快?

时间:2015-04-10 06:49:37

标签: sql postgresql

我想获取每天记录到数据库并且从未出现在日志中的唯一手机条目数。我认为这是一个微不足道的查询,但是当查询在一个有大约900K条目的表上花了10分钟时,我感到震惊。选择示例是获取2015年4月9日记录且以前从未记录过的唯一手机的数量。就像在特定的一天让谁成为真正的新访问者一样。 SQL Fiddle Link

SELECT COUNT(DISTINCT mobile_number)
FROM log_entries
WHERE created_at BETWEEN '2015-04-09 00:00:00'
    AND '2015-04-09 23:59:59'
    AND mobile_number NOT IN (
        SELECT mobile_number
        FROM log_entries
        WHERE created_at < '2015-04-09 00:00:00'
        )

我在created_atmobile_number上有个别索引。

有没有办法让它更快?我看到了一个非常相似的问题here on SO,但这是在使用两个表格。

6 个答案:

答案 0 :(得分:4)

NOT IN可以重写为NOT EXISTS查询,这通常更快(不幸的是,Postgres优化器不够智能,无法检测到这一点)。

SELECT COUNT(DISTINCT l1.mobile_number) 
FROM log_entries as l1
WHERE l1.created_at >= '2015-04-09 00:00:00' 
  AND l1.created_at <= '2015-04-09 23:59:59' 
  AND NOT EXISTS (SELECT * 
                  FROM log_entries l2
                  WHERE l2.created_at < '2015-04-09 00:00:00'
                    AND l2.mobile_number = l1.mobile_number);

(mobile_number, created_at)上的索引应进一步改善效果。


附注:created_at <= '2015-04-09 23:59:59'不包含小数秒的行,例如: 2015-04-09 23:59:59.789。处理时间戳时,最好在“第二天”使用“低于”而不是在相关日期使用“低于或等于”。

更好地使用:created_at < '2015-04-10 00:00:00'而不是在那一天以小数秒“捕获”行。

答案 1 :(得分:1)

我倾向于建议将NOT IN转换为左反连接(即左连接仅保留的左侧行与右侧匹配)。在这种情况下,它有点复杂,因为它是同一个表的两个不同范围的自联接,所以你真正加入了两个子查询:

SELECT COUNT(n.mobile_number)
FROM (
  SELECT DISTINCT mobile_number
  FROM log_entries
  WHERE created_at BETWEEN '2015-04-09 00:00:00' AND '2015-04-09 23:59:59'
) n
LEFT OUTER JOIN (
  SELECT DISTINCT mobile_number
  FROM log_entries
  WHERE created_at < '2015-04-09 00:00:00'
) o ON (n.mobile_number = o.mobile_number)
WHERE o.mobile_number IS NULL;

与@a_horse_with_no_name提供的典型NOT EXISTS表述相比,我对此表现感兴趣。

请注意,我还将DISTINCT检查按下了子查询。

您的查询似乎是&#34;&lt; time range&gt;&#34;中有多少新看到的手机号码。正确?

答案 2 :(得分:0)

不是WHERE created_at >= '2015-04-09 00:00:00' AND created_at <= '2015-04-09 23:59:59'正在处理WHERE created_at&lt; '2015-04-09 00:00:00'?我在这里错过了什么吗?

答案 3 :(得分:0)

NOT IN并不快。并且您的子查询返回了许多重复记录。也许你应该将唯一的数字放到专用表中(因为GROUP BY也会很慢)。

答案 4 :(得分:0)

尝试这样的事情:

SELECT mobile_number, min(created_at)
FROM log_entries
GROUP BY mobile_number
HAVING min(created_at) between '2015-04-09 00:00:00' and '2015-04-09 23:59:59'

添加覆盖mobile_number和created_at的单个索引会略微提高性能,假设表中还有其他列,因为只需要扫描该索引。

答案 5 :(得分:0)

尝试使用WITH(如果你的sql支持它)。这是帮助(postgres):http://www.postgresql.org/docs/current/static/queries-with.html

你的查询应该是这样的:

WITH  b as
(SELECT distinct mobile_number
        FROM log_entries
        WHERE created_at < '2015-04-09 00:00:00') 
SELECT COUNT(DISTINCT a.mobile_number)
FROM log_entries a   
left join b using(mobile_number)
where created_at >= '2015-04-09 00:00:00'
   AND created_at <= '2015-04-09 23:59:59' and b.mobile_number is null;