【问题标题】:Optimal single query for checking if record exists in either/or table用于检查记录是否存在于/或表中的最佳单一查询
【发布时间】:2011-11-23 20:56:31
【问题描述】:

这是一个有点奇怪的问题,所以最好的提问方式是举个例子。我有一份客户名单。我想获得在CourseHistory 表或Access 表(或两者)中有相应条目的任何客户。

我想要获取这些客户的最佳单一查询(无子查询)。我想出了

SELECT
   c.cusid
FROM
   Customers c
   CROSS JOIN Realms r
   LEFT JOIN Course.CourseHistory ch ON (c.cusid = ch.cusid)
   LEFT JOIN Access a ON (c.cusid = a.cusid AND r.realmid = a.realmid)
WHERE
   realmname = 'Course'
   AND COALESCE(chid, accid)

这可行,但速度很慢,可能是因为它必须对Customers 进行全面扫描。由于 CourseHistoryAccess 可以为 null 并且结果仍然有效,因此它们必须保持连接。有没有更正确的方法来做这个查询?

【问题讨论】:

    标签: mysql


    【解决方案1】:

    摆脱那个 CROSS JOIN 到 Realms 和 INNER JOIN 那个表到 Access。

    SELECT
       c.cusid
    FROM
       Customers c
       LEFT JOIN Course.CourseHistory ch ON (c.cusid = ch.cusid)
       LEFT JOIN Access a 
           INNER JOIN realms r
               ON a.realmid = r.realmid
                   AND r.realmname = 'Course'
           ON c.cusid = a.cusid
    WHERE
       COALESCE(chid, accid)
    

    【讨论】:

    • 这会返回不同的结果。我还认为在联接列表中的外部联接之后进行内部联接是不正确的。不过,它更快。
    • 具体来说,这只会返回具有Access ID 的结果,因为Access 上的 LEFT JOIN 被 Realm 上的内部连接(不能返回空行)无效。跨度>
    • @tandu 我认为你误解了这个查询。客户将与由 Access 和 Realms 之间的内部连接形成的派生表保持连接。这不会否定左连接。
    • 我手动运行了他的查询,结果确实更少。我知道的。我的第二个回答主要是假设。
    • 嘿乔,您的回答向我展示了在我的回答中使用的正确关系。 +1 以避免侵犯版权。
    【解决方案2】:

    这是您的原始查询

    SELECT  
       c.cusid  
    FROM  
       Customers c  
       CROSS JOIN Realms r  
       LEFT JOIN Course.CourseHistory ch ON (c.cusid = ch.cusid)  
       LEFT JOIN Access a ON (c.cusid = a.cusid AND r.realmid = a.realmid)  
    WHERE  
       realmname = 'Course'  
       AND COALESCE(chid, accid)  
    ; 
    

    从你们那里,我现在意识到了这一点

    • Realm 可以访问 Access
    • 访问覆盖客户,但您不需要
    • 可以通过 cusid 访问 Course.CourseHistory

    给定这条路径,这里是重构的查询

    SELECT r.cusid
    FROM
    (SELECT realmid FROM Realms WHERE realmname = 'Course') r
    LEFT JOIN
    (SELECT realmid,cusid,accid FROM Access) a ON r.realmid=a.realmid
    LEFT JOIN
    (SELECT cusid FROM Course.CourseHistory) ch ON a.cusid=ch.cusid
    WHERE COALESCE(chid, accid);
    

    您将需要以下索引

    ALTER TABLE Realms ADD INDEX realmname_realmid_ndx (realmname,realmid);
    ALTER TABLE Access ADD INDEX realmid_cusid_accid_ndx (realmid,cusid,accid);
    

    试试看!!!

    【讨论】:

    • 领域没有 cusid 列。
    • @RolandoMySQLDBA,对不起,但我想知道:当您建议时,您真的是指(SELECT realmid,cusid,accid FROM Access) a,即从表中选择所有ID到内存中的未索引空间中,并应用条件之后通过检查每一行?
    • @newtover 是的,我愿意。该索引将使查询仅从索引中获取所需的数据,并且永远不会触及 Access 表。在这方面,它作为一个覆盖索引。
    • 每个子查询都尽可能小。键的笛卡尔连接应该比整个表的笛卡尔连接更快。制作索引以支持子查询是此实例中的关键。我以前做过:stackoverflow.com/a/6023217/491757
    【解决方案3】:

    您当前的查询以笛卡尔积Customers x Realms 开头,应该返回太多重复项。如果Course.CourseHistoryAccess 上的每个客户有多个记录,则还应该有很多重复项。

    下面的查询应该更有效。查询中的UNION 应用隐式DISTINCT,即所有ID 都是唯一的。此外,查询允许 MySQL 优化器从表统计信息中受益,并以最佳顺序排列连接。

    SELECT cuid
    FROM Customers c
    JOIN Course.CourseHistory ch USING (cuid)
    UNION
    SELECT cuid
    FROM Realms r
    JOIN Access a USING (realmid)
    JOIN Customers c USING (cuid)
    WHERE r.realmname = 'Course';
    

    【讨论】:

    • MySQL 中的 CROSS JOINs 不需要显式连接条件。如果他们这样做了,至少 MySQL 永远不会抱怨它。 UNION 不是算作两个单独的查询吗?笛卡尔积实际上是目标..通常它会受到其他表的键的限制,但由于它们都可以是 NULL 它不是。
    • @tandu,它确实需要一个明确的条件。它在文档 (dev.mysql.com/doc/refman/5.1/en/join.html) 中有说明并且易于检查,因此不会产生警告。我不明白你关于计算查询的观点,你如何计算它们以及你如何衡量什么更好?如果查询返回正确的结果并且运行速度足够快,这不正是您所需要的吗?
    • 我在文档中没有看到任何说 CROSS JOIN 需要明确的连接条件的内容。实际上,CROSS JOININNER JOIN 都不需要它。
    • @tandu,您对笛卡尔积的评论毫无意义。如果有意,则可以通过从查询中完全删除 Realms 表来获得相同的结果(尽管重复次数较少)。
    • @tandu,好吧,我的意思是你几乎不需要笛卡尔积。至少,这种情况下似乎不需要它。
    【解决方案4】:

    您似乎希望从 CourseHistory 和 Access(在“Course”领​​域内)收集所有客户 ID,并检索与这些 ID 匹配的客户。这是一个正是这样做的查询。

    SELECT
        c.cusid
    FROM
        Customers c
        INNER JOIN (
            SELECT ch.cusid FROM Course.CourseHistory AS ch
            UNION
            SELECT a.cusid FROM Access a
            INNER JOIN Realms r ON r.realmid = a.realmid AND r.realmname = 'Course'
        ) AS ids ON c.cusid = ids.cusid
    

    如果你真的只需要客户 ID,并且客户表保证包含所有现有的客户 ID,那么你可以省略外部选择,而只使用内部 UNION。

    如果您真的需要一个查询,那么您将不得不接受更糟糕的事情。这至少是对原始查询的改进:

    SELECT
        c.cusid
    FROM
        Customers c
        LEFT JOIN Course.CourseHistory ch ON (c.cusid = ch.cusid)
        LEFT JOIN Access a ON (c.cusid = a.cusid)
        LEFT JOIN Realms r ON (r.realmid = a.realmid AND realmname = 'Course')
    WHERE
        AND chid IS NOT NULL OR realmname IS NOT NULL
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多