簡體   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