2013-12-13 1 views
2

Я пытаюсь получить это право в течение некоторого времени без использования.t-sql string уникальный идентификатор (база данных 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") 
); 

Проблема состоит в том CustomerID поле, которое содержит уникальную строку для каждой записи (ALFKI, айсберги, 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 = «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

Я считаю, что вы можете сделать что-то вроде этого, чтобы убедиться, что вы все получите уникальный идентификатор

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 только тогда, когда оба ваших требований истинны т.е. Length of new ID is 5 и его does not exist in the customers table already.

+4

По мере того, как таблица Customers становится больше, стоимость чтения всех этих значений для проверки дубликата возрастает, как и ваши шансы на попадание в дубликат. См. Ссылки в моем ответе, чтобы понять, почему это решение просто не будет масштабироваться. –

+2

@ Bartosz, если вашему учителю нравится это решение, вы должны выйти из курса и попросить вернуть свои деньги. Это тип решения, с которым человек базы данных просто не должен быть удовлетворен. Удостоверьтесь, что вы даете своему учителю ссылку на этот вопрос - если вы не хотите учиться на нем, возможно, вы должны дать им шанс ... –

+0

@AaronBertrand Я согласен с вами, и я смеялся, когда я читал ваши комментарий ", если вашему учителю нравится это решение, вы должны выйти из курса и попросить вернуть свои деньги 'lollll –

7

Основная проблема здесь заключается в том, что дополнительные затраты на обнаружение столкновений с генерируемой строки, и попробуйте еще раз, увеличивается, как вы генерировать больше и больше строк (так как вы должны читать все этих строк, чтобы убедиться, что вы Бесполезный 'генерировать дубликат). В то же время шансы на попадание дубликата повышаются, что означает, что чем больше получается таблица, тем медленнее этот процесс будет получен.

Зачем вам нужно создавать уникальную строку во время выполнения? Стройте их все заранее. This article и this post - это случайные числа, но основная концепция такая же. Вы создаете набор уникальных строк и вытаскиваете один из стека, когда вам это нужно. Ваш шанс столкновения остается постоянным на 0% на протяжении всего срока службы приложения (при условии, что вы создаете стек из достаточно уникальных значений). Оплачивайте расходы на коллизии впереди, в своей собственной настройке, а не постепенно с течением времени (и за счет того, что пользователь ждет этих попыток, наконец, даст уникальный номер).

Это будет генерировать 100000 уникальных строк 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; 

этого не достаточно? Вы можете генерировать около 1.12 миллиона уникальных значений, изменяя TOP (10) на TOP (20). Это заняло 18 секунд. Все еще недостаточно? TOP (24) даст вам чуть меньше 8 миллионов за 2 минуты. Он будет экспоненциально дороже, поскольку вы создаете больше строк, потому что DISTINCT должен сделать ту же проверку дубликатов, которую вы хотите сделать каждыеsingle время, когда вы добавляете клиента.

Таким образом, создать таблицу:

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-значная случайная строка со всеми этими усилиями, лучше, чем уникальный номер, который система может генерировать для вас гораздо легче?

+0

Мне это очень нравится. 1 (искренний) вопрос, но как выглядит производительность на 'SELECT', а затем' DELETE' на этой потенциально миллионной строке столбца 'StringStack'? Также любой шанс DEADLOCKS, если многие экземпляры приложения пытаются делать INSERTS в одно и то же время? – Shiva

+1

@Shiva 'DELETE TOP (1)' собирается сделать кластеризованный индекс, чтобы получить самую первую доступную строку (и выберем самое низкое значение ID). Просто нет более эффективного способа выбрать одну строку из этой таблицы. Что касается тупиков, маловероятно.Наиболее распространенные взаимоблокировки обычно возникают в результате двух разных транзакций, пытающихся получить блокировки на двух разных объектах в разном порядке (есть, конечно, другие). В этом случае это одно атомное утверждение. 500 человек могут попробовать это одновременно, и они будут блокировать, но не блокировать (если не задействованы другие транзакции). –

3

Ответ Мухаммеда Али работает, но будет достаточно интенсивно работать (особенно, когда осталось несколько комбинаций из 5 букв, которые нужно использовать): ваша функция использует случайный генератор, и потребуется некоторое время, чтобы найти комбинация, которая не используется, тем более, что она имеет очень ограниченную память о ее предыдущих результатах. Это означает, что он попытается и может дать вам что-то вроде этого (преувеличивая немного): BAGER в первый раз, затем ANSWE второй раз, затем снова BAGER в третий раз. Вы видите, что вы потеряете много времени, когда генератор даст вам один и тот же ответ снова и снова (особенно по 12 М возможных комбинаций).

Если вы ищете идентификатор фиксированной длины (поскольку вы используете NCHAR (5), я предполагаю, что это хорошее предположение), я бы предпочел бы построить таблицу, содержащую все возможные комбинации, и выбрать одно значение эта таблица каждый раз, когда вам это нужно. Вы удалите его, как только он будет использован, или отметьте его как использованный (который я бы предпочел, по причинам повторного использования).

Это приводит к моему окончательному комментарию (который я не могу поставить в качестве комментария, потому что у меня недостаточно репутации): почему бы не использовать функцию IDENTITY, предоставляемую MS-SQL? Это обеспечивает намного лучшую обработку генерации основного ключа ...

+0

+1 это очень похоже на мой ответ (но без кода). :-) –

+0

yup, извините, я не получил ваш ответ, прежде чем публиковать мой ^^ «И вместо этого у меня было бы +1 твое, если бы у меня была репутация, чтобы сделать это .. – Nevoris

+0

Если бы это было до меня, я бы используйте int ID и auto increment, но это моя задача, моя учительница, которая делает нас никогда не используемыми в реальности задачами – martodox