t-sql 字符串唯一 ID(Northwind 数据库)
Posted
技术标签:
【中文标题】t-sql 字符串唯一 ID(Northwind 数据库)【英文标题】:t-sql string unique ID (Northwind database) 【发布时间】:2013-12-31 16:02:46 【问题描述】:一段时间以来,我一直在努力解决这个问题。
我在 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")
);
问题是 CustomerID 字段包含每个记录的唯一字符串(ALFKI、BERGS、BERGS 等)
我想创建一个存储过程,它将插入一行新数据并创建一个唯一的 CustomerID。内置函数是不可能的,因为我需要字符串长度为 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);
【问题讨论】:
【参考方案1】:这里的主要问题是,从生成的字符串中检测冲突并重试的增量成本会随着您生成的字符串越来越多而增加(因为您必须读取这些字符串中的所有确保您没有生成重复)。同时,重复命中的几率也会上升,这意味着桌子越大,这个过程就会越慢。
为什么需要在运行时生成唯一字符串?提前构建它们。 This article 和 this post 是关于随机数的,但基本概念是相同的。您建立一组独特的字符串,并在需要时从堆栈中拉出一个。在应用程序的整个生命周期内,您的冲突几率始终保持在 0%(前提是您构建了足够多的唯一值)。在您自己的设置中预先支付碰撞成本,而不是随着时间的推移逐渐增加(并且以用户等待这些尝试最终产生唯一编号为代价)。
这将生成 100,000 个唯一的 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 秒。还是不够? TOP (24)
将在大约 2 分钟内给你不到 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
是否存在等等。您唯一需要构建的额外内容是某种类型的检查,它会在您情绪低落时通知您。
顺便说一句,这些是 CustomerID 的可怕标识符。顺序代理键(例如 IDENTITY 列)有什么问题?包含所有这些努力的 5 位随机字符串如何比系统可以更轻松地为您生成的唯一数字更好?
【讨论】:
我非常喜欢这个。 1(真诚的)问题,SELECT
和DELETE
在可能百万行单列StringStack
表上的性能如何?如果 App 的许多实例几乎同时尝试执行 INSERTS,还有可能出现死锁吗?
@Shiva DELETE TOP (1)
将进行聚集索引查找以获取可用的第一行(并将选择最低的 ID 值)。根本没有一种更有效的方法可以从此表中选择一行。至于死锁,可能性很小。最常见的死锁通常是由于两个不同的事务试图以不同的顺序在两个不同的对象上获得锁(当然还有其他的)。在这种情况下,它是一个原子语句。 500 人可以同时尝试,他们会阻塞但不会死锁(除非涉及其他事务)。【参考方案2】:
Muhammed Ali 的答案很有效,但会证明资源相当密集(尤其是当剩下的 5 个字母组合不多时):您的函数使用随机生成器,需要一段时间才能找到未使用的组合,特别是因为它对其先前结果的记忆非常有限。 这意味着它会尝试,并且可能会给你一些类似的东西(有点夸张):第一次是 BAGER,第二次是 ANSWE,第三次是 BAGER。您会发现生成器一遍又一遍地为您提供相同的答案(尤其是超过 1200 万种可能的组合)会浪费大量时间。
如果您正在寻找一个固定长度的 ID(因为您使用 NCHAR(5),我想这是一个很好的假设),我宁愿研究构建一个包含所有可能组合的表,并从中选择一个值每次你需要一张桌子。一旦它被使用,您将删除它,或将其标记为已使用(出于可重用性的原因,我更愿意这样做)。
这导致了我的最终评论(我不能将其作为评论,因为我没有足够的声誉):为什么不使用 MS-SQL 提供的 IDENTITY 函数?这样可以更好地处理主键生成...
【讨论】:
是的,很抱歉,在发布我的 ^^ 之前我没有得到你的答案。如果我有这样做的声誉,我会为你 +1。 如果由我来决定,我会使用 int ID 和自动增量,但这是我老师的任务,让我们做从未在现实世界中使用过的任务 @BartoszJakubowiak,您老师的课程资料是否没有让您深入了解他们希望您如何解决这个问题?该网站适用于现实世界的编程问题,因为我们无法(像您一样)了解您的老师教给您的内容以及他们期望的解决方案类型。\【参考方案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 条件设置为始终为真,并且仅当您的两个要求都为真时才退出 while 循环,即Length of new ID is 5
和它does not exist in the customers table already
。
【讨论】:
随着客户表变大,读取所有这些值以检查重复项的成本会上升,您遇到重复项的几率也会上升。请参阅我的答案中的链接,以了解为什么此解决方案根本无法扩展。 @Bartosz 如果您的老师喜欢这个解决方案,您应该退出课程并要求退款。这是数据库人员根本不应该满足的解决方案类型。确保你给你的老师一个这个问题的链接——如果你不想从中学习,也许你应该给他们一个机会...... @AaronBertrand 我同意你的观点,当我读到你的评论“如果你的老师喜欢这个解决方案,你应该退出课程并要求退款”时,我大笑了起来 这是大学的必修课,所以我不能拿回我的钱,我没有付钱 :D 无论如何,@AaronBertrand 这个项目是为了通过当然,没有真正的好处。有兴趣的可以在这里查看解决方案link以上是关于t-sql 字符串唯一 ID(Northwind 数据库)的主要内容,如果未能解决你的问题,请参考以下文章