繁体   English   中英

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

[英]Is there a way to make an SQL NOT IN query faster?

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

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上有单独的索引。

有没有办法让它更快? 在这里看到了一个非常类似的问题但这是在使用两个表。

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'而不是在那一天用“小数秒”“捕获”行。

我倾向于建议将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检查下推到子查询中。

您的查询似乎是“<时间范围>中有多少新看到的手机号码”。 对?

是不是WHERE created_at >= '2015-04-09 00:00:00' AND created_at <= '2015-04-09 23:59:59' '2015-04-09 00:00 WHERE created_at >= '2015-04-09 00:00:00' AND created_at <= '2015-04-09 23:59:59'照顾WHERE created_at <'2015-04-09 00:00 :00' ? 我在这里错过了什么吗?

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

尝试这样的事情:

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的单个索引将略微提高性能,因为只需要扫描该索引。

尝试使用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;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM