2010-12-15 69 views
3

我的任務是爲項目每天創建一個遞增的序列號。多個進程(理論上在多臺機器上)需要產生這個。由於我在這個項目中使用的SQL Server(2008)反正它結束了作爲Microsoft SQL Server:每天生成一個序列號

[date]_[number] 

20101215_00000001 
20101215_00000002 
... 
20101216_00000001 
20101216_00000002 
... 

,我試圖用T-SQL/SQL魔術做到這一點。這就是我現在:

我創建了一個包含這樣的序號表:

CREATE TABLE [dbo].[SequenceTable](
    [SequenceId] [bigint] IDENTITY(1,1) NOT NULL, 
    [SequenceDate] [date] NOT NULL, 
    [SequenceNumber] [int] NULL 
) ON [PRIMARY] 

我天真的解決方案至今被觸發,插入後,即設置的SequenceNumber:

CREATE TRIGGER [dbo].[GenerateMessageId] 
ON [dbo].[SequenceTable] 
AFTER INSERT 
AS 
BEGIN 
-- SET NOCOUNT ON added to prevent extra result sets from 
-- interfering with SELECT statements. 
SET NOCOUNT ON; 

-- The ID of the record we just inserted 
DECLARE @InsertedId bigint; 
SET @InsertedId = (SELECT SequenceId FROM Inserted) 

-- The next SequenceNumber that we're adding to the new record 
DECLARE @SequenceNumber int; 
SET @SequenceNumber = (
    SELECT SequenceNumber FROM 
    (
     SELECT SequenceId, ROW_NUMBER() OVER(PARTITION BY SequenceDate ORDER BY SequenceDate ASC) AS SequenceNumber 
     FROM SequenceTable 
    ) tmp 
    WHERE SequenceId = @InsertedId 
) 

-- Update the record and set the SequenceNumber 
UPDATE 
    SequenceTable 
SET 
    SequenceTable.SequenceNumber = ''[email protected] 
FROM 
    SequenceTable 
INNER JOIN 
    inserted ON SequenceTable.SequenceId = inserted.SequenceId 
END 

正如我所說的那樣,這太天真了,並且爲一個單獨的數字保留了整整一天的行數,我不再需要:我做一個插入,獲取生成的序列號,然後忽略表。沒有必要商店他們在我身邊,我只需要生成一次。另外,我很確定這不會很好地擴展,表中包含的行越多,越慢(即我不想陷入「在我的開發機器上只有10.000行的陷阱」)。

我想現在的方式更多的是用一些創造力來看待SQL,但結果似乎是 - erm - 沒那麼有用。更聰明的想法?

+0

取決於你能指望realisticly每天要插入,您可以創建一張桌子,並在接下來的100年裏每天預先填充組合。之後,選擇並刪除當前日期的MIN序列。 – 2010-12-15 13:17:57

+0

您的觸發器代碼對於多行插入本質上是不安全的(因爲它假定插入包含單個行) – 2010-12-15 13:47:05

+0

由於我是唯一插入 - 沒關係。這只是我擔心的正確和表現。 – 2010-12-15 16:10:48

回答

3

忘掉那SequenceTable。您應該在決賽桌上創建兩列:日期時間和身份。如果您真的需要將它們組合起來,只需添加一個計算列即可。

我想這將是類似的東西:

CREATE TABLE [dbo].[SomeTable] (
    [SequenceId] [bigint] IDENTITY(1,1) NOT NULL, 
    [SequenceDate] [date] NOT NULL, 
    [SequenceNumber] AS (CAST(SequenceDate AS VARCHAR(10)) + '_' + RIGHT('0000000000' + CAST(SequenceID AS VARCHAR(10)), 10)) PERSISTED 
) ON [PRIMARY] 

這樣將擴大 - 你沒有創建任何形式的中介或臨時數據。

編輯我仍然認爲上面的答案是最好的解決方案。但還有另一種選擇:計算列可以參考函數...

所以做到這一點:

CREATE FUNCTION dbo.GetNextSequence (
    @sequenceDate DATE, 
    @sequenceId BIGINT 
) RETURNS VARCHAR(17) 
AS 
BEGIN 
    DECLARE @date VARCHAR(8) 
    SET @date = CONVERT(VARCHAR, @sequenceDate, 112) 

    DECLARE @number BIGINT 
    SELECT 
     @number = COALESCE(MAX(aux.SequenceId) - MIN(aux.SequenceId) + 2, 1) 
    FROM 
     SomeTable aux 
    WHERE 
     aux.SequenceDate = @sequenceDate 
     AND aux.SequenceId < @sequenceId 

    DECLARE @result VARCHAR(17) 
    SET @result = @date + '_' + RIGHT('00000000' + CAST(@number AS VARCHAR(8)), 8) 
    RETURN @result 
END 
GO 

CREATE TABLE [dbo].[SomeTable] (
    [SequenceId] [bigint] IDENTITY(1,1) NOT NULL, 
    [SequenceDate] [date] NOT NULL, 
    [SequenceNumber] AS (dbo.GetNextSequence(SequenceDate, SequenceId)) 
) ON [PRIMARY] 
GO 

INSERT INTO SomeTable(SequenceDate) values ('2010-12-14') 
INSERT INTO SomeTable(SequenceDate) values ('2010-12-15') 
INSERT INTO SomeTable(SequenceDate) values ('2010-12-15') 
INSERT INTO SomeTable(SequenceDate) values ('2010-12-15') 
GO 

SELECT * FROM SomeTable 
GO 

SequenceId   SequenceDate SequenceNumber 
-------------------- ------------ ----------------- 
1     2010-12-14 20101214_00000001 
2     2010-12-15 20101215_00000001 
3     2010-12-15 20101215_00000002 
4     2010-12-15 20101215_00000003 

(4 row(s) affected) 

這是醜陋的,但工作的,對不對? :-)沒有任何臨時表,沒有意見,沒有觸發器,它會有一個體面的表現(當然,至少索引超過SequenceIdSequenceDate)。而你可以刪除記錄(因爲和身份被用於結果計算字段)。

+2

不會 - 這不會令人遺憾:這錯過了「重新開始新的一天」的邊界。我每天需要一個新的序列,從0或1開始。 – 2010-12-15 13:14:29

+0

因此,除了像你已經做的事情之外,你沒有別的選擇。你需要知道這是一個「新的一天」,所以你需要閱讀以前的記錄 - 這不是很聰明順便說一句,但這只是我的看法。 – rsenna 2010-12-15 13:22:52

+1

智能與否不適合討論,不幸的是 - 這是我的要求,也是不能改變的。我嘗試刪除的其他選擇是鎖定全局互斥鎖和讀取/更新ini文件的進程,這種文件不靈巧,並且不跨越機器邊界運行。 – 2010-12-15 13:25:17

-1

如果您不介意數字不是從1開始,您可以使用DATEDIFF(dd, 0, GETDATE())這是1-1-1900以來的天數。這將每天增加。

+0

他們希望每天增加多次,例如'20101215_00000001','20101215_00000002',... – onedaywhen 2010-12-15 12:57:44

1

如果您創建的SequenceDate指數和SequenceId,我不認爲將表現太糟糕了你可以做類似

CREATE TABLE SequenceTableStorage (
    SequenceId bigint identity not null, 
    SequenceDate date NOT NULL, 
    OtherCol int NOT NULL, 
) 

CREATE VIEW SequenceTable AS 
SELECT x.SequenceDate, (CAST(SequenceDate AS VARCHAR(10)) + '_' + RIGHT('0000000000' + CAST(SequenceID - (SELECT min(SequenceId) + 1 FROM SequenceTableStorage y WHERE y.SequenceDate = x.SequenceDate) AS VARCHAR(10)), 10)) AS SequenceNumber, OtherCol 
    FROM SequenceTableStorage x 

編輯:

如果事務插入一行上面的代碼可能會錯過一些序列號,例如,然後回滾(然後標識值將會丟失在空間)。

這個問題可以用這個視圖來解決,這個視圖的性能可能不夠好,也可能不夠好。

CREATE VIEW SequenceTable AS 
SELECT SequenceDate, (CAST(SequenceDate AS VARCHAR(10)) + '_' + RIGHT('0000000000' + row_number() OVER(PARTITION BY SequenceDate ORDER BY SequenceId) 
    FROM SequenceTableStorage 

我的猜測是,它會足夠好,直到你開始獲得每天數百萬的序列號。

+0

這似乎很有希望。正是如此,如果我有數百萬行(儘管有索引),性能依然很差。如果我可以依靠Min(SequenceId)等於Top 1 SequenceId,我可以顯着提高性能。現在我正在努力證明/確保這個替換是正確的... – 2010-12-15 14:48:04

+0

只要它與SequenceDate一起編制索引,Min(SequenceId)就不應該執行錯誤。 SQL Server完全可以實現它可以從索引中找到最小值。 – erikkallen 2010-12-15 21:03:53

2

如果您可以創建具有不同名稱的實際表格,並通過視圖執行所有其他操作,那麼它可能適合帳單。它也要求沒有交易被徹底刪除(所以你需要在視圖/表中添加適當的觸發/權限,以防止):

create table dbo.TFake (
    T1ID int IDENTITY(1,1) not null, 
    T1Date datetime not null, 
    Val1 varchar(20) not null, 
    constraint PK_T1ID PRIMARY KEY (T1ID) 
) 
go 
create view dbo.T 
with schemabinding 
as 
    select 
     T1Date, 
     CONVERT(char(8),T1Date,112) + '_' + RIGHT('00000000' + CONVERT(varchar(8),ROW_NUMBER() OVER (PARTITION BY CONVERT(char(8),T1Date,112) ORDER BY T1ID)),8) as T_ID, 
     Val1 
    from 
     dbo.TFake 
go 
insert into T(T1Date,Val1) 
select '20101201','ABC' union all 
select '20101201','DEF' union all 
select '20101202','GHI' 
go 
select * from T 

結果:

T1Date T_ID Val1 
2010-12-01 00:00:00.000 20101201_00000001 ABC 
2010-12-01 00:00:00.000 20101201_00000002 DEF 
2010-12-02 00:00:00.000 20101202_00000001 GHI 

您當然也可以從視圖中隱藏日期列,並將其默認爲CURRENT_TIMESTAMP。

+0

謝謝。雖然這看起來好於我的方法(+1),但它不可行。我將每天產生很多這些東西(這就是爲什麼該數字被格式化爲8位數字)。不要移除行不是一個選項。 – 2010-12-15 14:52:31

+0

@Benjamin - 您可以刪除行,只要您刪除與特定日期相關的所有行,否則某些行(您留下的)的生成ID值可能會更改。 – 2010-12-15 14:58:38

+0

你可以通過創建[索引視圖](http://msdn.microsoft.com/en-us/library/dd171921%28v=sql.100%29.aspx)來改善它,對吧?無論如何,你的答案几乎就是那樣的...... – rsenna 2010-12-15 20:40:24

0

我試過這種方式來爲用戶日誌和它的工作創建會話代碼;

CREATE FUNCTION [dbo].[GetSessionSeqCode]() 
RETURNS VARCHAR(15) 
AS 
BEGIN 
DECLARE @Count INT; 
DECLARE @SeqNo VARCHAR(15) 

SELECT @Count = ISNULL(COUNT(SessionCode),0) 
FROM UserSessionLog 
WHERE SUBSTRING(SessionCode,0,9) = CONVERT(VARCHAR(8), GETDATE(), 112) 

SET @SeqNo = CONVERT(VARCHAR(8), GETDATE(), 112) +'-' + FORMAT(@Count+1,'D3'); 

RETURN @SeqNo 
END 

生成的代碼是: '20170822-001' , '20170822-002' , '20170822-003'