【问题标题】:Filter customers without using subqueries in SQL在 SQL 中不使用子查询过滤客户
【发布时间】:2021-09-18 11:03:53
【问题描述】:

如何在不使用子查询的情况下找到过去 6 个月内未购买任何图书的客户。

SELECT first_name, last_name, email
FROM customers
WHERE id NOT IN (
    SELECT customers.id
    FROM customers
    LEFT JOIN orders ON orders.customer_id = customers.id
    WHERE DATEDIFF(MONTH, orders.purchased_date, GETDATE()) < 6
    GROUP BY customers.id
);

【问题讨论】:

  • 所以我只是使用估计的执行计划对我拥有499,290 客户端和“1,333,326”订单的真实表粗略地检查了它的性能。你的查询占了 43%,Johns 查询占了 39%(一个微小的改进),我的查询占了 9%(一个巨大的改进),SMors 查询占了 9%(又是一个巨大的改进)。很明显,性能不是您的标准,这就是为什么知道您为什么要排除子查询选项真的很有趣?
  • 哇,我没想到。当我学习 sql 时,讲师说子查询需要很长时间。以我的例子为例,当查询执行时,它会在客户表中执行两次,一次用于查找过去六个月内购买过 int 的所有客户,然后查找所有客户并检查客户 ID 是否不存在
  • 这是学习 SQL Server 的一课...性能并不总是容易预测...只能通过测量来确定性能。特别是如果您对性能感兴趣,您应该要求一个高性能的查询,而不是假设应用可能会或可能不会产生任何影响的限制。

标签: sql sql-server database tsql filtering


【解决方案1】:

为什么不使用子查询?它是这里工作的最佳工具。您可以使用NOT EXISTS 而不是IN 来改进您的查询(同时仍然使用子查询),并且不将DATEDIFF 函数直接应用于您的列,因为这会使查询不可分割(即不能使用索引)。 DATEDIFF 也计算当时和现在之间的月份变化,而不是实际月份。请参阅下面的逻辑更改。

SELECT c.first_name, c.last_name, c.email
FROM customers c
WHERE NOT EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.customer_id = c.customers.id
    AND o.purchased_date >= DATEADD(MONTH, -6, GETDATE())
)

也始终建议使用表别名。

假设您从性能的角度提出了这个问题,我使用估计的执行计划对我拥有的真实表(有 499,290 个客户和 1,333,326 个订单)粗略地检查了这个问题的性能。你的查询占了 43%,John 的查询占了 39%(一个微小的改进),我的查询占了 9%(一个巨大的改进),SMor 的查询占了 9%(又是一个巨大的改进)。因此,如果性能确实是您的问题所在,那么您应该提出这个问题,而不是人为地限制性能最佳的解决方案。

【讨论】:

    【解决方案2】:

    EXCEPT 是一种杂乱无章的选择。 cte 获取所有客户 ID,然后根据购买日期在 6 个月内的订单删除这些客户 ID。然后,您只需将 cte 加入到 customer 表中。

    with cte as (
       select customer_id from dbo.customers
       except
       select customer_id from dbo.orders where purchase_date >= dateadd(month, -6, getdate())
    )
    select cust.customer_id, ... 
    from dbo.customers as cust
    inner join cte on cte.customer_id = cust.customer_id
    order by ...;
    

    【讨论】:

    • CTE is 本质上是一个子查询 :) 但实际上这取决于 OP 为什么要排除它们。
    • 再试一次 - 它是 DERIVED 表的语法糖,它被用作连接。它不是真正意义上的子查询。奇怪的是,你在这里写了这个,但不是为了你使用 IN - 那到底是不是实际上是一个子查询?
    • 派生表是子查询的一种形式 :) 但是是的,我并没有声称在我的答案中删除了子查询。
    【解决方案3】:

    这是一个没有子查询的选项(不清楚为什么)......只是蛮力:)

    Select C.first_name
          ,C.last_name
          ,C.email
          ,LastPurchase = max(O.purchased_date)
      From customers C
      Join orders    O on O.customer_id = C.customer_id 
      Group by C.customer_id
              ,C.first_name
              ,C.last_name
              ,C.email
      Having max(O.purchased_date) <= dateadd(month,-6,getdate())
    

    【讨论】:

    • 这样可以处理没有订单的客户吗?
    • @DaleK 如果您没有订单,您是客户吗?
    • 从技术上讲,您可能不是 :),但我见过的大多数客户数据库都有客户由于某种原因没有订单。
    猜你喜欢
    • 1970-01-01
    • 2021-06-01
    • 1970-01-01
    • 2012-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多