2014-01-17 56 views
0

我創造了這個SQL,以便找到那些沒有下令X天的客戶的客戶。優化SQL:未下令X天

它返回結果集,所以這篇文章主要是剛拿到就可以了第二種意見,和可能的優化。

SELECT o.order_id, 
     o.order_status, 
     o.order_created, 
     o.user_id, 
     i.identity_firstname, 
     i.identity_email, 

    (SELECT COUNT(*) 
    FROM orders o2 
    WHERE o2.user_id=o.user_id 
    AND o2.order_status=1) AS order_count, 

    (SELECT o4.order_created 
    FROM orders o4 
    WHERE o4.user_id=o.user_id 
    AND o4.order_status=1 
    ORDER BY o4.order_created DESC LIMIT 1) AS last_order 
FROM orders o 
INNER JOIN user_identities ui ON o.user_id=ui.user_id 
INNER JOIN identities i ON ui.identity_id=i.identity_id 
    AND i.identity_email!='' 
INNER JOIN subscribers s ON i.identity_id=s.identity_id 
    AND s.subscriber_status=1 
    AND s.subsriber_type=e 
    AND s.subscription_id=1 
WHERE DATE(o.order_created) = "2013-12-14" 
    AND o.order_status=1 
    AND o.user_id NOT IN 
    (SELECT o3.user_id 
    FROM orders o3 
    WHERE o3.user_id=o.user_id 
     AND o3.order_status=1 
     AND DATE(o3.order_created) > "2013-12-14") 

你們能否發現這個SQL有任何潛在的問題?日期是動態插入的。

,我把生產的最後SQL,將基本上只包括o.order_id,i.identity_id和o.order_count - 這ORDER_COUNT需要是正確的。其他選定的字段和'last_order'子查詢將不包含在內,它僅用於測試。

這應該給我,有他們在那個特定的最後一天爲了用戶的列表,並且是一個通訊訂戶。我特別懷疑WHERE子句中NOT IN部分的正確性以及order_count子查詢的正確性。

+4

>你們可以發現這個SQL有任何潛在的問題嗎?也許,但個人而言,我寧願從頭開始使用一組適當的DDL(和/或一個sqlfiddle)與預期的結果一起。 – Strawberry

回答

2

有幾個問題:

A.使用可轉位上列功能

你是通過比較DATE(order_created)與某些常數來搜索訂單。這是一個可怕的想法,因爲一)DATE()函數爲每個行(CPU執行)和b)在列的數據庫不能使用索引(假設存在)

B.使用WHERE ID NOT IN (...)

使用NOT IN (...)幾乎總是一個壞主意,因爲優化通常有這種結構的麻煩,並經常得到該計劃是錯誤的。你幾乎總是可以表達它作爲外部用WHERE條件過濾器使用IS NULL條件的接柱偏出加入(並增加了不需要DISTINCT的附帶好處,因爲只有過一個小姐回)

Ç 。離開那個加入過濾掉行太晚

早期的大部分,你可以通過不使加入更好的屏蔽掉行。您可以通過加入不太可能匹配連接表列表中的較早前的表,並通過將非關鍵條件放入連接而非where子句來儘早排除行。 一些無論如何,但我經常發現它們不會

D.避免像瘟疫相關的子查詢!

您有幾個相關的子查詢 - 爲執行的主表中的每行。這真是一個非常糟糕的主意。有時候優化器有時可以將它們製作成一個連接,但爲什麼依賴(希望)。大多數相關的子查詢可以表示爲連接;你的例子也不例外。

根據以上考慮,也有一些具體的變化:

  • O2和O4是相同加入,讓O4可完全可以省去 - 只需使用氧氣轉換後的加入
  • DATE(order_created) = "2013-12-14"應該寫成order_created between "2013-12-14 00:00:00" and "2013-12-14 23:59:59"

這個查詢應該是你想要什麼:

SELECT 
    o.order_id, 
    o.order_status, 
    o.order_created, 
    o.user_id, 
    i.identity_firstname, 
    i.identity_email, 
    count(o2.user_id) AS order_count, 
    max(o2.order_created) AS last_order 
