我的任务是为项目每天创建一个递增的序列号。多个进程(理论上在多台机器上)需要生成这个。它最终成为
[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,但结果似乎是 - 呃 - 没那么有用。更聪明的想法?
答案 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)
这很难看,但是有效吗? :-)没有任何临时表,没有视图,没有触发器,并且它会有一个不错的性能(至少有一个索引超过SequenceId
和SequenceDate
,当然)。并且可以删除记录(因为身份用于生成的计算字段)。
答案 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以来的天数。这将每天增加。