【问题标题】:How to rewrite a NOT IN subquery as join如何将 NOT IN 子查询重写为连接
【发布时间】:2012-05-22 11:33:53
【问题描述】:

假设 MySQL 中的以下表格描述了文件夹中包含的文档。

mysql> select * from folder;
+----+----------------+
| ID | PATH           |
+----+----------------+
|  1 | matches/1      |
|  2 | matches/2      |
|  3 | shared/3       |
|  4 | no/match/4     |
|  5 | unreferenced/5 |
+----+----------------+


mysql> select * from DOC;
+----+------+------------+
| ID | F_ID | DATE       |
+----+------+------------+
|  1 |    1 | 2000-01-01 |
|  2 |    2 | 2000-01-02 |
|  3 |    2 | 2000-01-03 |
|  4 |    3 | 2000-01-04 |
|  5 |    3 | 2000-01-05 |
|  6 |    3 | 2000-01-06 |
|  7 |    4 | 2000-01-07 |
|  8 |    4 | 2000-01-08 |
|  9 |    4 | 2000-01-09 |
| 10 |    4 | 2000-01-10 |
+----+------+------------+

列 ID 是主键,表 DOC 的列 F_ID 是引用表 FOLDER 主键的非空外键。通过在 where 子句中使用文档的“日期”,我想查找哪些文件夹仅包含选定的文档。对于早于 2000-01-05 的文档,可以这样写:

SELECT DISTINCT d1.F_ID 
FROM DOC d1 
WHERE d1.DATE < '2000-01-05' 
AND d1.F_ID NOT IN (
    SELECT d2.F_ID 
    FROM DOC d2 WHERE NOT (d2.DATE < '2000-01-05')
);

它正确地返回“1”和“2”。通过阅读 http://dev.mysql.com/doc/refman/5.5/en/rewriting-subqueries.html 如果将子查询替换为连接,则可以提高大表的性能。我已经找到了与 NOT IN 和 JOINS 相关的问题,但不完全是我想要的。那么,有什么想法可以用连接来编写吗?

【问题讨论】:

    标签: mysql sql join subquery


    【解决方案1】:
    select distinct d1.F_ID
    from DOC d1
    left outer join (
        select F_ID
        from DOC 
        where date >= '2000-01-05'
    ) d2 on d1.F_ID = d2.F_ID
    where d1.date < '2000-01-05' 
        and d2.F_ID is null
    

    【讨论】:

    • 正确且超快速的响应!虽然我没有问,但我想我不能避免重复'2000-01-05'。
    • 子查询连接会比WHERE上的子查询执行得更好吗?
    【解决方案2】:

    一般的答案是:

    select t.*
    from t
    where t.id not in (select id from s)
    

    可以改写为:

    select t.*
    from t left outer join
         (select distinct id from s) s
         on t.id = s.id
    where s.id is null
    

    我认为您可以将此应用于您的情况。

    【讨论】:

      【解决方案3】:

      如果我正确理解您的问题,您想找到代表仅包含“2000-01-05”之前文档的文件夹的 F_ID,那么只需

      SELECT F_ID 
      FROM DOC 
      GROUP BY F_ID 
      HAVING MAX(DATE) < '2000-01-05'
      

      【讨论】:

      • 是的,这就是我所要求的,这确实是一个更简单且正确的查询。但是,实际情况是 DOC 表有更多列,并且 where 子句将以编程方式生成。在我看来,动态添加“where”子句比“have”更容易,但我会检查大表的性能并试一试。谢谢!
      • 欢迎您,不过我也愿意阅读一下dev.mysql.com/doc/refman/5.5/en/execution-plan-information.html。另外,除了您在此处公开的子集之外,我不知道您的架构的外观。但我倾向于遵循“如果我需要子查询,我应该重新审视我的数据模型”的经验法则。换句话说,也许规范化你的模型可能会使事情更清洁/更容易实现:)
      【解决方案4】:

      示例表和插入语句

      CREATE TABLE `tleft` (
        `id` int(2) NOT NULL,
        `name` varchar(100) DEFAULT NULL,
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8
      
      CREATE TABLE `tright` (
        `id` int(2) NOT NULL,
        `t_left_id` int(2) DEFAULT NULL,
        `description` varchar(100) DEFAULT NULL,
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8
      
      
      
      INSERT INTO `tleft` (`id`, `name`)
      VALUES
          (1, 'henry'),
          (2, 'steve'),
          (3, 'jeff'),
          (4, 'richards'),
          (5, 'elon');
      
      
      
      INSERT INTO `tright` (`id`, `t_left_id`, `description`)
      VALUES
          (1, 1, 'sample'),
          (2, 2, 'sample');
      

      左加入: SELECT l.id,l.name FROM tleft l LEFT JOIN tright r ON l.id = r.t_left_id ;

      返回 ID:1、2、3、4、5

      右加入: SELECT l.id,l.name FROM tleft l RIGHT JOIN tright r ON l.id = r.t_left_id ;

      返回 ID:1,2

      子查询不正确: select id from tleft where id not in ( select t_left_id from tright);

      返回 ID:3,4,5

      上述子查询的等价连接:

      SELECT l.id,l.name FROM tleft l LEFT JOIN tright r ON l.id = r.t_left_id WHERE r.t_left_id IS NULL;
      

      AND 子句将在 JOIN 期间应用,WHERE 子句将在 JOIN 之后应用。

      示例: SELECT l.id,l.name FROM tleft l LEFT JOIN tright r ON l.id = r.t_left_id AND r.description ='hello' WHERE r.t_left_id IS NULL ;

      希望对你有帮助

      【讨论】:

      • 感谢您对minimal reproducible example 采取行动。特别是表格格式的初始化。如果您的查询是正确缩进的代码块,那就更好了。 PS这个问题很差&(显然)重复(即使发布)&应该关闭。此外,目前尚不清楚该答案对其他答案或重复答案的添加是什么,并且它对问题没有太多特定性,因此(根据向下投票箭头鼠标悬停文本)它不是很“有用”。请参阅How to Ask & How to Answer
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-31
      • 2023-03-20
      • 1970-01-01
      • 1970-01-01
      • 2014-03-03
      • 1970-01-01
      相关资源
      最近更新 更多