更好的 tsql 查询来确定谁是新客户(或不存在的替代品)

Posted

技术标签:

【中文标题】更好的 tsql 查询来确定谁是新客户(或不存在的替代品)【英文标题】:Better tsql query to figure out who's a new customer (or alternative to NOT EXISTS) 【发布时间】:2017-08-24 12:45:11 【问题描述】:

我有一张桌子,里面放着我们所有的订单。订单链接到一个人。每个人都隶属于一家公司。现在我需要一个在某个日期之前从未订购过的所有公司的列表。查询工作正常,但速度很慢。

这是我的 T-SQL 查询:

SELECT
    DISTINCT p1.company_id
FROM
    order o
    JOIN person p1 ON (o.person_id = p1.id AND p1.company_id IS NOT NULL)
WHERE
    o.orderDate > '2017-01-01'
AND
    o.orderDate < '2017-09-01'
AND NOT EXISTS (SELECT
                    p2.company_id
                FROM
                    order o2
                    JOIN person p2 ON (o2.person_id = p2.id AND p2.company_id = p1.company_id)
                WHERE
                    o2.orderDate < '2017-01-01')

我已经将它从 NOT IN 更改为 NOT EXISTS。因为这是这里大多数人推荐的。没有太大帮助。更好的索引稍微改善了这种情况,但查询仍然很慢。我认为这是因为对于每个订单都必须执行子查询。

这是执行计划: https://www.brentozar.com/pastetheplan/?id=SyAlAU3db 为简单起见,我在上面的示例中删除了一些 WHERE 子句)

查询在 Azure SQL(以及用于开发的 SQL Server Express 12)上运行

有人对如何解决这个问题有更好的想法吗?

【问题讨论】:

寻求性能帮助的问题应包括 DDL、DML 所涉及的表以及测试数据。如果您的测试数据很大,请尝试为表编写模式和统计信息(right click database-&gt;generate scripts-&gt;select specific database objects-&gt;in next screen select advanced and choose Script statistics) 并粘贴它有问题..有了这些信息,任何人都可以重现您面临的相同问题。否则,回答您的问题变得非常困难。粘贴服务器版本也有帮助 您能否在这里向我们展示当前的执行计划:brentozar.com/pastetheplan @Rammy 给你一个更简单的打击。您是否尝试过 EXCEPT 语句?有两个类似的子集,给你。此外,如果前面的注释没有意义,请分析 1) 索引 2) 列类型的执行计划。 干杯,将尝试以下建议并更新我的问题 【参考方案1】:

如果您有执行计划可以分享,这将有助于性能分析。

我对查询进行了一些更改,如下所示,您可以尝试改进它

SELECT p1.company_id
FROM  order o
INNER JOIN person p1 
    ON (o.person_id = p1.id AND p1.company_id IS NOT NULL)
GROUP BY p1.company_id
HAVING SUM(CASE WHEN  (o.orderDate > '2017-01-01' AND  o.orderDate < '2017-09-01') THEN 1 ELSE 0 END) > 0
      AND
      SUM(CASE WHEN  orderDate < '2017-01-01' THEN 1 ELSE 0 END) = 0

【讨论】:

次秒执行(相比 12s)。天才!非常感谢。【参考方案2】:

这个是怎么回事。希望我正确理解了任务。

(
  SELECT p1.company_id
  FROM order o
  JOIN person p1 
    ON o.person_id = p1.id 
  WHERE p1.company_id IS NOT NULL
    AND o.orderDate > '2017-01-01'
    AND o.orderDate < '2017-09-01'
)
EXCEPT
(
  SELECT p2.company_id
  FROM order o2
  JOIN person p2 
    ON o2.person_id = p2.id 
  WHERE p2.company_id IS NOT NULL
    AND o2.orderDate < '2017-01-01'
)

【讨论】:

只需要与众不同。 @cloudsafe 不需要多余的DISTINCT。它将是唯一的集合,没有重复。 可能是我理解错了,但是如果同一个人有很多订单呢? @cloudsafe EXCEPT 的最终结果始终是唯一的。我相信这里任何多余的 DISTINCT 甚至会降低性能。看一个简单的例子。 link 两个查询的结果只有一条记录为 1。 干杯!这可以正常工作,但只会将查询时间(在我的笔记本电脑上)从 12 秒减少到大约 11 秒 :-(【参考方案3】:

我认为这样就可以了(哎呀错过了尚未订购的)

;With FirstOrders
as
(
    Select p1.company_id   
    ,      MIN(o.orderDate) as FirstCompanyOrder
    From Orders o 
    Join Person P1 on o.person_id = p1.id
    Group by P1.Company_id
    Having MIN(o.OrderDate) < '2017-01-01'
)

Select distinct o.company_id
From      Orders      o 
Left join FirstOrders FO on o.Company_id = FO.ComapnyId
where FO.company_id is null

【讨论】:

【参考方案4】:

也许这会对你有所帮助:

WITH cte AS
(
    SELECT o.person_id, MIN(o.orderDate) minOrderDate
        FROM order o 
        GROUP BY o.person_id
)
SELECT DISTINCT p1.company_id
    FROM person p1
    JOIN cte ON cte.person_id = p1.id
    WHERE p1.company_id IS NOT NULL AND cte.minOrderDate > '2017-01-01' AND cte.minOrderDate < '2017-09-01';

【讨论】:

以上是关于更好的 tsql 查询来确定谁是新客户(或不存在的替代品)的主要内容,如果未能解决你的问题,请参考以下文章

将布尔字段查询为“不正确”(例如,错误或不存在)

TSQL 如果存在于子查询中

IBM的大数据云江湖,谁是新盟主?

分布式存储赛道百花齐放,谁是新龙头?

TSQL 等效于 MS Access 交叉表查询

TSQL 左连接,只有右起最后一行