SQL 服务器:性能问题:WHERE 子句中的 OR 语句替换
SQL Server: Performance issue: OR statement substitute in WHERE clause
我只想 select
基于列 PostingDate
的 table Stock
中的记录。
在另一个名为 InitClient
的 table 中,PostingDate
应该在 InitDate
之后。但是,目前在两个 table 中都有 2 个 client
(客户端 1 和客户端 2),它们都有不同的 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 |
如果上述代码中有可能的替代品以保持性能,同时使其动态化,有什么建议吗?
期望您的查询是动态的并没有错。但是,为了提高性能,您可能需要在相互冲突的期望之间达成妥协。我将在这里介绍几种优化查询的方法,其中一些涉及一些重大变化,但最终由您或您的客户决定如何改进。另外,一些改进可能是无效的,所以不要想当然,测试一切。话不多说,看推荐
查询
首先我会试着稍微改变一下查询,也许这样的东西可以帮助你
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
。
帮手table
您可以创建一个助手 table 来存储 Stock
记录的相关数据,这样您就可以偶尔执行一次慢速查询,也许一周一次,或者每次新客户端进入舞台并将结果存储在助手 table 中。一旦先决条件计算完成,您将能够仅查询 helper table 及其少量记录,达到闪电般的快速行为。因此,我们的想法是很少执行慢查询,cache/store 结果并重用它们而不是每次都计算它。
一个新专栏
您可以在 Stock
table 中创建一个名为 InitDate
的列,并定期为每条记录填充数据。第一次执行会花费很长时间,但之后您将只能查询 Stock
table 而没有连接和子选择。
您可以大大优化此查询。
- 首先,这两个
LEFT JOIN
基本上只是半连接,因为实际上 return 它们没有任何结果。所以我们可以把它们变成一个 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
);
我只想 select
基于列 PostingDate
的 table Stock
中的记录。
在另一个名为 InitClient
的 table 中,PostingDate
应该在 InitDate
之后。但是,目前在两个 table 中都有 2 个 client
(客户端 1 和客户端 2),它们都有不同的 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 |
如果上述代码中有可能的替代品以保持性能,同时使其动态化,有什么建议吗?
期望您的查询是动态的并没有错。但是,为了提高性能,您可能需要在相互冲突的期望之间达成妥协。我将在这里介绍几种优化查询的方法,其中一些涉及一些重大变化,但最终由您或您的客户决定如何改进。另外,一些改进可能是无效的,所以不要想当然,测试一切。话不多说,看推荐
查询
首先我会试着稍微改变一下查询,也许这样的东西可以帮助你
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
。
帮手table
您可以创建一个助手 table 来存储 Stock
记录的相关数据,这样您就可以偶尔执行一次慢速查询,也许一周一次,或者每次新客户端进入舞台并将结果存储在助手 table 中。一旦先决条件计算完成,您将能够仅查询 helper table 及其少量记录,达到闪电般的快速行为。因此,我们的想法是很少执行慢查询,cache/store 结果并重用它们而不是每次都计算它。
一个新专栏
您可以在 Stock
table 中创建一个名为 InitDate
的列,并定期为每条记录填充数据。第一次执行会花费很长时间,但之后您将只能查询 Stock
table 而没有连接和子选择。
您可以大大优化此查询。
- 首先,这两个
LEFT JOIN
基本上只是半连接,因为实际上 return 它们没有任何结果。所以我们可以把它们变成一个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
);