MySQL LEFT JOIN 两列之一

MySQL LEFT JOIN on either of two columns

我想在任意两列之一上加入两个 table。我不知道哪一个会匹配。当从列表 table 中找到匹配行时,我想要来自 feed_REIN 的所有记录和附加数据。我认为 LEFT JOIN 会起作用。

当我只有一个条件(即 RETS.list_number = listings.CVMLS)时,它运行正常。一旦我在 LEFT JOIN 中添加了额外的 OR 条件,它就需要永远完成查询。

feed_RETS 有大约 125k 条记录,listings 有大约 12k 条记录。

我做错了什么?

SELECT 

COUNT(*)

FROM feed_RETS AS RETS LEFT JOIN listings ON listings.statusID IN (1,2,3) AND (RETS.list_number = listings.CVMLS OR RETS.list_number = listings.REIN) 

WHERE RETS.public_status NOT LIKE '%Sold%'

试试这个:

SELECT 
COUNT(*)
FROM feed_RETS AS RETS 
LEFT JOIN listings on
    RETS.list_number = listings.CVMLS 
    OR RETS.list_number = listings.REIN
WHERE public_status NOT LIKE '%Sold%' and statusID IN (1,2,3) 

查看解释计划以更好地理解带有 OR 子句的查询。此查询唯一可能的连接算法很可能是嵌套循环连接,这对您的表来说效率非常低。您可以将查询重写为:

SELECT 
COUNT(*)
FROM(
(SELCT * FROM feed_RETS AS RETS LEFT JOIN listings ON statusID IN (1,2,3) AND RETS.list_number = listings.CVMLS WHERE public_status NOT LIKE '%Sold%')
UNION
(SELCT * FROM feed_RETS AS RETS LEFT JOIN listings ON statusID IN (1,2,3) AND RETS.list_number = listings.REIN WHERE public_status NOT LIKE '%Sold%'))T

如果您打算 EITHER cvmls OR rein(异或),并且应用程序确保其中一个可以为真但不能同时为真,那么逻辑上 LEFT JOIN 将是不必要的,并且查询将始终产生相同的结果行数。但是,如果两者都可以在同一行上匹配,那么请考虑您是想要 COUNT(*) [所有可能的匹配项,包括连接左侧的重复项] 还是 COUNT(DISTINCT r.list_number) [仅不同的列表]:

-- Query 1
SELECT COUNT(*)
  FROM feed_RETS AS RETS LEFT JOIN listings 
                                ON listings.statusID IN (1,2,3)
                               AND (    RETS.list_number = listings.CVMLS 
                                     OR RETS.list_number = listings.REIN
                                   ) 
 WHERE RETS.public_status NOT LIKE '%Sold%'
;

-- Query 2 - Is the count the same?
SELECT COUNT(*)
  FROM feed_RETS 
 WHERE public_status NOT LIKE '%Sold%'
;

如果查询 2 returns 的计数不同,请注意列表 table 中的某些行被计算了多次。如果您不想这样,那么您需要一个不同的计数——或者可能是以下改进之一。

如果查询旨在限制此连接的所有条件返回的行,那么您需要一个 INNER JOIN(为了清楚起见,还可以移动 ON 条件进入 WHERE 子句):

SELECT COUNT(*)
  FROM feed_RETS AS RETS INNER JOIN listings 
                                 ON (    RETS.list_number = listings.CVMLS 
                                      OR RETS.list_number = listings.REIN
                                    ) 
 WHERE listings.statusID IN (1,2,3)
   AND RETS.public_status NOT LIKE '%Sold%'
;

您的查询可能仍然很慢,原因有两个(尽我所能根据一般假设进行诊断):

  1. JOIN 条件中的 OR 强制进行完整的 table 扫描,因为优化器根本不知道要使用哪个索引或是否使用任何索引。
  2. 匹配字符串'%Sold%开头的通配符%强制进行全table扫描,因为正常类型的索引是通过将列内容从左开始划分来构建的-向右。将索引想象成按字母顺序排列的姓名列表:如果您在姓名的开头 ("names beginning with 'Jo'") 进行匹配,则可以使用有序列表快速找到匹配的姓名;相比之下,如果您要查找名称中间的内容 ("names with 'nat' in them"),那么您的索引对您毫无用处。

这个查询实际上可能更快:

SELECT SUM(CASE 
             WHEN l_cvmls.cvmls IS NOT NULL OR l_rein.REIN IS NOT NULL 
             THEN 1 
             ELSE 0
           END
          ) listing_count
  FROM (  feed_RETS AS r LEFT JOIN listings l_cvmls
                               ON l_cvmls.statusID IN (1,2,3)
                              AND r.list_number = l_cvmls.CVMLS
       ) LEFT JOIN listings l_rein ON l_rein.statusID IN (1,2,3)
                                  AND r.list_number = l_rein.REIN
 WHERE r.public_status NOT LIKE '%Sold%'
;

如果您可以避免 '%Sold%' 并改用 'Sold%',查询可能会更快。