2011-02-07 202 views
0

當有8000行要處理時,此報告大約需要16秒。現在有50000行,報告需要2:30分鐘。SQL查詢優化

這是我第一次通過這個,客戶需要它,所以我按照需要完成的邏輯順序編寫了這段代碼,但沒有考慮優化。

現在隨着數據的增加,報告花費的時間越來越長,我需要再看看這個並優化它。我正在考慮索引視圖,表函數等

我認爲最大的瓶頸是循環通過臨時表,使4選擇語句,並更新臨時表... 50,000次。

我想我可以將所有這一切壓縮成一個大的SELECT,或者(a)4個連接到同一個表以獲得4個狀態,但是我不知道如何獲得TOP 1,或者我可以嘗試(b)使用嵌套子查詢,但與當前代碼相比,這兩者看起來都很混亂。我不希望任何人爲我編寫代碼,但是如果一些SQL專家可以仔細閱讀這段代碼,並告訴我任何明顯的低效率和替代方法,或者提高速度的方法,或者我應該使用的技術,而不是,將不勝感激。

PS:假設這個數據庫的大部分是規範化的,但設計不好,而且我無法添加索引。我基本上必須像現在一樣使用它。

代碼說(小於)我必須替換「小於」符號,因爲它裁剪了我的一些代碼。

謝謝!

 
CREATE PROCEDURE RptCollectionAccountStatusReport AS 

SET NOCOUNT ON; 

DECLARE @Accounts TABLE 
(
    [AccountKey] INT IDENTITY(1,1) NOT NULL, 
    [ManagementCompany] NVARCHAR(50), 
    [Association] NVARCHAR(100), 
    [AccountNo] INT UNIQUE, 
    [StreetAddress] NVARCHAR(65), 
    [State] NVARCHAR(50), 
    [PrimaryStatus] NVARCHAR(100), 
    [PrimaryStatusDate] SMALLDATETIME, 
    [PrimaryDaysRemaining] INT, 
    [SecondaryStatus] NVARCHAR(100), 
    [SecondaryStatusDate] SMALLDATETIME, 
    [SecondaryDaysRemaining] INT, 
    [TertiaryStatus] NVARCHAR(100), 
    [TertiaryStatusDate] SMALLDATETIME, 
    [TertiaryDaysRemaining] INT, 
    [ExternalStatus] NVARCHAR(100), 
    [ExternalStatusDate] SMALLDATETIME, 
    [ExternalDaysRemaining] INT 
); 

INSERT INTO 
    @Accounts (
    [ManagementCompany], 
    [Association], 
    [AccountNo], 
    [StreetAddress], 
    [State]) 
SELECT 
    mc.Name AS [ManagementCompany], 
    a.LegalName AS [Association], 
    c.CollectionKey AS [AccountNo], 
    u.StreetNumber + ' ' + u.StreetName AS [StreetAddress], 
    CASE WHEN c.InheritedAccount = 1 THEN 'ZZ' ELSE u.State END AS [State] 
FROM 
    ManagementCompany mc WITH (NOLOCK) 
JOIN 
    Association a WITH (NOLOCK) ON a.ManagementCompanyKey = mc.ManagementCompanyKey 
JOIN 
    Unit u WITH (NOLOCK) ON u.AssociationKey = a.AssociationKey 
JOIN 
    Collection c WITH (NOLOCK) ON c.UnitKey = u.UnitKey 
WHERE 
    c.Closed IS NULL; 

DECLARE @MaxAccountKey INT; 
SELECT @MaxAccountKey = MAX([AccountKey]) FROM @Accounts; 

DECLARE @index INT; 
SET @index = 1; 

WHILE @index (less than) @MaxAccountKey BEGIN 

DECLARE @CollectionKey INT; 
SELECT @CollectionKey = [AccountNo] FROM @Accounts WHERE [AccountKey] = @index; 

DECLARE @PrimaryStatus NVARCHAR(100) = NULL; 
DECLARE @PrimaryStatusDate SMALLDATETIME = NULL; 
DECLARE @PrimaryDaysRemaining INT = NULL; 
DECLARE @SecondaryStatus NVARCHAR(100) = NULL; 
DECLARE @SecondaryStatusDate SMALLDATETIME = NULL; 
DECLARE @SecondaryDaysRemaining INT = NULL; 
DECLARE @TertiaryStatus NVARCHAR(100) = NULL; 
DECLARE @TertiaryStatusDate SMALLDATETIME = NULL; 
DECLARE @TertiaryDaysRemaining INT = NULL; 
DECLARE @ExternalStatus NVARCHAR(100) = NULL; 
DECLARE @ExternalStatusDate SMALLDATETIME = NULL; 
DECLARE @ExternalDaysRemaining INT = NULL; 

SELECT TOP 1 
@PrimaryStatus = a.StatusName, @PrimaryStatusDate = c.StatusDate, @PrimaryDaysRemaining = c.DaysRemaining 
FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey 
WHERE c.CollectionKey = @CollectionKey AND a.StatusType = 'Primary Status' AND a.StatusName 'Cleared' 
ORDER BY c.sysCreated DESC; 

