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 个clients,它们都有不同的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 JOINs 基本上只是半连接,因为您实际上并没有从它们返回任何结果。所以我们可以把它们变成一个EXISTS。 您还将获得到int 的隐式转换,因为Clientvarchar1,2int。所以把它改成'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子句

SQL Server:性能问题:WHERE 子句中的 OR 语句替换

SQL Server:IF .. ELSE 中的 Where 子句

WHERE 子句中的 SQL 查询子选择优化 (SQL Server)

where 子句中的 case 语句 - SQL Server

SQL Server:动态 where 子句