SQL Server:性能问题:WHERE 子句中的 OR 语句替换
Posted
技术标签:
【中文标题】SQL Server:性能问题:WHERE 子句中的 OR 语句替换【英文标题】:SQL Server: Performance issue: OR statement substitute in WHERE clause 【发布时间】:2021-10-16 11:18:22 【问题描述】:我只想 select
基于列 PostingDate
的表 Stock
中的记录。
PostingDate
应该在另一个名为 InitClient
的表中的 InitDate
之后。但是,两个表(客户端 1 和客户端 2)中当前有 2 个client
s,它们都有不同的InitDate
。
通过下面的代码,我得到了我目前所需要的,基于下面还包含的示例数据。但是,出现了两个问题,首先是基于数百万条记录,查询花费的时间太长(几小时)。其次,每次包含新客户时,它都不是动态的。
解决性能问题的一个潜在选择是编写两个单独的查询,一个用于客户端 1,一个用于客户端 2,两者之间有一个 UNION
。不幸的是,这不够动态,因为可能有多个客户端。
SELECT
Material
,Stock
,Stock.PostingDate
,Stock.Client
FROM Stock
LEFT JOIN (SELECT InitDate FROM InitClient where Client = 1) C1 ON 1=1
LEFT JOIN (SELECT InitDate FROM InitClient where Client = 2) C2 ON 1=1
WHERE
(
(Stock.Client = 1 AND Stock.PostingDate > C1.InitDate) OR
(Stock.Client = 2 AND Stock.PostingDate > C2.InitDate)
)
样本数据集:
CREATE TABLE InitClient
(
Client varchar(300),
InitDate date
);
INSERT INTO InitClient (Client,InitDate)
VALUES
('1', '5/1/2021'),
('2', '1/31/2021');
SELECT * FROM InitClient
CREATE TABLE Stock
(
Material varchar(300),
PostingDate varchar(300),
Stock varchar(300),
Client varchar(300)
);
INSERT INTO Stock (Material,PostingDate,Stock,Client)
VALUES
('322', '1/1/2021', '5', '1'),
('101', '2/1/2021', '5', '2'),
('322', '3/2/2021', '10', '1'),
('101', '4/13/2021', '5', '1'),
('400', '5/11/2021', '170', '2'),
('401', '6/20/2021', '200', '1'),
('322', '7/20/2021', '160', '2'),
('400', '8/9/2021', '93', '2');
SELECT * FROM Stock
想要的结果,但随后用 OR 语句的替代品来提升性能:
| Material | PostingDate | Stock | Client |
|----------|-------------|-------|--------|
| 322 | 1/1/2021 | 5 | 1 |
| 101 | 2/1/2021 | 5 | 2 |
| 322 | 3/2/2021 | 10 | 1 |
| 101 | 4/13/2021 | 5 | 1 |
| 400 | 5/11/2021 | 170 | 2 |
| 401 | 6/20/2021 | 200 | 1 |
| 322 | 7/20/2021 | 160 | 2 |
| 400 | 8/9/2021 | 93 | 2 |
如果在上述代码中可以替代以保持性能,同时使其动态化,有什么建议吗?
【问题讨论】:
为什么您的子查询有TOP
而没有ORDER BY
?您肯定不会对每次运行查询时返回的任意行感到满意(并且该任意行可能在您运行所述查询时每次都不同)。
我同意,没必要。但是,由于只有两条记录(每个客户端 1 条),因此在这种情况下,每次的结果都是相同的。我会编辑它。
那为什么是TOP
?如果子查询只能返回 1 行没有TOP
,那么TOP
不应该在那里。
没错,这就是我删除它的原因,如您所见,它产生了相同的结果。谢谢你的评论。
只有你知道/理解你的模型,它由过度简化的 DDL 表示。但是,客户端 1 的初始日期为 2021 年 5 月 1 日,却有 2021 年 1 月 1 日的库存,这似乎很奇怪。同样奇怪的是,为什么您在外连接的同时还引用了 WHERE 子句中未保留的表 - 击败了外连接。你的加入对我来说没有逻辑意义。如果不了解您的目标和您在编写这个相当奇怪的查询时选择的决定,就很难提出任何建议
【参考方案1】:
您可以对这个查询进行相当多的优化。
首先,这两个LEFT JOIN
s 基本上只是半连接,因为您实际上并没有从它们返回任何结果。所以我们可以把它们变成一个EXISTS
。
您还将获得到int
的隐式转换,因为Client
是varchar
而1,2
是int
。所以把它改成'1','2'
,或者你可以改变列类型。
PostingDate
也是varchar
,应该是date
SELECT
s.Material
,s.Stock
,s.PostingDate
,s.Client
FROM Stock s
WHERE s.Client IN ('1','2')
AND EXISTS (SELECT 1
FROM InitClient c
WHERE s.PostingDate > c.InitDate
AND c.Client = s.Client
);
接下来您要查看索引。对于此查询(不考虑正在运行的任何其他查询),您可能需要以下索引(删除聚集索引的 INCLUDE
)
InitClient (Client, InitDate)
Stock (Client) INCLUDE (PostingDate, Material, Stock)
即使有了这些索引,您也可能会扫描到Stock
,因为IN
的功能类似于OR
。这并不总是发生,值得检查。如果是这样,您可以改写为使用UNION ALL
SELECT
s.Material
,s.Stock
,s.PostingDate
,s.Client
FROM (
SELECT *
FROM Stock s
WHERE s.Client = '1'
UNION ALL
SELECT *
FROM Stock s
WHERE s.Client = '2'
) s
WHERE EXISTS (SELECT 1
FROM InitClient c
WHERE s.PostingDate > c.InitDate
AND c.Client = s.Client
);
db<>fiddle
【讨论】:
【参考方案2】:期望您的查询是动态的并没有错。但是,为了提高性能,您可能需要在相互冲突的期望之间达成妥协。我将在这里介绍几种优化查询的方法,其中一些涉及一些重大更改,但最终是您或您的客户决定需要如何改进。此外,有些改进可能无效,所以不要想当然,测试一切。废话不多说,来看看建议
查询
首先我会尝试稍微改变一下查询,也许这样的东西可以帮助你
SELECT
Material
,Stock
,Stock.PostingDate
,C1.InitDate
,C2.InitDate
,Stock.Client
FROM Stock
LEFT JOIN InitClient C1 ON Client = 1
LEFT JOIN InitClient C2 ON Client = 2
WHERE
(
(Stock.Client = 1 AND Stock.PostingDate > C1.InitDate) OR
(Stock.Client = 2 AND Stock.PostingDate > C2.InitDate)
)
有时,摆脱子选择的简单步骤就可以解决问题
索引
您可能希望通过创建索引来加快处理速度,例如在Stock.PostingDate
上。
助手表
您可以创建一个帮助表来存储Stock
记录的相关数据,因此您可以在一段时间内执行一次慢速查询,可能一周一次,或者每次新客户进入舞台并存储结果在辅助表中。完成先决条件计算后,您将能够仅查询具有少量记录的辅助表,达到闪电般的快速行为。所以,想法是很少执行慢查询,缓存/存储结果并重用它们,而不是每次都计算它。
一个新列
您可以在Stock
表中创建一个名为InitDate
的列,并定期用每条记录的数据填充该列。第一次执行需要很长时间,但之后您将能够只查询Stock
表而无需连接和子选择。
【讨论】:
我检查了索引,似乎没问题。但是,额外加载记录听起来像是一种解决方法。奇怪的是,没有 OR 语句和 UNION,它只需要 1 分钟。一旦我添加了 OR 语句,它就需要 3 小时 30 分钟......对我来说仍然听起来很奇怪,为什么 OR 语句会花费这么多时间。 @titatovenaar 我试着理解你的评论。 "我检查了索引,似乎没问题。" 是什么意思?意思是?你已经有索引了吗?什么是索引,为什么它很好? “但是,额外加载记录听起来像是一种解决方法。”我不记得额外加载记录,也没有工会。简而言之,我的建议如下:1. 摆脱子选择,2. 索引,3. 通过日常 cron 作业或添加客户端时执行的触发器存储查询结果的帮助表,跨度> @titatovenaar 4. 创建和维护一个 Stock.InitDate 列,然后您可以摆脱左连接和子选择,因此您的查询将在不到一分钟的时间内执行,至少这是根据您所描述的假设非常安全。如果您还有其他问题,或者解决方案效率低下,请告诉我。以上是关于SQL Server:性能问题:WHERE 子句中的 OR 语句替换的主要内容,如果未能解决你的问题,请参考以下文章
SQL Server:性能问题:WHERE 子句中的 OR 语句替换
SQL Server:IF .. ELSE 中的 Where 子句
WHERE 子句中的 SQL 查询子选择优化 (SQL Server)