FROM orders o 
LEFT JOIN orders o2 ON o2.user_id = o.user_id AND o2.order_status=1 
LEFT JOIN orders o3 ON o3.user_id = o.user_id 
    AND o3.order_status=1 
    AND o3.order_created >= "2013-12-15 00:00:00" 
JOIN user_identities ui ON o.user_id=ui.user_id 
JOIN identities i ON ui.identity_id=i.identity_id AND i.identity_email != '' 
JOIN subscribers s ON i.identity_id=s.identity_id 
    AND s.subscriber_status=1 
    AND s.subsriber_type=e 
    AND s.subscription_id=1 
WHERE o.order_created between "2013-12-14 00:00:00" and "2013-12-14 23:59:59" 
AND o.order_status=1 
AND o3.order_created IS NULL -- This gets only missed joins on o3 
GROUP BY 
    o.order_id, 
    o.order_status, 
    o.order_created, 
    o.user_id, 
    i.identity_firstname, 
    i.identity_email; 

最後一行是使用LEFT JOIN

免責聲明如何實現一樣NOT IN (...):未測試。

+0

Your查詢有一個(可能的)語法錯誤 - 'BETWEEN'沒有匹配的結束值(當然,除非它決定嘗試'AND o.order_status = 1 ...)。[不是你應該使用BETWEEN和日期/時間值反正](http://sqlblog.com/blogs/aaron_bertrand/archive/2011/10/19/what-do-between-and-the-devil-have-in-common.aspx)(或確實,任何非整數計數值) –

+0

@ Clockwork-Muse該死的!我把這兩者之間的評論,而不是代碼。現在修復.Thx – Bohemian

+0

感謝您解剖我的SQL查詢,並給出了一個明確的錯誤描述我已經做了:-)我測試了你的查詢,它給出了和我最初的SQL完全相同的結果 - 太棒了!並感謝您放棄它而不要求表結構!而且,您使用BETWEEN進行的查詢實際上可以幫助我爲我的下一個自動回覆提供幫助,該回復每週只發送兩次,並收集在一定時間內收到最後訂單的用戶! – Phliplip

0

因爲你還沒有發佈任何聲明表或示例數據,但你的查詢有3個相關子查詢,這很可能使其表現不佳的結果真的不能評論(OK,其中之一是last_order和僅用於測試)。

消除相關聯的子查詢,並與替換它們加入會給這樣的事情: -

SELECT o.order_id, 
     o.order_status, 
     o.order_created, 
     o.user_id, 
     i.identity_firstname, 
     i.identity_email, 
     Sub1.order_count, 
     Sub2.last_order 
FROM orders o 
INNER JOIN user_identities ui ON o.user_id=ui.user_id 
INNER JOIN identities i ON ui.identity_id=i.identity_id 
    AND i.identity_email!='' 
INNER JOIN subscribers s ON i.identity_id=s.identity_id 
    AND s.subscriber_status=1 
    AND s.subsriber_type=e 
    AND s.subscription_id=1 
LEFT OUTER JOIN 
(
    SELECT user_id, COUNT(*) AS order_count 
    FROM orders 
    WHERE order_status=1 
    GROUP BY user_id 
) Sub1 
ON o.user_id = Sub1.user_id 
LEFT OUTER JOIN 
(
    SELECT user_id, MAX(order_created) as last_order 
    FROM orders 
    WHERE order_status=1 
    GROUP BY user_id 
) AS Sub2 
ON o.user_id = Sub2.user_id 
LEFT OUTER JOIN 
(
    SELECT DISTINCT user_id 
    FROM orders 
    WHERE order_status=1 
    AND DATE(order_created) > "2013-12-14" 
) Sub3 
ON o.user_id = Sub3.user_id 
WHERE DATE(o.order_created) = "2013-12-14" 
    AND o.order_status=1 
    AND Sub3.user_id IS NULL 
+0

在'order_created'上使用'DATE(...)'會導致它忽略任何索引 - 使用時間戳值。您錯過了檢索訂單數量和最後訂單日期的子查詢可以合併爲一個(儘管我起初也是這樣)。通常'...... JOIN(SELECT DISTINCT ...'可以轉化爲'WHERE EXISTS'(或'NOT',在這種情況下),儘管我不確定這是否會給MySQL帶來性能優勢。 –