通过函数计数和排序优化查询

Optimise query with count and order by function

我对这个查询的优化有疑问,我有 3 tables (Products = Catalogo.GTIN, Sales Header = TEDEF.Factura and Sales Detail = TEDEF.Farmacia).

查询尝试查找列 VPRODEXENIGV_FARMode。这个没有 ORDER BY 的查询执行不到 3 秒(详细信息 table 大约有 3000 万行)。

但是当我添加 ORDER BY 子句时,查询现在需要 30 多分钟才能 运行。

我想知道如何优化此查询或优化此查询所需的索引。

SELECT *
FROM Catalogo.GTIN G
CROSS APPLY
    (SELECT TOP 1 
         COUNT(FAR.VPRODEXENIGV_FAR) [ROW],
         YEAR(FAC2.VFECEMI_FAC) [AÑO],
         MONTH(FAC2.VFECEMI_FAC) [MES],
         FAR.VCODPROD_FAR_003,
         CASE WHEN FAR.VPRODEXENIGV_FAR = 'A' THEN 1 ELSE 0 END AfectoIGV
     FROM 
         TEDEF.Factura FAC2
     INNER JOIN 
         TEDEF.Farmacia FAR ON FAC2.VTDOCPAGO_FAC = FAR.VTDOCPAGO_FAC
                            AND FAC2.VNDOCPAGO_FAC = FAR.VNDOCPAGO_FAC
     WHERE 
         G.CODIGO = FAR.VCODPROD_FAR_003
     GROUP BY 
         YEAR(FAC2.VFECEMI_FAC),
         MONTH(FAC2.VFECEMI_FAC),
         FAR.VCODPROD_FAR_003,
         FAR.VPRODEXENIGV_FAR
     ORDER BY 
         1 DESC --- <----- THE PROBLEM IS HERE
    ) GG

哎呀!您有一个非常昂贵的依赖子查询。它很昂贵,因为 SELECT TOP(n) ... ORDER BY col DESC 做了很多工作来创建一个结果集,只是为了丢弃除一行以外的所有内容。而且,它是一个依赖子查询,因此 Catalogo.GTIN 的每一行都是 运行 .

您似乎想要计算每个 Catalogo.GTIN 行在最近一个月和一年中的结果集行数。因此,让我们尝试重构您的查询来做到这一点。

我们将从一个子查询开始,以获取每个商品的最新 Factura 行的月份开始日期。

                   SELECT CODIGO,
                          DATEFROMPARTS(YEAR(maxd), MONTH(maxd),1) maxmes
                     FROM (
                       SELECT MAX(FAC2.VFECEMI_FAC) maxd,
                              G.CODIGO
                         FROM Catalogo.GTIN G
                         JOIN TDEF.Farmacia FAR
                               ON G.CODIGO = FAR.VCODPROD_FAR_003
                         JOIN TEDEF.Factura FAC2
                               ON FAC2.VTDOCPAGO_FAC = FAR.VTDOCPAGO_FAC
                              AND FAC2.VNDOCPAGO_FAC = FAR.VNDOCPAGO_FAC
                        GROUP BY G.CODIGO
                       ) maxd

明智的做法是对其进行测试并确保其正常工作并表现良好。如果在SSMS中测试,可以使用“Show Actual Execution Plan”,看它是否推荐额外的索引。这个子查询只需要 运行 一次,而不是每 G.CODIGO 行一次。

然后我们将在您的大型查询中使用它。

SELECT G.*,
       COUNT(FAR.VPRODEXENIGV_FAR) [ROW],
       YEAR(FAC2.VFECEMI_FAC) [AÑO],
       MONTH(FAC2.VFECEMI_FAC) [MES],
       FAR.VCODPROD_FAR_003,
       CASE WHEN FAR.VPRODEXENIGV_FAR = 'A' THEN 1 ELSE 0 END AfectoIGV
  FROM Catalogo.GTIN G
  JOIN (
                   SELECT CODIGO,
                          DATEFROMPARTS(YEAR(maxd), MONTH(maxd),1) maxmes
                     FROM (
                       SELECT MAX(FAC2.VFECEMI_FAC) maxd,
                              G.CODIGO
                         FROM Catalogo.GTIN G
                         JOIN TDEF.Farmacia FAR
                               ON G.CODIGO = FAR.VCODPROD_FAR_003
                         JOIN TEDEF.Factura FAC2
                               ON FAC2.VTDOCPAGO_FAC = FAR.VTDOCPAGO_FAC
                              AND FAC2.VNDOCPAGO_FAC = FAR.VNDOCPAGO_FAC
                        GROUP BY G.CODIGO
                       ) maxd
       ) maxmes ON G.CODIGO = maxmes.CODIGO
  JOIN TEDEF.Farmacia FAR 
         ON G.CODIGO = FAR.VCODPROD_FAR_003
  JOIN TEDEF.Factura FAC2
         ON FAC2.VTDOCPAGO_FAC = FAR.VTDOCPAGO_FAC
        AND FAC2.VNDOCPAGO_FAC = FAR.VNDOCPAGO_FAC
        AND FAC2.VFECEMI_FAC >= maxmes.maxmes
 GROUP BY maxmes.maxmes, 
          G.CODIGO,
         FAR.VCODPROD_FAR_003,
         FAR.VPRODEXENIGV_FAR

这是棘手的一点:

DATEFROMPARTS(YEAR(maxd), MONTH(maxd),1) maxmes 将任何日期 maxd 变成该月的第一天。

并且,FAC2.VFECEMI_FAC >= maxmes.maxmes 过滤掉该月第一天之前的行(对于那个 CODIGO)。它以 sargable 方式实现:一种可以利用 FAC2.VFECEMI_FAC.

上的索引的方式

这是另一种方法 TOP(1) ORDER BY d DESC。而且更快。

一切都是关于行的集合。特别是在使用 GROUP BY 时,限制每组中的行数对性能有帮助。

显然我无法调试它。

又是我,最后我解决了优化问题,现在查询延迟大约20秒(使用排序指令和table超过3000万行的计数)我希望这个方法可以帮助他人,或者可以成为社区的最佳选择。 我解决了应用排序但使用 Row_Number 指令的问题,这样服务器将我的索引用于排序指令并创造奇迹:

WITH x
AS
(
    SELECT *, ROW_NUMBER() OVER (PARTITION BY GG.COD, GG.[AÑO], GG.[MES] ORDER BY GG.[ROW] DESC) [ID]
    FROM Catalogo.GTIN G
        CROSS APPLY
        (
            SELECT COUNT(FAR.VPRODEXENIGV_FAR) [ROW]
                , YEAR(FAC2.VFECEMI_FAC) [AÑO]
                , MONTH(FAC2.VFECEMI_FAC) [MES]
                , FAR.VCODPROD_FAR_003 [COD]
                , CASE WHEN FAR.VPRODEXENIGV_FAR = 'A' THEN 1 ELSE 0 END AfectoIGV
            FROM TEDEF.Factura FAC2
            INNER JOIN TEDEF.Farmacia FAR
                ON FAC2.VTDOCPAGO_FAC = FAR.VTDOCPAGO_FAC
                    AND FAC2.VNDOCPAGO_FAC = FAR.VNDOCPAGO_FAC
            WHERE G.CODIGO = FAR.VCODPROD_FAR_003
            GROUP BY YEAR(FAC2.VFECEMI_FAC)
                , MONTH(FAC2.VFECEMI_FAC)
                , FAR.VCODPROD_FAR_003
                , FAR.VPRODEXENIGV_FAR
--            ORDER BY 1 DESC   --- <---- this is the bad guy, please, don't do that xD
        ) GG
) SELECT *
FROM x WHERE ID = 1

这样我就可以对 Count 指令进行排序并计算列的模式 FAR.VPRODEXENIGV_FAR