2

私はしばらくの間、これを正しくしようとしてきましたが、役に立ちませんでした。

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")
);

問題は、各レコード (ALFKI、BERGS、BERGS など) に固有の文字列を含む CustomerID フィールドです。

新しいデータを含む行を挿入し、一意の 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を選択することになっています( set @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);
4

3 に答える 3

8

ここでの主な問題は、生成された文字列から衝突を検出して再試行するための増分コストが、より多くの文字列を生成するにつれて増加することです (重複を生成していないことを確認するために、これらの文字列をすべて読み取る必要があるため)。 . 同時に、重複する可能性が高くなります。つまり、テーブルが大きくなるほど、このプロセスは遅くなります。

実行時に一意の文字列を生成する必要があるのはなぜですか? それらをすべて事前に構築します。この記事この投稿は乱数に関するものですが、基本的な概念は同じです。一意の文字列のセットを構築し、必要なときにスタックから 1 つ引き出します。衝突の可能性は、アプリケーションの存続期間を通じて 0% で一定に保たれます (十分な一意の値のスタックを構築する場合)。時間の経過とともに段階的に増加するのではなく、独自の設定で事前に衝突のコストを支払う (最終的に一意の番号を生成するための試行をユーザーが待機するというコストがかかる)。

これにより、100,000 個の一意の 5 文字の文字列が生成されますが、1 回のコストは約 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)に変更することで、約 112 万の一意の値を生成できますTOP (20)。これには 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;

そして、必要なときにスタックから 1 つポップするプロシージャを作成します。

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 桁のランダムな文字列がどのように優れているのでしょうか?

于 2013-12-13T01:51:44.160 に答える
3

Muhammed Ali の答えは機能しますが、かなりリソース集約的であることがわかります (特に、使用する 5 文字の組み合わせがあまりない場合)。関数はランダムジェネレーターを使用し、組み合わせを見つけるのに時間がかかります。特に以前の結果のメモリが非常に限られているため、使用されません。これは、試行することを意味し、次のようなものを提供する可能性があります (少し誇張しています): 最初は BAGER、2 回目は ANSWE、3 回目は BAGER です。ジェネレーターが同じ答えを何度も返すことで、かなりの時間を失うことがわかります (特に 12M を超える可能な組み合わせ)。

固定長の ID を探している場合 (NCHAR(5) を使用しているため、これは適切な仮定だと思います)、考えられるすべての組み合わせを含むテーブルの作成を検討し、毎回このテーブルの値を 1 つ選択します。必要です。使用したら削除するか、使用済みとしてマークします(再利用性の理由から、これが望ましいです)。

これは私の最後のコメントにつながります(十分な評判がないため、コメントとして入れることはできません):MS-SQLが提供するIDENTITY関数を使用しないのはなぜですか?これにより、主キー生成の処理が大幅に改善されます...

于 2013-12-13T01:53:04.033 に答える
-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

Length of new ID is 5while 条件を常に true に設定し、両方の要件が TRUE の場合、つまりitの場合にのみ while ループから抜け出しますdoes not exist in the customers table already

于 2013-12-13T01:05:05.537 に答える