2012-08-17 59 views
2

我的問題。我認爲我有一個性能查殺子查詢,但無法證明它。我第一次嘗試使用JOIN失敗。 有人可以提供更高性能的解決方案,或確認這確實可以接受,因爲它是?SQL優化:使用任何子查詢查找第一個未查看視頻

我有兩個表,一個包含todo-list(joblist)和一個跟蹤每個用戶進度(userprogress)的表。工作可以,但不能觀看視頻。 (這是一個電子學習網站。)

當觀看視頻時,它們會自動設置爲enum字段上的「已完成」。用戶也可以手動跳過視頻(狀態=「跳過」)。

表結構如下。

要獲取用戶根本沒有看過的第一個視頻(userprogress中沒有記錄)或已開始觀看(status ='started'),我正在使用此查詢。

我已經設置了用於選擇或排序的字段上的索引。但是我不確定他們是否都需要。

SELECT語句有兩個部分

  1. 的內部子查詢,我在那裏取都見過或跳過視頻
  2. 主要發言,我在那裏取第一視頻中(1)

有一個用於PHP(:email)的命名參數,以避免SQL注入。

SELECT jl.where_to_do_it FROM joblist AS jl 
INNER JOIN userprogress AS up 
ON (jl.joblistID = up.joblistID) 
WHERE jl.what_to_do = 'video' 
    AND jl.joblistID NOT IN 
     (
     SELECT injl.joblistID 
     FROM joblist AS injl 
     INNER JOIN userprogress AS inup 
     ON (injl.joblistID = inup.joblistID) 
     WHERE 
      (inup.status = 'finished' OR inup.status = 'skipped') 
      AND 
      inup.email = :email 
      AND 
      injl.what_to_do = 'video' 
    ) 
ORDER BY jl.joborder ASC 
LIMIT 0,1 

這是EXPLAIN輸出,這我需要一些幫助理解

id select_type table  type possible_keys     key   key_len ref   rows Extra 
1 PRIMARY  jl  ref PRIMARY,what_to_do   what_to_do 602  const  9  Using where; Using filesort 
1 PRIMARY  up  ref joblistID      joblistID 3  jl.joblistID 1  Using index 
2 DEP-SUB  injl eq_ref PRIMARY,what_to_do   PRIMARY  3  func   1  Using where 
2 DEP-SUB  inup eq_ref nodup,email,joblistID,status nodup  455  const,func 1  Using where 

在CREATE TABLE命令:

