2010-12-15 149 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'