2013-12-13 77 views
2

我一直在努力,現在得到這個權利有一段時間了,沒有用。噸-SQL字符串唯一的ID(Northwind數據庫)

我在MSSQL數據庫中的表,我想使用存儲過程來插入新行

CREATE TABLE "Customers" (
"CustomerID" NCHAR(5) NOT NULL, 
"CompanyName" NVARCHAR(40) NOT NULL, 
"ContactName" NVARCHAR(30) NULL, 
"ContactTitle" NVARCHAR(30) NULL, 
"Address" NVARCHAR(60) NULL, 
"City" NVARCHAR(15) NULL, 
"Region" NVARCHAR(15) NULL, 
"PostalCode" NVARCHAR(10) NULL, 
"Country" NVARCHAR(15) NULL, 
"Phone" NVARCHAR(24) NULL, 
"Fax" NVARCHAR(24) NULL, 
PRIMARY KEY ("CustomerID") 
); 

的問題是它包含唯一的字符串每個記錄客戶ID字段(ALFKI,BERGS,BERGS等)

我想打一個存儲過程,而將插入新的數據行,並創建一個獨特的客戶ID。由於我需要字符串長度爲5個字符,所以構建函數是不成問題的。

我有其產生5個字符ID如下

begin 

declare @chars char(26) = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 
declare @i int = 0 
declare @id varchar(max) = '' 

while @i < 5 
begin 
     set @id = @id + substring(@chars, cast(ceiling(rand() * 26) as int), 1) 

    set @i = @i + 1 
end 

Select (cast(@id as nvarchar(400))) 

end 

一個過程,以及我試圖使工作,沒有使用的那個。它應該選擇一個唯一的ID(設置@id =「ANATR」有故意使其進入循環

begin 
declare @randID varchar(5) = '' 
declare @selectID varchar(20) = '' 
declare @chars char(26) = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 
declare @i int = 0 
declare @id varchar(10) = '' 

while @i < 5 
begin 
    set @id = @id + substring(@chars, cast(ceiling(rand() * 26) as int), 1)   
    set @i = @i + 1 
end 
select @id 
set @id = 'ANATR' 

SET @selectID = (SELECT CustomerID FROM CUSTOMERS WHERE CustomerID = @id) 


while @selectID <> 'NULL' 
begin 
    set @id = '' 
    while @i < 5 
     begin 
      set @id = @id + substring(@chars, cast(ceiling(rand() * 26) as int), 1)   
      set @i = @i + 1 
     end 

    SET @selectID = (SELECT CustomerID FROM CUSTOMERS WHERE CustomerID = @id) 

    SELECT @id 
end 


end 

這裏是插入過程我此刻

CREATE PROCEDURE [dbo].[InsertCustomers] 

(

@CustomerID nchar(5), 

@CompanyName nvarchar(40), 

@ContactName nvarchar(30) = NULL, 

@ContactTitle nvarchar(30) = NULL, 

@Address nvarchar(60) = NULL, 

@City nvarchar(15) = NULL, 

@Region nvarchar(15) = NULL, 

@PostalCode nvarchar(10) = NULL, 

@Country nvarchar(15) = NULL, 

@Phone nvarchar(24) = NULL, 

@Fax nvarchar(24) = NULL 

) 

AS 

SET NOCOUNT OFF; 

INSERT INTO [dbo].[Customers] ([CustomerID], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax]) VALUES (@CustomerID, @CompanyName, @ContactName, @ContactTitle, @Address, @City, @Region, @PostalCode, @Country, @Phone, @Fax); 

回答

-3

我相信你可以做這樣的事情,以確保您所有得到一個唯一的ID

begin 

declare @chars char(26) = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 
declare @i int = 0 
declare @id varchar(max) = '' 


while (1=1) 
begin 
     set @id = @id + substring(@chars, cast(ceiling(rand() * 26) as int), 1) 

    set @i = @i + 1 

    IF (NOT EXISTS(SELECT * FROM Customers WHERE CustomerID = @id) AND LEN(@id) = 5) 
     BREAK 
    ELSE 
     CONTINUE 
end 

Select (cast(@id as nvarchar(400))) 

end 

設置,而條件是總是正確,打破了while循環,只有當您的兩個要求是TRUE即Length of new ID is 5和它does not exist in the customers table already

+4

由於Customers表變大,閱讀所有這些值以檢查是否存在重複的成本上升,因爲這樣做你打一個重複的機率。請參閱我的答案中的鏈接,瞭解爲什麼此解決方案無法擴展。 –

+2

@Bartosz如果你的老師喜歡這個解決方案,你應該退出課程並要求退款。這是數據庫人員根本不應該滿意的解決方案類型。請確保你給你的老師一個鏈接到這個問題 - 如果你不想從中吸取教訓也許你應該給他們一個機會... –

+0

@AaronBertrand我同意你的看法,我笑我的頭當我讀了你評論「如果你的老師喜歡這個解決方案,你應該退出課程並要求退款'lollll –

7

這裏的主要問題是,從生成的字符串檢測碰撞的增量成本,再試,隨着你產生越來越多的字符串(因爲你必須閱讀這些字符串所有以確保你沒不生成重複)。與此同時,擊打副本的機率會增加,這意味着桌子越大,這個過程就會越慢。

