Sql-Server

生成按欄位分區或分組的序列

  • November 2, 2019

給定一個像這樣的表:

Id     Grouping    SequenceId    
1         A            1         
2         B            1         
3         A            2         
4         A            3         
5         C            1         
6         B            2

其中 SequenceId 基於 Grouping 遞增,例如DENSE_RANK()

我想有效地生成下一個 SequenceId INSERT。SQL Server 有SEQUENCE,但我需要每個分組的序列。我還可以創建一個如下所示的表(為了靈活性,可能還有更多列):

Grouping     NextValue
  A             4
  B             3
  C             2

以及一個以原子方式獲取下一個值並更新表的過程,但這不會像 a 那樣有效,SEQUENCE而且感覺很像重新發明輪子。我只是還沒有找到我要找的輪子。

當序列的數量可能很大並且我想避免在讀取時像這樣的聚合函式時對性能造成影響時,為每個分組生成序列的有效且可擴展的方法是DENSE_RANK()什麼?

最終,最佳解決方案將取決於您需要什麼的具體細節,以及各種解決方案在您的環境中的執行方式。

首先:使用一個SEQUENCE或一個虛擬“序列”表將導致行為更接近於ROW_NUMBER- DENSE_RANKwith DENSE_RANK,如果您訂購的值(例如,記錄創建日期)對於分區中的三個記錄是相同的,它們都會獲得相同的排名,但他們每個人都會獲得唯一的行號。

其次,即使使用 a SEQUENCE,您也不能保證編號中沒有間隙。如果您從序列中獲取下一個值,但最終回滾事務,則序列號仍會被消耗。

在您的情況下,另一個問題是管理序列。您可能需要為您創建的每個組創建一個序列。創建和使用序列需要動態 SQL。它可能不易受到 SQL 注入的影響(如果所有動態值都是系統生成的,而不是使用者輸入生成的),但這仍然是一些人關心的問題。

此外,您需要一種方法來確保這些Grouping值正確地與序列相關聯。如果這些值是面向使用者的,那麼有人會想在某個時候更改一個 - 如果序列名稱取決於該面向使用者的值,那麼原始序列將在更改後失去,而新的序列可能必須開始(或者,您必須包含程式碼以在更改後更新序列名稱)。更實際地,您可能需要一個帶有GroupingID 和對應名稱的表SEQUENCE

使用“虛擬序列”表可以避免其中一些問題,但可以創建另一個更大的問題——阻塞。如果您包括從虛擬序列中檢索下一個值並在事務中更新它以創建您的記錄,那麼,如果事務被回滾,那麼序列號也會發生變化;但是,在第一個事務完成之前,您不能向表中添加另一條記錄。我使用過這樣的表,幾乎我們遇到的每一個死鎖都會回到那個表。而且,如果您在插入新記錄的事務之外獲取下一個 ID,那麼您可能會回到 ID 範圍內的空白。

當然,您應該考慮到間隙也可能自然發生 - 每當刪除記錄時,就會出現間隙。是否可以容忍/期望這些差距,或者流程的業務所有者是否真的希望看到 1、2、3、4、5,即使這些行實際上是 1、3、4、6、7(因為 2 和 5 是刪除)?

因此,找出需求背後的實際原因是什麼。如果絕對需要讓報告顯示沒有任何間隙的序列,那麼ROW_NUMBER(或者,可能,如果您想要相同的 tie 值,DENSE_RANK)可能是您可用的最佳解決方案,我會檢查如何它導致的大部分性能受到影響。如果應該保留一些間隙(特別是對於已刪除的行),您幾乎必須使用上面建議的選項之一。如果回滾在您的應用程序中很少見(我見過一些情況確實如此),那麼SEQUENCE可能是你最好的選擇,性能明智。“虛擬序列”表必須非常小心地處理,否則可能會成為主要的性能瓶頸;但是,它可以工作(我應該注意,我之前提到的大多數死鎖問題發生在處理批量數據插入的作業中,而不是在應用程序中的正常使用者活動中,以及用於獲取一系列值而不是一次獲取一個值的附加常式被創建 - 但是,使用這些增加了再次跳過值的可能性)。

確保提出此要求的業務所有者了解提供無間隙的永久序列值會對性能等產生重大影響,並查看該要求是否真的是必須具備的,或者是必須具備的(或者,實際上需要ROW_NUMBERDENSE_RANK)。

引用自:https://dba.stackexchange.com/questions/174606