Microsoft SQL Server:每天生成一个序列号

时间:2010-12-15 12:24:05

标签: sql sql-server tsql sequence

我的任务是为项目每天创建一个递增的序列号。多个进程(理论上在多台机器上)需要生成这个。它最终成为

[date]_[number]

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

因为我在这个项目中使用的是SQL Server(2008),所以我尝试使用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 = ''+@SequenceNumber
FROM
    SequenceTable
INNER JOIN
    inserted ON SequenceTable.SequenceId = inserted.SequenceId
END

正如我所说,这是相当幼稚的,并且只保留一整天的行数,而不管怎么说我再也不需要了:我执行插入操作,获取生成的序列号,然后忽略表格。不需要存储他们,我只需要生成一次。另外我很确定这不会很好地扩展,表中包含的行越多越慢(即我不想陷入“仅在我的开发机器上运行10.000行”陷阱)。

我想当前的方式更多的是我用一些创造力来看SQL,但结果似乎是 - 呃 - 没那么有用。更聪明的想法?

5 个答案:

答案 0 :(得分: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,当然)。并且可以删除记录(因为身份用于生成的计算字段)。

答案 1 :(得分: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。

答案 2 :(得分:1)

您可以执行类似

的操作
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

如果在SequenceDate和SequenceId上创建索引,我认为性能不会太差。

修改

上面的代码可能会遗漏一些序列号,例如,如果事务插入一行然后回滚(那么标识值将在空间中丢失)。

这可以通过此视图修复,其性能可能会或可能不够好。

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

我的猜测是,在你每天开始获得数百万个序列号之前,这将是足够好的。

答案 3 :(得分: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

生成的代码是: &#39; 20170822-001&#39; &#39; 20170822-002&#39; &#39; 20170822-003&#39;

答案 4 :(得分:-1)

如果您不介意数字不是从1开始,您可以使用DATEDIFF(dd, 0, GETDATE()),这是自1-1-1900以来的天数。这将每天增加。