CREATE TABLE IF NOT EXISTS `joblist` (
    `joblistID` mediumint(10) unsigned NOT NULL AUTO_INCREMENT, 
    `what_to_do` varchar(200) COLLATE utf8_swedish_ci NOT NULL, 
    `where_to_do_it` varchar(100) COLLATE utf8_swedish_ci NOT NULL, 
    `joborder` mediumint(6) NOT NULL, 
    `track` enum('fast','slow','bonus') COLLATE utf8_swedish_ci NOT NULL DEFAULT 'slow', 
    `chapter` tinyint(11) unsigned NOT NULL COMMENT 'What book chapter it relates to', 
    PRIMARY KEY (`joblistID`), 
    KEY `nodupjobs` (`joborder`,`chapter`), 
    KEY `what_to_do` (`what_to_do`), 
    KEY `where_to_do_it` (`where_to_do_it`), 
    KEY `joborder` (`joborder`), 
    KEY `track` (`track`), 
    KEY `chapter` (`chapter`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_swedish_ci COMMENT='Suggested working order'; 


CREATE TABLE IF NOT EXISTS `userprogress` (
    `upID` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `email` varchar(150) COLLATE utf8_swedish_ci NOT NULL COMMENT 'user id', 
    `joblistID` mediumint(9) unsigned NOT NULL COMMENT 'foreign key', 
    `progressdata` varchar(300) COLLATE utf8_swedish_ci DEFAULT NULL COMMENT 'JSON object describing progress', 
    `percentage_complete` tinyint(3) unsigned DEFAULT NULL, 
    `status` enum('begun','skipped','finished') COLLATE utf8_swedish_ci DEFAULT 'begun', 
    `lastupdate` datetime NOT NULL, 
    `approved` datetime DEFAULT NULL, 
    PRIMARY KEY (`upID`), 
    UNIQUE KEY `nodup` (`email`,`joblistID`), 
    KEY `email` (`email`), 
    KEY `joblistID` (`joblistID`), 
    KEY `status` (`status`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_swedish_ci COMMENT='Keep track of what the user has done'; 
+0

我有兩個工作(和一個不工作)的答案。到目前爲止,我只有兩個人都贊成。如果一個人相對於另一個人有明顯的優勢,那麼在決定接受哪一個作爲接受的答案時,我需要幫助。 (或者也許有人會提出一個更好的解決方案。)我會等待一兩天,然後才能將答案設置爲已接受。 – itpastorn 2012-08-17 11:28:28

回答

2

是的,你是正確的。 IN和NOT IN在mysql中表現特別差。下面是一個修訂版:

SELECT jl.where_to_do_it 
FROM joblist jl INNER JOIN 
    userprogress up 
    ON (jl.joblistID = up.joblistID) 
WHERE jl.what_to_do = 'video' and 
     not exists (
      (SELECT 1 
      FROM joblist injl INNER JOIN 
       userprogress inup 
       ON (injl.joblistID = inup.joblistID) 
      WHERE (inup.status = 'finished' OR inup.status = 'skipped') and 
        inup.email = :email and 
        injl.what_to_do = 'video' and 
        ini1.joblistid = j1.joblistid 
     ) 
ORDER BY jl.joborder ASC 
LIMIT 0,1 
+0

明天我會試試這個(睡覺的時間)。感謝你的回答。如果它有效,我會獎勵你。 (我認爲它會!) – itpastorn 2012-08-17 01:14:15

+0

關閉計算機之前的快速問題。在子查詢中是否需要ON語句和最後的等號比較?他們似乎以我未經訓練的眼光去做同樣的事情。 – itpastorn 2012-08-17 01:17:31

+0

是的。最後的平等代替了「不在」的情況。其餘的是您的原始查詢。 – 2012-08-17 01:23:05

1

你看上去手忙腳亂......你的子查詢尋找與地位影片完成或跳過,然後在outter查詢找誰不具備的那些這種地位,我會改變,對於這樣的

SELECT jl.where_to_do_it FROM joblist AS jl 
INNER JOIN userprogress AS up 
ON (jl.joblistID = up.joblistID) 
WHERE jl.what_to_do = 'video' 
AND up.status <> 'finished' AND inup.status <> 'skipped' 
AND up.email = :email 
AND jl.what_to_do = 'video' 

或者,也許我理解錯了的情況,反正這個問題似乎是NOT IN(我不會建議曾經使用過這一點),而不是嘗試改變子查詢條件並做一個左參加它,並添加一個條件And SQ.joblistID IS NULL,這樣的事情

SELECT jl.where_to_do_it FROM joblist AS jl 
INNER JOIN userprogress AS up 
ON (jl.joblistID = up.joblistID) 
LEFT JOIN (
    SELECT injl.joblistID 
    FROM joblist AS injl 
    INNER JOIN userprogress AS inup 
    ON (injl.joblistID = inup.joblistID) 
    WHERE 
     (inup.status = 'finished' OR inup.status = 'skipped') 
     AND 
     inup.email = :email 
     AND 
     injl.what_to_do = 'video' 
) SQ ON jl.joblistID = SQ.joblistID 
WHERE jl.what_to_do = 'video' 
AND SQ.joblistID IS NULL  
ORDER BY jl.joborder ASC 

但我認爲第一個選項將工作...

希望它有幫助

+0

第一個建議失敗,因爲userprogress可能根本沒有匹配的記錄。它應該在狀態='開始'時以及沒有任何記錄時爲這個用戶_起作用。看第二個建議.... – itpastorn 2012-08-17 11:10:01

+0

第二個建議似乎工作,由我的初步測試來判斷。爲此,我會給你一個upvote。 – itpastorn 2012-08-17 11:20:59

+0

感謝您的支持。我認爲如果你爲'LEFT JOIN'改變'INNER JOIN',並且改變'WHERE'部分中的條件(涉及userprogress表)到連接中的ON'conditions,第一種解決方案可能會工作。如果沒有,那麼第二種解決方案的運行速度會比'NOT IN'或'NOT EXISTS'快,我總是使用這種方法。 – saul672 2012-08-17 13:15:16