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 的隐式转换,因为 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