2016-01-15 33 views
4

請注意,以下問題是專門針對MySQL使用複雜篩選優化SQL查詢

想象的表中調用Cars結構如下(我們可以忽略缺乏適當的鍵約束等,因爲它是不相關的我的問題):

CREATE TABLE Cars 
(
    id Integer, 
    maker_id Integer, 
    status_id Integer, 
    notes Varchar(100) 
); 

現在想象加載一些測試數據是這樣的:

INSERT INTO Cars 
(id, maker_id, status_id, notes) 
VALUES 
(1, 1001, 0, 'test1'), 
(2, 1001, 0, 'test2'), 
(3, 1001, 0, 'test3'), 
(4, 1002, 0, 'test4'), 
(5, 1002, 0, 'test5'), 
(6, 1002, 1, 'test6'), 
(7, 1002, 1, 'test7'), 
(8, 1002, 2, 'test8'), 
(9, 1003, 3, 'test9'), 
(10, 1003, 3, 'test10'), 
(11, 1003, 4, 'test11'), 
(12, 1003, 4, 'test12'), 
(13, 1003, 5, 'test13'), 
(14, 1003, 5, 'test14') 

有14個記錄,在maker_id 3個DISTINCT值(1001,1002,1003),和6個DISTINCT值在status_id(0,1,2,3,4,5)。

現在,想象一下采取DISTINCT雙(maker_id,status_id)。

SELECT DISTINCT maker_id, status_id FROM Cars; 

以下是在SQL小提琴一個示例的鏈接:http://sqlfiddle.com/#!9/cb1c7/2

這導致以下記錄(maker_idstatus_id):

  • (1001,0)
  • ( 1002,0)
  • (1002,1)
  • (1002,2)
  • (1003,3)
  • (1003,4)
  • (1003,5)

的爲我需要返回邏輯如下:

如果一個給定maker_id值(例如1001)對於其對應的DISTINCTmaker_id,status_id)對只有1個不同記錄,只需將其返回即可。在這個例子中:(1001,0)。

如果給定maker_id值具有大於1對於其相應DISTINCTmaker_idstatus_id)對不同的記錄,返回所有他們的除所述一個與status_id值的0。在本例中:(1002 ,1),(1002,2),(1003,3),(1003,4)和(1003,5)。

請注意,我們遺漏了(1002,0)。

任何人都可以想到一個conciser /更有效率(就運行時而言)寫這個查詢的方式嗎?在現實世界中,我的桌子有數百萬條記錄。

我想出了以下內容:

SELECT 
    subq.maker_id, 
    subq.status_id 
FROM 
(
    SELECT DISTINCT 
    maker_id, 
    status_id, 
    (SELECT COUNT(*) FROM Cars WHERE maker_id = c.maker_id AND status_id != 0 GROUP BY maker_id) AS counter 
    FROM Cars AS c 
) AS subq 

WHERE 
    subq.counter IS NULL 
    OR (subq.counter IS NOT NULL AND subq.status_id != 0) 
; 

這裏是SQL小提琴一個例子:http://sqlfiddle.com/#!9/cb1c7/3

+1

1.你有什麼指標? 2.首先分別選擇兩個案例。 – philipxy

+0

@philipxy感謝您的回覆! 1)我們沒有indeces(並且不能創建它們)2)你的意思是做兩個子查詢和UNION結果嗎? – cuddlyhugbear

+0

兩種選擇和UNION是我現在唯一能想到的其他方式...... –

回答

2

有幾個查詢模式,可以返回指定的結果。有些人會比其他人看起來更復雜。有可能是性能的差異。

