通过函数计数和排序优化查询
Optimise query with count and order by function
我对这个查询的优化有疑问,我有 3 tables (Products = Catalogo.GTIN
, Sales Header = TEDEF.Factura
and Sales Detail = TEDEF.Farmacia
).
查询尝试查找列 VPRODEXENIGV_FAR
的 Mode
。这个没有 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
我对这个查询的优化有疑问,我有 3 tables (Products = Catalogo.GTIN
, Sales Header = TEDEF.Factura
and Sales Detail = TEDEF.Farmacia
).
查询尝试查找列 VPRODEXENIGV_FAR
的 Mode
。这个没有 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