1

我正在使用SQL中的函數,我第一次需要幫助優化。我正在使用SQL Server 2016.sql優化表值函數

我的函數返回一個表變量,比較一年中每個月不同項目的計劃和完成的僱員小時數。但問題是查詢在30-40s內加載大約30,000行。我查了一些關於優化查詢的建議,但是我在代碼中找不到任何錯誤。你能給我一些建議如何優化它?

整個功能的代碼:

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 [email protected]    

    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  

rbh子查詢得到的大多數列的值,比如項目號碼,用戶數據等

第一左JOIN(rbhp)得到的時間之和該用戶計劃在明確的任務和月份中投入項目(返回表中的列PlannedHours)。

第二個LEFT JOIN(rbhw)獲取用戶在確定任務和月份中實際用於項目的小時數(返回表中的列DoneHours)。

+0

將函數轉換爲內聯表值函數。這可能會提高性能。 –

+0

這是一個巨大的查詢。你的源表如何?我會首先優化它們的索引 - 確保您用來加入表的字段被索引。 –

+2

在SSMS中,打開「包含實際執行計劃」。手動運行內部查詢('SELECT'部分)(正確聲明和設置變量),查看查詢計劃。最有可能的是,您可以看到一些非常粗體的箭頭 - 這些指向您查詢零件,需要優化。通常你需要創建一些(覆蓋)索引,有時候會改變邏輯;在'JOIN'條件下使用自定義函數也可以成爲性能殺手。 – Arvo

回答

0

我在這裏看到一個問題。您聲明瞭一個表變量,然後創建了一個複合主鍵,然後對該表變量執行INSERT。

首先,如果你做了很多DML操作,聚集索引不是一個好的選擇。

其次,你已經有了id列,這是一個標識列,爲什麼你仍然需要這些組合列作爲主鍵。

我認爲,如果不是必要的,你可以放棄這個複合主鍵,然後它會提高你的性能很多。

+0

不幸的是它沒有幫助:(可能最大的問題是在select語句中。 – boogie

0

實際計劃中顯示的成本是根據估計的行數估算的,可能完全錯誤。至少使用設置的統計信息來查看哪個表導致大多數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) 

你應該總是嘗試將實際值,而無需使用功能比較,如日期> = xxx和日期< = YYY

的參數的默認值:

@Project = '%',      
@Task = '%', 

表明,當你不想限制項目或任務時,你正在通過%,而當你這樣做的時候,你正在做其他事情。這可能最終導致參數嗅探的情況很容易,當你要使用完全錯誤的計劃時,因爲它針對不同的情況進行了優化 - 因爲你也使用類似+標量函數的參數,你甚至不會在任何情況下都會有很好的索引使用。如果您將查詢拆分爲更小的部分並使用臨時文件,它可能會有所幫助。表,但這需要了解你的數據庫並測試它的行爲。