我有一个存储过程,它从我创建的两个表中获取信息,生成一个摘要表,然后与几个视图一起使用。
以前,这需要60-90秒来运行。我有两个调用不同成本的函数,第三个调用另一个调用cost*qty。我删除了所有3个函数,并替换为一个新函数,它几乎是其他代价函数的精确副本
我写这个是因为我正在努力,所以它有点演变。我在速度上有所提高,但仍然没有以前快,我不知道为什么。
ALTER FUNCTION [dbo].[fn_getFactoryStdCost]
(@PartID int)
RETURNS decimal(20, 4)
AS
BEGIN
DECLARE @pureID int = 0
SET @pureID = (SELECT TOP(1) PURE_COST_ID
FROM visuser.PART_COST
WHERE EN_PART_ID = @partID
ORDER BY EN_REV_MASTER_ID DESC, IC_WAREHOUSE_ID DESC)
RETURN (SELECT TOP(1) (TOT_MATERIAL_N + TOT_MATERIAL_OVERHEAD_N)
FROM visuser.PURE_COST
WHERE PURE_COST_ID = @pureID
ORDER BY (TOT_MATERIAL_N + TOT_MATERIAL_OVERHEAD_N) DESC)
END
替换为。我添加了 WITH INLINE = OFF
在它第一次被困在排除这个可能性之后。这个函数本身工作得很好。
ALTER FUNCTION [dbo].[fn_getFactoryStdCost]
(@PartID int)
RETURNS decimal(20,4)
WITH INLINE = OFF
AS
BEGIN
DECLARE @pureID int = 0
SET @pureID = (SELECT TOP(1) PURE_COST_ID
FROM visuser.PART_COST
WHERE EN_PART_ID = @partID
ORDER BY EN_REV_MASTER_ID DESC, IC_WAREHOUSE_ID DESC)
RETURN (SELECT TOP(1) (TOT_MATERIAL_N + TOT_MATERIAL_OVERHEAD_N + TOT_RUN_VALUE_N + TOT_FIXED_OVERHEAD_N) FROM visuser.PURE_COST WHERE PURE_COST_ID = @pureID ORDER BY (TOT_MATERIAL_N + TOT_MATERIAL_OVERHEAD_N) DESC)
END
我做的其他改变是 [Qty] > 0 AND
到[part count]行,并将commodity id的基于字符串的条目替换为int(这更合适),因为commodity\u id是对commodity\u代码的引用,而commodity\u代码就是字符串。
我希望它跑得更快,而不是无限期地跑。这个过程现在需要很长时间才能运行。我现在已经38分钟了。我还试着在过程本身中复制代码并运行它,这也会花费很长时间,所以它是代码本身的一部分。
allpartslist表有1.04m行,bombreakdown表也有。bombreakdown表要复杂得多,需要40-60秒才能生成。bomsummary表将有4100行。allpartslist表有适当的索引,bombreakdown没有。
ALTER PROCEDURE [dbo].[createBOMSummary]
AS
DECLARE @processTime int=0, @begin datetime, @end datetime
SET @begin = SYSDATETIME()
IF OBJECT_ID(N'dbo.bomSummary', N'U') IS NOT NULL
DROP TABLE bomSummary
SELECT
DISTINCT ap.[SourcePartID] AS [Assembly Part ID],
p.[PART_X] AS [Assembly Part #],
p.[DESCR_X] AS [Assembly Part Description],
(SELECT COUNT(DISTINCT [Component Part #]) FROM [bomBreakdown] WHERE [Qty] > 0 AND [Component Part ID] IS NOT NULL AND SourcePartID = ap.SourcePartID GROUP BY [SourcePartID]) AS [Part Count],
(SELECT SUM([Qty]) FROM [bomBreakdown] WHERE [Component Part ID] IS NOT NULL AND SourcePartID = ap.[SourcePartID] GROUP BY [SourcePartID]) AS [Total # of Parts],
([dbo].[fn_getFactoryStdCost](ap.[SourcePartID])) AS [Factory Std Cost],
COALESCE(
(SELECT COUNT(DISTINCT ComponentPartID)
FROM AllPartsList apl
LEFT JOIN visuser.EN_PART p1
ON p1.[EN_Part_ID] = apl.[ComponentPartID]
WHERE
apl.ComponentPartID IS NOT NULL AND
apl.SourcePartID = ap.SourcePartID AND
p1.Commodity_ID IN (15, 84, 85, 87, 81, 92) -- Commodity Codes: 009, 072, 073, 075, 079, 082
GROUP BY SourcePartID
), 0) AS [# of Docs], --0sec
COALESCE(
(SELECT COUNT(DISTINCT ComponentPartID)
FROM AllPartsList apl
LEFT JOIN visuser.EN_PART p1
ON p1.[EN_Part_ID] = apl.[ComponentPartID]
WHERE
apl.ComponentPartID IS NOT NULL AND
apl.SourcePartID = ap.SourcePartID AND
p1.Commodity_ID IN (28) -- Commodity Code 034
GROUP BY SourcePartID
), 0) AS [# of Software], --0sec
COALESCE(
(SELECT COUNT(*)
FROM visuser.[PART_COST]
WHERE [STD_PO_Cost_N] > 0 AND
EN_PART_ID IN
(SELECT DISTINCT ComponentPartID FROM AllPartsList WHERE ComponentPartID IS NOT NULL AND SourcePartID = ap.SourcePartID)
), 0) AS [# of Std Cost Items], --0sec
COALESCE(
(SELECT COUNT(DISTINCT ComponentPartID)
FROM AllPartsList apl
LEFT JOIN visuser.EN_PART p1
ON p1.[EN_Part_ID] = apl.[ComponentPartID]
WHERE
apl.ComponentPartID IS NOT NULL AND
apl.SourcePartID = ap.SourcePartID AND
p1.Commodity_ID IN (11) -- Commodity Code: 002
GROUP BY SourcePartID), 0
) AS [# of HR Devices] ,--0sec
COALESCE(
(SELECT COUNT(DISTINCT ComponentPartID)
FROM AllPartsList apl
LEFT JOIN visuser.EN_PART p1
ON p1.[EN_Part_ID] = apl.[ComponentPartID]
WHERE
apl.ComponentPartID IS NOT NULL AND
apl.SourcePartID = ap.SourcePartID AND
p1.Commodity_ID IN (5) -- Commodity Code: 007
GROUP BY SourcePartID), 0
) AS [# of 3rd Party Devices], --0sec
COALESCE(
(SELECT COUNT(DISTINCT ComponentPartID)
FROM AllPartsList apl
LEFT JOIN visuser.EN_PART p1
ON p1.[EN_Part_ID] = apl.[ComponentPartID]
WHERE
apl.ComponentPartID IS NOT NULL AND
apl.SourcePartID = ap.SourcePartID AND
p1.Commodity_ID IN (13) AND -- Commodity Code: 005
p1.MAKE_BUY_C = 'B'
GROUP BY SourcePartID
), 0) AS [# of Robots], --0sec
COALESCE(
(SELECT COUNT(*)
FROM visuser.[PART_COST] c
LEFT JOIN visuser.[EN_PART] p
ON p.[EN_PART_ID] = c.[EN_PART_ID]
WHERE
c.[STD_PO_Cost_N] > 0 AND
p.[MAKE_BUY_C] = 'B' AND
c.[EN_PART_ID] IN
(SELECT DISTINCT ComponentPartID FROM AllPartsList WHERE ComponentPartID IS NOT NULL AND SourcePartID = ap.SourcePartID)
), 0) AS [# of Buy Parts], --0sec
COALESCE(
(SELECT COUNT(*)
FROM visuser.[PART_COST] c
LEFT JOIN visuser.[EN_PART] p
ON p.[EN_PART_ID] = c.[EN_PART_ID]
WHERE
c.[STD_PO_Cost_N] > 0 AND
p.[MAKE_BUY_C] = 'M' AND
c.[EN_PART_ID] IN
(SELECT DISTINCT ComponentPartID FROM AllPartsList WHERE ComponentPartID IS NOT NULL AND SourcePartID = ap.SourcePartID)
), 0) AS [# of Make Parts]
INTO bomSummary
FROM AllPartsList ap
LEFT JOIN visuser.EN_PART p
ON p.[EN_Part_ID] = ap.[SourcePartID]
ORDER BY [PART_X]
SET @end = SYSDATETIME()
SET @processTime = DATEDIFF(s, @begin, @end)
PRINT @end
PRINT CHAR(10)+CHAR(13)
PRINT 'bomSummary Processing Time: ' + CONVERT(varchar, @processTime)
GO
以下是bombreakdown表的外观:
以及allpartslist表:
如果我注解掉函数行,两条记录需要1米20秒来处理,下面是执行计划的一部分。看起来每次合并都会增加4-6秒的处理时间。
如果我删除所有的合并,那么需要2分50秒来处理所有4981条记录。下面是它的执行列表:
执行计划建议了几个额外的索引,所以我添加了这些索引,现在1条记录需要0秒,2条记录需要5秒,10条记录需要1秒,100条记录需要2秒,1000条记录需要28秒,所有4981条记录需要4分17秒。额外的索引当然有帮助,我不再看到%s超过1000%,有几个仍然超过100%,这使我认为有一些更多的优化,可以做的,我只是不知道在哪里。执行计划是巨大的,所以这里只有几张照片:
不知道这两张唱片是怎么回事。这已经不是以前的90秒了,但至少现在结束了。
奇怪的是,它有(1000行受影响),然后(1行受影响)。我不知道那一排是什么,也不知道它从哪里来。我还是很想知道为什么做了这么少的改变会有这么大的不同。
我正在使用:
sql server 2019(v15.0.2070.41)
ssms版本18.5
以下是我根据allmhuran的建议修改的结果:
SELECT
DISTINCT ap.[SourcePartID] AS [Assembly Part ID],
p.[PART_X] AS [Assembly Part #],
p.[DESCR_X] AS [Assembly Part Description],
oa2.[Part Count],
oa2.[Total # of Parts],
([dbo].[fn_getFactoryStdCost](ap.[SourcePartID])) AS [Factory Std Cost],
oa2.[# of Docs],
oa2.[# of Software],
'Logic Pending' AS [# of Std Cost Items],
oa2.[# of HR Devices],
oa2.[# of 3rd Party Devices],
oa2.[# of Robots],
oa2.[# of Buy Parts],
oa2.[# of Make Parts]
FROM AllPartsList ap
LEFT JOIN visuser.EN_PART p
ON p.[EN_Part_ID] = ap.[SourcePartID]
OUTER APPLY (
SELECT
[Part Count] = COUNT( DISTINCT IIF( [Qty] = 0, null, [Component Part #]) ),
[Total # of Parts] = SUM([Qty]),
[# of Docs] = COUNT( DISTINCT IIF( [Commodity Code] IN ('009', '072', '073', '075', '079', '082'), [Component Part #], null) ), -- Commodity Codes: 009, 072, 073, 075, 079, 082 : Commodity ID: 15, 84, 85, 87, 81, 92
[# of Software] = COUNT( DISTINCT IIF( [Commodity Code] IN ('034'), [Component Part #], null) ), -- Commodity Code 034 : Commodity ID: 28
[# of HR Devices] = COUNT( DISTINCT IIF( [Commodity Code] IN ('002'), [Component Part #], null) ), -- Commodity Code 002 : Commodity ID: 11
[# of 3rd Party Devices] = COUNT( DISTINCT IIF( [Commodity Code] IN ('007'), [Component Part #], null) ), -- Commodity Code 007 : Commodity ID: 5
[# of Robots] = COUNT( DISTINCT IIF( ( [Commodity Code] IN ('005') AND [Make/Buy] = 'B' ), [Component Part #], null) ), -- Commodity Code 005 : Commodity ID: 13
[# of Buy Parts] = COUNT( DISTINCT IIF( [Make/Buy] = 'B', [Component Part #], null) ),
[# of Make Parts] = COUNT( DISTINCT IIF( [Make/Buy] = 'M', [Component Part #], null) )
FROM bomBreakdown
WHERE
[Component Part ID] IS NOT NULL AND
[SourcePartID] = ap.[SourcePartID] AND
--[SourcePartID] = ap.[AssemblyPartID] AND
ap.SourcePartID = 964
GROUP BY [SourcePartID]
) oa2
1条答案
按热度按时间llycmphe1#
好吧,抽点时间来看看。
标量函数重构
正如我在评论中提到的,标量函数对基于集合的操作不好。一般来说,如果你有
然后,这将通过覆盖下的痛苦行(rbar)操作将select变成一行。
您可以通过坚持使用基于集合的操作来提高性能。一种方法是将标量udf标记为
inline
,它基本上告诉sql它可以在生成查询计划之前将您的查询重写为:但是标量函数内联对于微软来说是一件很难解决的事情,而且仍然有一些缺陷。另一种方法是使用内联表值函数和
cross apply
或者outer apply
```create function inline_TVF(@i int) returns table as return
(
select result = @i * 2
)
select c = u.result
from t
outer apply inline_TVF(t.c) u;
select [Part Count] =
(
select count(distinct [Component Part #])
from bomBreakdown
where Qty > 0
and [Component Part ID] is not null
and SourcePartID = ap.SourcePartID
group by SourcePartID
),
[Total # of Parts] =
(
select sum(Qty)
from bomBreakdown
where [Component Part ID] is not null
and SourcePartID = ap.SourcePartID
group by SourcePartID
)
-- , more ...
select a = (
select x1 from y where z
),
b = (
select x2 from y where almost_z
)
select a = t.x1,
b = t.x2
from (
select x1 where z,
x2 where almost_z
from y
) t
select oa1.[Part Count],
oa1.[Total # of Parts]
into bomSummary
from AllPartsList ap
left join visuser.EN_PART p on p.EN_Part_ID = ap.SourcePartID
outer apply (
select [Part Count] = count
(
distinct iif
(
Qty = 0, null, [Component Part #]
)
),
[Total # of Parts] = sum(qty)
from bomBreakdown
where [Component Part ID] is not null
and SourcePartID = ap.SourcePartID
group by SourcePartID
)
oa1