2017-04-20 50 views
3

首先發布的問題,我提前道歉,任何失誤。 該表包含分配給團隊的記錄,初始分配由另一個進程完成。通常情況下,我們必須重新分配代理商的記錄,並將其平均分配給團隊的其他成員。我們一個接一個地手工完成,這很麻煩。所以我想出了這個解決方案:SQL Server 2008是否有更高效的方式來執行此更新循環?

DECLARE @UpdtAgt TABLE (ID INT, Name varchar(25)) 
    INSERT INTO @UpdtAgt 
    VALUES (1, 'Gandalf') 
      ,(2,'Hank') 
      ,(3,'Icarus') 


    CREATE TABLE #UpdtQry (TblID varchar(25)) 
    INSERT INTO #UpdtQry 
    SELECT ShtID 
    FROM TestUpdate 

    DECLARE @RowID INT 
    DECLARE @AgtID INT 
    DECLARE @Agt varchar(25) 
    DECLARE @MaxID INT 
    SET @MaxID = (SELECT COUNT(*) FROM @UpdtAgt) 
    SET @AgtID = 1 

    --WHILE ((SELECT COUNT(*) FROM #UpdtQry) > 0) 
    WHILE EXISTS (SELECT TblID FROM #UpdtQry) 

    BEGIN 
    SET @RowID = (SELECT TOP 1 TblID FROM #UpdtQry) 
    SET @Agt = (SELECT Name FROM @UpdtAgt WHERE ID = @AgtID) 

    UPDATE TestUpdate 
    SET Assignment = @Agt 
    WHERE ShtID = @RowID 

    DELETE #UpdtQry WHERE TblID = @RowID 

    IF @AgtID < @MaxID 
     SET @AgtID = @AgtID + 1 
    ELSE 
     SET @AgtID = 1 


    END 

    DROP TABLE #UpdtQry 

這實際上是我第一次嘗試這樣做的深入。 100行的更新大約需要30秒。 UPDATE表TestUpdate只有CLUSTERED索引。我怎樣才能讓這更高效?

編輯:我沒有在我的解釋中很好地定義@UpdtAgt和#UpdtQry表。 @UpdtAgt將持有正在重新分配記錄的代理,並且每次使用時都可能會更改。 #UpdtQry將有一個WHERE子句來定義哪些代理記錄將被重新分配,同樣,這將隨着每次使用而改變。我希望這能讓這個更清楚一點。再一次,道歉沒有得到正確的第一次。

編輯2:我評論了舊的WHILE子句並插入了HABO建議的子句。再次感謝HABO。

+0

哪裏是'TestUpdate'表結構?看起來像一個'CTE'可以完成這項工作 – Sami

+0

當檢查一行或多行的存在時,使用['EXISTS']會更高效(https://msdn.microsoft.com/zh-cn/library/ms188336 .aspx)而不是獲取確切的[COUNT](https://msdn.microsoft.com/en-us/library/ms175997.aspx),然後檢查它是否大於零。通常,不使用ORDER BY,使用'TOP'會被忽視。 – HABO

+0

HOLY CRAP!那樣做了!在WHILE中使用EXISTS,而不是SELECT COUNT(*),從30秒到1秒以內。非常感謝你! – EFrost

回答

1

我認爲這是你在找什麼:

DECLARE @UpdtAgt TABLE 
(
    ID INT, 
    Name VARCHAR(25) 
) 

INSERT @UpdtAgt 
VALUES (1, 'Gandalf') 
     ,(2, 'Hank') 
     ,(3, 'Icarus') 

UPDATE t 
SET t.Assignment = a.Name 
FROM TestUpdate AS t 
INNER JOIN @UpdtAgt AS a 
    ON t.ShtID = a.ID 

這應該立刻完成所有的4行。

P.S ...

如果你創建一個像你原來的職位在今後的表,請儘量保持你的列和變量與他們的目的是一致的命名!

在您的例子,你用IDAgtIDShtID和(最容易混淆)TblID(我想他們是同樣的事情?[請糾正我,如果我錯了!])。如果你將它稱爲AgtID(並且@AgtID代表變量[對@RowID沒有真正的需求]),那麼一眼就可以很容易地看到發生了什麼! AssignmentName也是一樣的。

+0

感謝您的回答,並提供關於命名的建議!我不確定這會起作用,TestUpdate在生產中是一個體面大小的表,超過10萬個,而且我只希望更新它的一小部分。 #UpdtQry表將引入哪個部分,即需要更新的部分。對不起,如果我原來的帖子不清楚。在生產中,將在#UpdtQry臨時表中創建WHERE子句。 – EFrost

0

因爲這是你第一次嘗試這樣的事情,所以我想祝賀你的作品。雖然它不是理想的(什麼是?)它符合主要目標:它的工作原理。有一種更好的方法可以使用一種稱爲遊標的方法來完成此操作。我提醒自己使用微軟的以下頁面的正確語法:Click here for full instruction on cursors

話雖如此,這篇文章末尾的代碼顯示我快速解決您的情況。請注意以下幾點:

  1. 定義了@TestUpdate表,以便在不使用永久表的情況下在MSSQL中運行查詢。
  2. 只有@UpdtAgt表需要設置爲臨時表。但是,如果這是經常使用的,最好將它作爲永久性表格。
  3. CLOSEDEALLOCATE最後的陳述是重要 - 忘記這些會有相當不愉快的後果。
DECLARE @TestUpdate TABLE (ShtID int, Assignment varchar(25)) 

INSERT INTO @TestUpdate 
    VALUES (1,'Fred') 
    ,(2,'Barney') 
    ,(3,'Fred') 
    ,(4,'Wilma') 
    ,(5,'Betty'),(6,'Leopold'),(7,'Frank'),(8,'Fred') 

DECLARE @UpdtAgt TABLE (ID INT, Name varchar(25)) 

INSERT INTO @UpdtAgt 
VALUES (1, 'Gandalf') 
     ,(2,'Hank') 
     ,(3,'Icarus') 

DECLARE @recid int 
DECLARE @AgtID int SET @AgtID=0 
DECLARE @MaxID int SET @MaxID = (SELECT COUNT(*) FROM @UpdtAgt) 

DECLARE assignment_cursor CURSOR 
FOR SELECT ShtID FROM @TestUpdate 

OPEN assignment_cursor 
FETCH NEXT FROM assignment_cursor 
INTO @recid 

WHILE @@FETCH_STATUS = 0 
BEGIN 
    SET @AgtID = @AgtID + 1 
    IF @AgtID > @MaxID SET @AgtID = 1 

    UPDATE @TestUpdate 
    SET Assignment = (SELECT TOP 1 Name FROM @UpdtAgt WHERE [email protected]) 
    FROM @TestUpdate TU 
    WHERE [email protected] 

    FETCH NEXT FROM assignment_cursor INTO @recid 
END 

CLOSE assignment_cursor 
DEALLOCATE assignment_cursor 

SELECT * FROM @TestUpdate 
+0

很多我讀過的東西都不鼓勵使用遊標,但我對它們的使用經驗很少。我誤解了遊標嗎? – EFrost

+0

如果過度使用遊標,需要多個人同時運行相同的遊標,或者您正在不斷運行遊標,則遊標可能很糟糕。在你的情況下,光標應該比你當前的解決方案有更好的性能。 –

相關問題