NOT IN 语句正在减慢我的查询速度

Posted

技术标签:

【中文标题】NOT IN 语句正在减慢我的查询速度【英文标题】:NOT IN statement is slowing down my query 【发布时间】:2019-08-09 07:24:57 【问题描述】:

我的查询有问题。我在这里有一个简单的例子来说明我的代码。

SELECT distinct ID 
FROM Table  
WHERE IteamNumber in (132,434,675) AND Year(DateCreated) = 2019
      AND ID NOT IN (
                     SELECT Distinct ID FROM Table  
                     WHERE IteamNumber in (132,434,675) AND DateCreated < '2019-01-01')

如您所见,我正在检索在 2019 年而非更早时间创建的唯一数据 ID。

select 语句工作正常,但是一旦我使用 NOT IN 语句,查询可能会轻松超过 1 分钟。

我的另一个问题可能与运行 Microsoft Business Central 的 SQL Server 的计算机/服务器性能有关吗?因为即使使用 (NOT IN) 语句,同样的查询也能完美运行,但那是在 Microsoft Dynamics C5 SQL Server 中。

所以我的问题是我的查询有问题还是主要是服务器问题?

更新:这是一个真实的例子:检索 500 行需要 25 秒

Select count(distinct b.No_),'2014'
from [Line] c    
inner join [Header] a
on a.CollectionNo = c.CollectionNo
Inner join [Customer] b
on b.No_ = a.CustomerNo

where  c.No_ in('2101','2102','2103','2104','2105')
and year(Enrollmentdate)= 2014 
and(a.Resignationdate < '1754-01-01 00:00:00.000' OR a.Resignationdate >= '2014-12-31')


and NOT EXISTS(Select distinct x.No_
                 from [Line] c    
                 inner join [Header] a
                 on a.CollectionNo = c.CollectionNo
                 Inner join [Customer] x
                 on x.No_ = a.CustomerNo
                 where x.No_ = b.No_ and 
                       c.No_ in('2101','2102','2103','2104','2105')
                       and Enrollmentdate < '2014-01-01'
                       and(a.Resignationdate < '1754-01-01 00:00:00.000' OR a.Resignationdate > '2014-12-31'))

【问题讨论】:

如果不了解更多关于您的数据库的信息,实际上无法回答这个问题,例如多少行,什么索引等。要获得实际的性能帮助,您确实需要向我们展示您的执行计划。 关注这个话题:NOT IN vs NOT EXISTS 在您的第一个查询中,您只包括那些日期在 2019 年创建的 ID。它将自动不包括任何具有“DateCreated Year(DateCreated) = 2019 替换为DateCreated &gt;= '2019-01-01' 并放弃NOT IN @Ritika,如果 ID 不是唯一的,这是不正确的:It will automatically not include any of the ID that have 'DateCreated &lt; '2019-01-01 【参考方案1】:

如果我理解正确,您可以将查询编写为带有HAVING 子句的GROUP BY 查询:

SELECT ID 
FROM t
WHERE IteamNumber in (132, 434, 675)
GROUP BY ID
HAVING MIN(DateCreated) >= '20190101' -- no row earlier than 2019
AND    MIN(DateCreated) <  '20200101' -- at least one row less than 2020

这将删除存在较早记录的行。您可以通过创建覆盖索引来进一步提高性能:

CREATE INDEX IX_t_0001 ON t (ID) INCLUDE (IteamNumber, DateCreated)

【讨论】:

仅删除 OP 表上的第二次扫描可能会有很大的好处。您可能还需要在 HAVING 中添加 MAX 子句,以确保不会捕获 2020 年的行(即使只是未来校对的一种形式),或者以便 OP 可以看到如何将其应用于不同年份。 嗨,萨尔曼,我刚刚尝试了你的灵魂,但我没有检索到任何数据。我使用 NOT in 的原因,因为 customerID 可能在 3 年前取消了他们的订阅,然后他们可以选择在 2019 年再次订阅,使用相同的帐号,即 id(在本例中) 另外,使用索引或强制索引存储过程而不让 sql server 选择它不是一个坏主意吗? @MishMish 此查询应生成与您的查询相同的结果。如果它没有给你想要的结果,你可以在生产代码中使用不同的 where 子句。另外,我只是建议和索引,如果 SQL Server 发现它有用,它会使用它。没有强迫。 嗨,萨尔曼,我在上面添加了原始查询,不确定它是否适用于有声明【参考方案2】:

我通常更喜欢 JOINs 而不是 INs,你可以获得相同的结果,但引擎往往能够更好地优化它。

您将主查询 (T1) 与 IN 子查询 (T2) 连接起来,然后过滤 T2.ID 为空,确保您没有找到任何符合这些条件的记录。

SELECT distinct T1.ID 
FROM Table T1 
     LEFT JOIN Table T2 on T2.ID = T1.ID AND 
                     T2.IteamNumber in (132,434,675) AND T2.DateCreated < '2019-01-01'
WHERE T1.IteamNumber in (132,434,675) AND Year(T1.DateCreated) = 2019 AND
      T2.ID is null

更新:这是根据您的真实查询更新的提案。由于您的子查询具有内部连接,因此我创建了一个 CTE,因此您可以离开连接该子查询。功能是相同的,您将主查询与子查询连接起来,并且只返回在子查询中没有找到匹配记录的行。

with previous as (
  Select x.No_
  from [Line] c    
       inner join [Header] a on a.CollectionNo = c.CollectionNo
       inner join [Customer] x on x.No_ = a.CustomerNo
  where     c.No_ in ('2101','2102','2103','2104','2105')
        and Enrollmentdate < '2014-01-01'
        and (a.Resignationdate < '1754-01-01 00:00:00.000' OR a.Resignationdate > '2014-12-31'))
)
Select count(distinct b.No_),'2014'
from [Line] c    
     inner join [Header] a on a.CollectionNo = c.CollectionNo
     inner join [Customer] b on b.No_ = a.CustomerNo
     left join previous p on p.No_ = b.No_
where    c.No_ in ('2101','2102','2103','2104','2105')
     and year(Enrollmentdate)= 2014 
     and (a.Resignationdate < '1754-01-01 00:00:00.000' OR a.Resignationdate >= '2014-12-31')
     and p.No_ is null

【讨论】:

嘿,马克,我一定要试试你的例子 还有一个问题,你知道 in 语句中的整数是否比 nvarchar/varchar 值表现更好? @MishMish,如果该字段被索引,我认为它是整数还是字符串并不重要,但如果它没有被索引,那么是的,引擎会比字符串更快地比较整数. 为字段做索引简单吗?它在存储过程中,我认为将索引实现到存储过程中是不好的 我刚刚用实际代码编辑了我的问题,你能告诉我如何使用多个连接来完成【参考方案3】:

问题是因为您的IN 语句,我认为最好避免使用任何IN 语句而不是这个,使用子查询创建join 并使用where 子句过滤掉您的数据。

IN 语句的情况下,表的每条记录都映射到子查询的所有记录,这肯定会减慢您的处理速度。

如果必须使用IN 子句,则将其与index 一起使用。为您尊重的列创建适当的索引,从而提高您的性能。

您可以使用EXISTS 代替IN 来提高查询的性能。

EXISTS 的示例是:

SELECT distinct ID 
FROM Table AS T 
WHERE IteamNumber in (132,434,675) AND Year(DateCreated) = 2019
      AND NOT EXISTS (
                     SELECT Distinct ID FROM Table AS T2 
                     WHERE T1.ID=T2.ID 
                     AND IteamNumber in (132,434,675) AND DateCreated < '2019-01-01' )

【讨论】:

一个设计良好的数据库应该能够处理in 语句。与任何数据库性能一样,我们需要了解全局以提供有用的建议。 谢谢你的darkrob,你能用我的例子在代码中演示一下你将如何使用join或exists吗? 我觉得奇怪的是它无法处理 Not in 语句,我们谈论的记录并不多,基本上我们在这里谈论的 Dynamics 业务中心 " 在我看来,最好避免任何 IN 语句,而不是 this" 是一个非常全面的语句。你是说用JOIN (VALUES(1),(2)) V(I) ON T.ID = V.I 代替WHERE T.ID IN (1,2) 吗?在许多情况下,IN 可以与JOIN 一样高效,而INJOIN 不是同义词; JOIN 将返回可能创建重复的所有相关行,而 INEXISTS (我倾向于使用)不会。它们可以在功能上相似,但并不相同。

以上是关于NOT IN 语句正在减慢我的查询速度的主要内容,如果未能解决你的问题,请参考以下文章

Python 操作Redis

python爬虫入门----- 阿里巴巴供应商爬虫

Python词典设置默认值小技巧

《python学习手册(第4版)》pdf

Django settings.py 的media路径设置

Python中的赋值,浅拷贝和深拷贝的区别