将 IN 子查询重写为 JOIN

发布于 2024-10-08 04:48:09 字数 356 浏览 8 评论 0原文

我从来没有在 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 重写它以加快速度?

客户表当前有 150 万行,因此性能是一个问题。

I've never had good performance with IN in MySQL and I've hit a performance issue with it again.

I'm trying to create a view. The relevant part of it is:

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;

Basically, I just want to know if the customer has a note attached to it or not. It doesn't matter how many notes. How can I rewrite this using JOIN to speed it up?

The customers table currently has 1.5 million rows so performance is an issue.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

金橙橙 2024-10-15 04:48:09

您不需要选择的客户 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)并且我通常会为清楚起见,请使用连接列以外的其他内容。

Don't you need the customer ID selected? As it stands, aren't you running the subquery once per customer, and getting a stream of true or false values with no idea which one applies to which customer?

If that is what you need, you don't need to reference the customers table (unless you keep your database in a state of semantic disintegrity and there could be entries in customer_notes for which there is no corresponding customer - but then you have bigger problems than the performance of this query); you can simply use:

SELECT DISTINCT Customer_ID
  FROM Customer_Notes
 ORDER BY Customer_ID;

to obtain the list of customer ID values with at least one entry in the Customer_Notes table.

If you want a list of Customer ID values and an associated true/false value, then you need to do a join:

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;

If this gives poor performance, check that you have an index on Customer_Notes.Customer_ID. If that isn't the issue, study the query plan.


Can't do ... in a view

The petty restrictions on what is allowed in a view is always a nuisance in any DBMS (MySQL is not alone in having restrictions). However, we can do it with a single regular join. I just remembered. COUNT(column) only counts non-null values, returning 0 if all values are null, so - if you don't mind getting a count rather than just 0 or 1 - you can use:

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;

And if you absolutely must have 0 or 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;

Note that the use of 'N.Customer_ID' is crucial - though any column in the table would do (but you've not divulged the names of any other columns, AFAICR) and I'd normally use something other than the joining column for clarity.

聊慰 2024-10-15 04:48:09

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

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

I think EXISTS suits your situation better than JOIN or IN.

SELECT 
   IF (EXISTS ( 
        SELECT *
        FROM customer_notes cn 
        WHERE c.customer_id = cn.customer_id),
       1, 0) AS filter_notes 
FROM customers 
洛阳烟雨空心柳 2024-10-15 04:48:09

试试这个

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

Try this

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文