【问题标题】:Rewrite IN subquery as JOIN将 IN 子查询重写为 JOIN
【发布时间】:2010-12-16 23:22:57
【问题描述】:

我从来没有在 MySQL 中使用 IN 获得过良好的性能,而且我再次遇到了性能问题。

我正在尝试创建一个视图。它的相关部分是:

SELECT
  c.customer_id,
  ....
  IF (c.customer_id IN (
            SELECT cn.customer_id FROM customer_notes cn
        ), 1, 0) AS has_notes
  FROM customers c;

基本上,我只想知道客户是否附有便条。多少个音符都没有关系。如何使用 JOIN 重写它以加快速度?

customers 表目前有 150 万行,因此性能是个问题。

【问题讨论】:

  • 我认为您想将其重写为 EXISTS 查询而不是 JOIN

标签: mysql join sql-optimization in-subquery


【解决方案1】:

您不需要选择客户 ID 吗?就目前而言,您不是为每个客户运行一次子查询,并获得一串真值或假值,但不知道哪个值适用于哪个客户?

如果这是您需要的,则无需引用客户表(除非您将数据库保持在语义不完整的状态,并且 customer_notes 中可能存在没有对应客户的条目 - 但是您有比这个查询的性能更大的问题);你可以简单地使用:

SELECT DISTINCT Customer_ID
  FROM Customer_Notes
 ORDER BY Customer_ID;

获取 Customer_Notes 表中至少包含一个条目的客户 ID 值列表。

如果您需要客户 ID 值列表和关联的真/假值,则需要进行联接:

SELECT C.Customer_ID,
       CASE WHEN N.Have_Notes IS NULL THEN 0 ELSE 1 END AS Has_Notes
  FROM Customers AS C
  LEFT JOIN (SELECT Customer_ID, COUNT(*) AS Have_Notes 
               FROM Customer_Notes
              GROUP BY Customer_ID) AS N
    ON C.Customer_ID = N.Customer_ID
 ORDER BY C.Customer_ID;

如果这导致性能不佳,请检查您在 Customer_Notes.Customer_ID 上是否有索引。如果这不是问题,请研究查询计划。


不能……在视图中

对视图中允许的内容的微小限制在任何 DBMS 中总是令人讨厌(MySQL 并不是唯一有限制的)。但是,我们可以通过一个常规连接来完成。我刚想起来。 COUNT(column) 只计算非空值,如果所有值都为空,则返回 0,所以 - 如果你不介意得到一个计数而不仅仅是 0 或 1 - 你可以使用:

SELECT C.Customer_ID,
       COUNT(N.Customer_ID) AS Num_Notes
  FROM Customers AS C
  LEFT JOIN Customer_Notes AS N
    ON C.Customer_ID = N.Customer_ID
 GROUP BY C.Customer_ID
 ORDER BY C.Customer_ID;

如果你绝对必须有 0 或 1:

SELECT C.Customer_ID,
       CASE WHEN COUNT(N.Customer_ID) = 0 THEN 0 ELSE 1 END AS Has_Notes
  FROM Customers AS C
  LEFT JOIN Customer_Notes AS N
    ON C.Customer_ID = N.Customer_ID
 GROUP BY C.Customer_ID
 ORDER BY C.Customer_ID;

请注意,“N.Customer_ID”的使用至关重要 - 尽管表中的任何列都可以(但您没有透露任何其他列的名称,AFAICR),而且我通常会使用除为清楚起见加入列。

【讨论】:

  • 您好 Jonathan,是的,我正在选择客户 ID。我只是将 SQL 缩减到最相关的部分。我已经编辑了我的问题以使其更清楚。
  • 这是非常聪明的乔纳森——首先对连接表进行分组以确保连接语句只返回原始表中的一行。不幸的是,在 MySQL 中创建 VIEW 时我不能使用子查询,但我有办法解决它。到目前为止,在对其进行一些测试时,性能似乎也不错。
【解决方案2】:

我认为EXISTSJOININ 更适合您的情况。

SELECT 
   IF (EXISTS ( 
        SELECT *
        FROM customer_notes cn 
        WHERE c.customer_id = cn.customer_id),
       1, 0) AS filter_notes 
FROM customers 

【讨论】:

  • 感谢加布的建议。但是,这仍然给我带来了糟糕的表现。
【解决方案3】:

试试这个

SELECT
  CASE WHEN cn.customer_id IS NOT NULL THEN 1
        ELSE 0
    END     AS filter_notes
  FROM customers c LEFT JOIN customer_notes cn
    ON c.customer_id= cn.customer_id

【讨论】:

  • 感谢您的建议,但是如果有两个客户的注释,这不是两次吗?
  • 是的......如果你只有客户出现一次,那么你可以使用不同的查询 (SELECT DISTINCT customer_id FROM customer_notes) cn 而不是 customer_notes cn .. 当然它可能会影响性能与之前的查询相比...
猜你喜欢
  • 1970-01-01
  • 2014-09-27
  • 2021-04-23
  • 2012-05-22
  • 1970-01-01
  • 1970-01-01
  • 2011-05-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多