我第一次使用SQL中的函数,我需要一个优化帮助。我正在使用SQL Server 2016。
我的函数返回一个表变量,用于比较一年中每个月不同项目的员工的计划和完成时间。但问题是查询加载大约30,000行30-40s。我已经检查了一些关于查询优化的建议,但我在代码中找不到任何错误。你能给我一些如何优化它的建议吗?
整个功能代码:
od -t x1
CREATE FUNCTION dbo.fnProjectHours(
@Project = '%',
@Task = '%',
@Year INT = 0
)
RETURNS @temp TABLE
(
Year INT, Month INT, Project VARCHAR(20), Task VARCHAR(20),
User VARCHAR(50), PlannedHours Numeric(14,2),
DoneHours Numeric(14,2) id int identity ,
primary key(Year, Project, Task, Month, User, id)
)
AS
BEGIN
SELECT @Year= ISNULL(NULLIF(@Year,0),DATEPART(yy,GETDATE()));
INSERT INTO @t
(
Year, Month, Project, Task, User, PlannedHours, DoneHours
)
SELECT rbh.Year, rbh.Month, rbh.Project, rbh.Task, rbh.User,
rbhp.SumPlan AS PlannedHours, rbhw.SumDone AS DoneHours
FROM
(
SELECT
CASE
WHEN DATEPART(yy, ll.DateStart) IS NULL THEN rbhw.Year
ELSE DATEPART(yy, ll.DateStart)
END AS Year,
CASE
WHEN DATEPART(mm, ll.DateStart) IS NULL THEN DATEPART(mm, rbhw.Date)
ELSE DATEPART(mm, ll.DateStart)
END AS Month,
dbo.wusr_fn_cut(mn.Number, '/') AS Project, ml.Task,
ISNULL(ll.Login,rbhw.Login) AS User
FROM dbo.Nag AS mn WITH (nolock)
INNER JOIN dbo.Lin AS ml WITH (NOLOCK) ON mn.ID = ml.ID
INNER JOIN dbo.LinLogin AS ll WITH (NOLOCK) ON ll.ID = ml.ID
AND ll.LinId = ml.LinId
INNER JOIN dbo.sl_Operator AS o WITH (Nolock) ON ll.Login = o.Login
FULL OUTER JOIN dbo.Hours AS rbhw WITH (NOLOCK)
ON dbo.wusr_fn_cut(mn.Number, '/') = rbhw.Project AND ml.Task = rbhw.Task
AND ll.Login = rbhw.Login AND DATEPART(yy, ll.DateStart) = DATEPART(yy, rbhw.Date)
AND DATEPART(mm, ll.DateStart) = DATEPART(mm, rbhw.Date)
WHERE (mn.Number IS NOT NULL) AND (mn.Status = 0) AND dbo.wusr_fn_cut(mn.Number, '/') LIKE @Project
AND ml.Task LIKE @Task
UNION ALL
SELECT
CASE
WHEN DATEPART(yy, ll.DateStart) IS NULL THEN rbhw.Year
ELSE DATEPART(yy, ll.DateStart)
END AS Year,
CASE
WHEN DATEPART(mm, ll.DateStart) IS NULL THEN DATEPART(mm, rbhw.Date)
ELSE DATEPART(mm, ll.DateStart)
END AS Month,
rbhw.Project, rbhw.Task,
ISNULL(ll.Login,rbhw.Login) AS User
FROM dbo.Nag AS mn WITH (nolock)
INNER JOIN dbo.Hours AS rbhw WITH (NOLOCK)
ON dbo.wusr_fn_cut(mn.Number, '/') = rbhw.Project
INNER JOIN dbo.Lin AS ml WITH (NOLOCK) ON mn.ID = ml.ID
AND rbhw.Task = ml.Task
INNER JOIN dbo.Operator AS o WITH (Nolock) ON rbhw.Login = o.Login
FULL OUTER JOIN dbo.LinLogin AS ll WITH (nolock) ON mn.ID = ll.ID
AND ml.LinId = ll.LinId AND o.Login = ll.Login
AND DATEPART(yy, rbhw.Date)=DATEPART(yy, ll.DateStart)
AND DATEPART(mm, rbhw.Date) = DATEPART(mm, ll.DateStart)
WHERE (rbhw.Project IS NOT NULL) AND (mn.Status = 0) AND (DATEPART(mm, ll.DateStart) IS NULL) AND rbhw.Project LIKE @Project
AND rbhw.Task LIKE @Task
) AS rbh
LEFT JOIN
(
SELECT DATEPART(yy, ll.DateStart) AS Year, DATEPART(mm, ll.DateStart) AS Month,
dbo.wusr_fn_cut(mn.Number, '/') AS Project, ml.Task AS Task,
ll.Login AS LoginLL, SUM(ll.Hours) AS SumPlan
FROM dbo.Nag AS mn WITH (nolock)
INNER JOIN dbo.Lin AS ml WITH (NOLOCK) ON mn.ID = ml.ID
INNER JOIN dbo.LinLogin AS ll WITH (NOLOCK) ON ll.ID = ml.ID
AND ll.LinId = ml.LinId
WHERE mn.Status=0
GROUP BY DATEPART(yy, ll.DateStart),DATEPART(mm, ll.DateStart),dbo.wusr_fn_cut(mn.Number, '/'),ml.Task,ll.Login
) AS rbhp
ON rbh.Project=rbhp.Project AND rbh.Task=rbhp.Task AND
rbh.Year=rbhp.Year AND rbh.Month=rbhp.Month AND rbh.User=rbhp.LoginLL
LEFT JOIN
(
SELECT h.Year, DATEPART(mm, h.Date) AS Month,h.Project AS Project, h.Task AS Task,
h.Login AS LoginRbhw, SUM(h.Hours) AS DoneSum
FROM dbo.Nag AS mn WITH (nolock)
INNER JOIN dbo.Lin AS ml WITH (NOLOCK) ON mn.ID = ml.ID
INNER JOIN dbo.Hours AS h WITH (NOLOCK) ON dbo.wusr_fn_cut(mn.Number, '/') = h.Project
AND ml.Task = h.Task
WHERE mn.Status=0
GROUP BY h.Year,DATEPART(mm, h.Date),h.Project,h.Task,h.Login
) AS rbhw
ON rbh.Project=rbhw.Project AND rbh.Task=rbhw.Task AND
rbh.Year=rbhw.Year AND rbh.Month=rbhw.Month AND rbh.User=rbhw.LoginRbhw
WHERE rbh.Month IS NOT NULL AND rbh.Year=@Year
GROUP BY rbh.Year, rbh.Month, rbh.Project, rbh.Task, rbh.User,rbhp.SumPlan, rbhw.DoneSum
ORDER BY rbh.Project, rbh.Task, rbh.User, rbh.Month
RETURN
END
子查询获取大多数列的值,例如项目编号,用户数据等。
第一个LEFT JOIN(rbh
)获取用户计划在明确任务和月份的项目上花费的小时数(返回表中的列rbhp
)。
第二个LEFT JOIN(PlannedHours
)获取用户在明确任务和月份中实际花费在项目上的小时数(返回表中的列rbhw
)。
答案 0 :(得分:0)
我在这里看到一个问题。您声明了一个表变量,然后创建了一个复合主键,然后对该表变量执行INSERT。
首先,如果你做了很多DML操作,聚集索引不是一个好的选择。
其次,您已经拥有了id列,这是一个标识列,为什么您仍然需要这些复合列作为主键。
我认为如果没有必要,你可以放弃那个复合主键,然后它会提升你的表现。
答案 1 :(得分:0)
实际计划中显示的成本是基于估计行数的估算值,可能完全错误。至少使用set statistics io来查看哪个表导致大多数I / O.
标量函数通常也很糟糕,并且您在整个地方使用wusr_fn_cut
。您无法在查询计划或统计信息中看到它对性能的影响,您需要使用例如计划缓存。
索引假脱机意味着SQL Server已创建临时索引到temp。 db因为它没有足够好的索引可供使用。如果你看到了,你肯定应该尝试索引原始表,以便不需要假脱机。
这种where子句也很糟糕,因为它不可篡改。
AND DATEPART(yy, rbhw.Date)=DATEPART(yy, ll.DateStart)
AND DATEPART(mm, rbhw.Date) = DATEPART(mm, ll.DateStart)
您应该始终尝试比较实际值而不使用函数,例如Date> = xxx和Date< = yyy
参数的默认值:
@Project = '%',
@Task = '%',
建议您在不希望限制项目或任务时以及当您执行其他操作时通过%
。这可能很容易在参数嗅探情况下结束,当您将使用完全错误的计划时,因为它针对不同的情况进行了优化 - 因为您还使用类似+标量函数的参数,您和&#c; #39;在任何情况下都不会有非常好的索引使用。如果您将查询分成更小的部分并使用temp,这可能会有所帮助。表,但这需要了解您的数据库并测试它的行为方式。