爲什麼你需要在運行時生成的唯一的字符串?預先構建它們。 This articlethis post約隨機數,但基本概念是相同的。當你需要一個字符串時,你建立一組獨特的字符串,並從堆棧中取出一個字符串。在應用程序的整個生命週期中,您的碰撞機會保持恆定爲0%(假設您建立了足夠的唯一值堆棧)。在您自己的設置中支付預先碰撞的費用,而不是隨着時間的推移增加(並且以等待這些嘗試最終產生唯一數字的用戶爲代價)。

這將產生10萬獨特的5字符串,以約1秒的低,一次性費用(我的機器上):

;WITH 
a(a) AS 
(
    SELECT TOP (26) number + 65 FROM master..spt_values 
    WHERE type = N'P' ORDER BY number 
), 
b(a) AS 
(
    SELECT TOP (10) a FROM a ORDER BY NEWID() 
) 
SELECT DISTINCT CHAR(b.a) + CHAR(c.a) + CHAR(d.a) + CHAR(e.a) + CHAR(f.a) 
FROM b, b AS c, b AS d, b AS e, b AS f; 

這還不夠嗎?你可以通過改變TOP (10)TOP (20)產生約112萬唯一值。這花了18秒。仍然不夠?在大約2分鐘內,TOP (24)會給你帶來不到800萬的收入。它會得到成倍更貴,你產生更多的字符串,因爲DISTINCT必須做同樣的重複檢查你想要做一次添加客戶。

因此,創建一個表:

CREATE TABLE dbo.StringStack 
(
    ID INT IDENTITY(1,1) PRIMARY KEY, 
    String CHAR(5) NOT NULL UNIQUE 
); 

插入該設置:

;WITH 
a(a) AS 
(
    SELECT TOP (26) number + 65 FROM master..spt_values 
    WHERE type = N'P' ORDER BY number 
), 
b(a) AS 
(
    SELECT TOP (10) a FROM a ORDER BY NEWID() 
) 
INSERT dbo.StringStack(String) 
SELECT DISTINCT CHAR(b.a) + CHAR(c.a) + CHAR(d.a) + CHAR(e.a) + CHAR(f.a) 
FROM b, b AS c, b AS d, b AS e, b AS f; 

然後就是創建彈出一個出棧的過程,當你需要它:

CREATE PROCEDURE dbo.AddCustomer 
    @CustomerName VARCHAR(64) /* , other params */ 
AS 
BEGIN 
    SET NOCOUNT ON; 

    DELETE TOP (1) dbo.StringStack 
    OUTPUT deleted.String, @CustomerName /* , other params */ 
    INTO dbo.Customers(CustomerID, CustomerName /*, ...other columns... */); 
END 
GO 

不傻循環,無需要檢查是否CustomerID你剛纔生成的存在,等等。唯一增加你想要建立的東西是某種類型的支票,當你變低時通知你。

順便說一句,這是一個可怕的客戶ID標識。順序代理鍵(如IDENTITY列)有什麼問題?一個5位隨機字符串是如何涉及所有這些工作的,比系統爲您更容易生成的唯一編號更好嗎?

+0

我非常喜歡這個。 1(真誠的)問題,那麼在那個潛在的百萬行單列'StringStack'表中,SELECT和DELETE是什麼性能?如果有很多應用程序的實例在同一時間嘗試插入INSERT,那麼還有任何DEADLOCKS的機會? – Shiva

+1

@Shiva'DELETE TOP(1)'將執行聚集索引查找以獲得第一行可用(並將選擇最低的ID值)。根本沒有一種更有效的方法來從該表中選擇單個行。至於僵局,極不可能。最常見的死鎖通常是由兩個不同的事務產生的,這些事務試圖以不同的順序鎖定兩個不同的對象(當然還有其他的對象)。在這種情況下,這是一個原子聲明。 500人可以同時嘗試這種方式,他們會阻止但不會陷入僵局(除非涉及其他交易)。 –

3

穆罕默德阿里的答案很有用,但是會證明是相當資源密集的(特別是當剩下的5個字母沒有多少組合時):你的函數使用隨機生成器,它需要一段時間才能找到一個沒有使用的組合,特別是因爲它對以前的結果的記憶非常有限。 這意味着它會嘗試,並可能給你的那種(有一點誇張)的東西:BAGER第一次,然後ANSWE第二次,然後再BAGER第三次。你會發現你將會失去大量的時間與發電機給你相同的答案一遍又一遍(特別是超過12M的可能組合)。

如果你正在尋找一個固定長度的ID(因爲你使用NCHAR(5),我想這是一個好的假設),我寧願考慮建立一個包含所有可能的組合的表,並選擇一個值這張桌子每次你需要一張。一旦使用它,你可以刪除它,或者將其標記爲已使用(爲了可重用性的原因,我更喜歡這種方式)。

這導致我最後的意見(我不能把作爲評論,因爲我沒有足夠的聲望):爲什麼不使用的MS-SQL提供的身份功能?這提供了一個更好的處理主鍵生成的...

+0

+1這聽起來很像我的回答(但沒有代碼)。 :-) –

+0

是的,對不起,我沒有得到你的答案,然後張貼我的^^「如果我有這樣的聲譽,我會有你的+1。 – Nevoris

+0

如果是在我看來,我會使用int ID和自動增量,但這是一個任務形式,我的老師誰使我們做從未在現實世界的任務 – martodox