一個巨大的一套執行GROUP BY操作可能是昂貴的(在資源方面和使用時間,特別是如果MySQL不能使用索引來優化該操作。(使用GROUP BY操作獲得的一種方式每個maker_idstatus_id計數。)

而且相關子查詢可能是昂貴的,當它們被重複執行。我通常只看到了相關子查詢的性能更好,當他們需要執行的次數是有限的。

我認爲獲得好表現的最好方法就是像這樣:

未測試

SELECT c.maker_id 
     , c.status_id 
    FROM Cars c 
    WHERE c.status_id > 0 

UNION ALL 

SELECT d.maker_id 
     , d.status_id 
    FROM Cars d 
    LEFT 
    JOIN Cars e 
    ON e.maker_id = d.maker_id 
    AND e.status_id > 0 
    WHERE e.maker_id IS NULL 
    AND d.status_id = 0 

至於是否這比其他查詢方式更有效或更簡潔,我們就需要測試。

但是對於使用此查詢獲得良好性能的任何鏡頭,我們將需要一個索引。

.. ON Cars (maker_id, status_id) 

我們預計EXPLAIN輸出將在Extra列中顯示「使用索引」。我們並不期待「使用filesort」。

這種方法的一個巨大缺點是,這將有效地通過表(或索引)兩次。

第一個SELECT非常簡單...讓我看看status_id是不是零的所有行。我們需要所有這些行。有可能是索引例如

... ON Cars (status_id, maker_id) 

可能對該查詢有益。但是,如果我們返回大部分表格,我會打賭美元甜甜圈,對其他索引的全面掃描將會更快或更快。

第二個SELECT使用反連接模式。這樣做的目的是讓所有具有status_id的行等於零,並從該集合中「過濾出」存在另一行的任何行,對於同一maker_id而不是零的status_id

我們做外部濾波連接操作(LEFT JOIN)與status_id=0返回所有行,與任何及所有匹配的行一起。 技巧WHERE子句中的謂詞,它過濾掉所有具有匹配的行。所以我們留下的是沒有找到匹配的行。也就是maker_id其中只有一個status_id=0行。

我們可以使用NOT EXISTS謂詞而不是反連接獲得等效結果。但根據我的經驗,有時表現並不好。我們可以重新寫第二SELECT(繼UNION ALL操作)

SELECT d.maker_id 
     , d.status_id 
    FROM Cars d 
    WHERE d.status_id = 0 
    AND NOT EXISTS 
     (SELECT 1 
      FROM Cars e 
      WHERE e.maker_id = d.maker_id 
      AND e.status_id > 0 
     ) 

這查詢的性能將是依賴於一個合適的索引就像反連接的。

重要提示:請勿而不要忽略關鍵字ALL。一個UNION ALL操作只是連接兩個查詢的結果。如果我們省略了ALL關鍵字,那麼我們要求MySQL執行「排序唯一」操作以消除重複的行。

注意:UNION ALL而不是OR條件的原因是我通常用UNION ALL得到了更好的查詢計劃。當謂詞處於不同的列和條件時,MySQL優化器似乎不太適用於OR,並且謂詞可以用於「驅動」執行計劃。通過將UNION ALL分成兩個查詢,我們通常可以爲這兩個部分制定一個好的計劃。

+0

什麼是一個非常深思熟慮的職位;謝謝!你是對的,UNION ALL方法肯定會提高性能。現在在我的實際(大)數據集上查看查詢的詳細信息。 – cuddlyhugbear

+0

@cuddlyhugbear:我在另一個答案中增加了另一種可能的方法。如果表上有* no *索引,並且無法創建索引(無論出於何種原因),則可能在另一個答案中的查詢可能會更快。 – spencer7593

+0

在這個答案中,「DISTINCT」或「GROUP BY」在哪裏? – Arth

1

此查詢將幫助:)

select 
    distinct c1.maker_id, c1.status_id 
from 
    Cars AS c1 
where 
    c1.status_id!=0 
    or c1.maker_id not in (
     select distinct c2.maker_id 
     from Cars AS c2 
     where c2.status_id!=0 
    ) 
+0

工作方式類似於感謝您的幫助!感謝您的幫助! – cuddlyhugbear

1

作爲一個完全不同的方法,以我的第一個答案......

給出一個不可能的情況...在表上沒有索引,也沒有可能性創建一個索引......我們基本上只剩下兩個非常糟糕的選擇:一個巨大的集合和一個通過它的傳遞,或者通過一個巨大的表格進行大量傳遞。

兩個邪惡中較小的一個似乎是這樣的。我們無法承受索引,但我們可以負擔得起的週期,以便在我們的查詢中即時創建索引。還有一個磁盤io到/ tmp。 SSD上安裝了散熱片,對吧?

我們將對整個翻轉套件進行排序,並運行幾次。

SELECT t.maker_id 
     , t.status_id 
    FROM (SELECT IF(s.status_id=0 AND [email protected]_maker,NULL,s.status_id) AS status_id 
       , @p_maker := s.maker_id AS maker_id 
      FROM (SELECT @p_maker := NULL) i 
      CROSS 
      JOIN (SELECT c.maker_id 
         , c.status_id 
         FROM Cars c 
        GROUP BY c.maker_id DESC, c.status_id DESC 
        ORDER BY c.maker_id DESC, c.status_id DESC 
       ) s 
     ) t 
    WHERE t.status_id IS NOT NULL 
1

我不能完全肯定的表現..但我喜歡的風采:

SELECT maker_id, 
     status_id 
    FROM cars 
    WHERE status_id != 0 
GROUP BY maker_id, status_id 
    UNION ALL 
    SELECT maker_id, 
     MAX(status_id) max_status_id 
    FROM cars 
GROUP BY maker_id 
    HAVING max_status_id = 0 
+1

@ spencer7593謝謝您,但是在樣本數據中有模糊不清的內容,並且明確提及了不同的記錄,並在要求中提出了相應的要求 – Arth

+1

您是完全正確的,我完全錯過了消除重複元組,一個非常聰明的方法,我喜歡它。 – spencer7593