SELECT TOP 1 
@SecondaryStatus = a.StatusName, @SecondaryStatusDate = c.StatusDate, @SecondaryDaysRemaining = c.DaysRemaining 
FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey 
WHERE c.CollectionKey = @CollectionKey AND a.StatusType = 'Secondary Status' AND a.StatusName 'Cleared' 
ORDER BY c.sysCreated DESC; 

SELECT TOP 1 
@TertiaryStatus = a.StatusName, @TertiaryStatusDate = c.StatusDate, @TertiaryDaysRemaining = c.DaysRemaining 
FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey 
WHERE c.CollectionKey = @CollectionKey AND a.StatusType = 'Tertiary Status' AND a.StatusName 'Cleared' 
ORDER BY c.sysCreated DESC; 

SELECT TOP 1 
@ExternalStatus = a.StatusName, @ExternalStatusDate = c.StatusDate, @ExternalDaysRemaining = c.DaysRemaining 
FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey 
WHERE c.CollectionKey = @CollectionKey AND a.StatusType = 'External Status' AND a.StatusName 'Cleared' 
ORDER BY c.sysCreated DESC; 

UPDATE 
    @Accounts 
SET 
    [PrimaryStatus] = @PrimaryStatus, 
    [PrimaryStatusDate] = @PrimaryStatusDate, 
    [PrimaryDaysRemaining] = @PrimaryDaysRemaining, 
    [SecondaryStatus] = @SecondaryStatus, 
    [SecondaryStatusDate] = @SecondaryStatusDate, 
    [SecondaryDaysRemaining] = @SecondaryDaysRemaining, 
    [TertiaryStatus] = @TertiaryStatus, 
    [TertiaryStatusDate] = @TertiaryStatusDate, 
    [TertiaryDaysRemaining] = @TertiaryDaysRemaining, 
    [ExternalStatus] = @ExternalStatus, 
    [ExternalStatusDate] = @ExternalStatusDate, 
    [ExternalDaysRemaining] = @ExternalDaysRemaining 
WHERE 
    [AccountNo] = @CollectionKey; 

SET @index = @index + 1; 

END; 

SELECT 
    [ManagementCompany], 
    [Association], 
    [AccountNo], 
    [StreetAddress], 
    [State], 
    [PrimaryStatus], 
    CONVERT(VARCHAR, [PrimaryStatusDate], 101) AS [PrimaryStatusDate], 
    [PrimaryDaysRemaining], 
    [SecondaryStatus], 
    CONVERT(VARCHAR, [SecondaryStatusDate], 101) AS [SecondaryStatusDate], 
    [SecondaryDaysRemaining], 
    [TertiaryStatus], 
    CONVERT(VARCHAR, [TertiaryStatusDate], 101) AS [TertiaryStatusDate], 
    [TertiaryDaysRemaining], 
    [ExternalStatus], 
    CONVERT(VARCHAR, [ExternalStatusDate], 101) AS [ExternalStatusDate], 
    [ExternalDaysRemaining] 
FROM 
@Accounts 
ORDER BY 
    [ManagementCompany], 
    [Association], 
    [StreetAddress] 
ASC; 

+0

您正在使用什麼版本的SQL Server? – Lamak 2011-02-07 17:32:03

+0

SQL 2008 R2是該版本。 – Blackcoil 2011-02-07 17:35:16

回答

3

首先使用臨時表而不是可變表。這些可以被索引。

接下來,不要循環!幾乎在所有情況下,循環對性能都不利。對於50000條記錄,這個循環運行50000次而不是一次,當你有一百萬條記錄時,這將會是可怕的!這是一個鏈接,可以幫助您瞭解如何進行基於集合的處理。它的寫法是爲了避免cursos,但循環與光標相似,所以它應該有所幫助。 http://wiki.lessthandot.com/index.php/Cursors_and_How_to_Avoid_Them

而且(nolock)會給出骯髒的數據讀取,這可能對報告非常不利。如果您的SQl Server版本高於2000,則有更好的選擇。

4
  1. 不要試圖去猜測其中查詢是哪裏錯了 - 看看執行計劃。它會告訴你什麼是咀嚼你的資源。

  2. 您可以直接從另一個表更新,即使從表變量:SQL update from one Table to another based on a ID match

這將使你在你的循環都合併成一個單一的(塊狀)語句。您可以使用不同的別名,如加入爲二級和三級狀態相同的表,

JOIN AccountStatus As TertiaryAccountStatus...AND a.StatusType = 'Tertiary Status' 
JOIN AccountStatus AS SecondaryAccountStatus...AND a.StatusType = 'Secondary Status' 
  • 我敢打賭,你沒有對AccountStatus的索引。 StatusType字段。您可以嘗試使用該表的PK。
  • HTH。

    2
    SELECT @CollectionKey = [AccountNo] FROM @Accounts WHERE [AccountKey] = @index; 
    

    此查詢將受益於您的表變量上的PRIMARY KEY聲明。

    • 當您說IDENTITY時,您要求數據庫自動填充該列。
    • 當您說PRIMARY KEY時,您要求數據庫將數據組織到聚集索引中。

    這兩個概念有很大的不同。通常,你應該使用它們兩個。

    DECLARE @Accounts TABLE 
    (
        [AccountKey] INT IDENTITY(1,1) PRIMARY KEY, 
    

    我無法添加索引。

    在這種情況下,將數據複製到可以添加索引的數據庫中。並使用:SET STATISTICS IO ON