2012-08-17 40 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