我编写了一个程序,可以在其中请求身份证。
有不同类型的身份证(红色,蓝色,绿色)
在请求的同时,程序应该生成标识号。数字(数字范围)取决于请求的卡。
Red Card: 1 - 50000
Blue Card: 50001 - 100000
Green Card: 100001 - 150000
如果我向系统添加新的身份证,那么序列应该自动为新添加的身份证创建一个新的数字范围。数字不应重复出现。一个号码只能使用一次。
我怎样才能做到这一点?任何人都可以帮助我吗?
我编写了一个程序,可以在其中请求身份证。
有不同类型的身份证(红色,蓝色,绿色)
在请求的同时,程序应该生成标识号。数字(数字范围)取决于请求的卡。
Red Card: 1 - 50000
Blue Card: 50001 - 100000
Green Card: 100001 - 150000
如果我向系统添加新的身份证,那么序列应该自动为新添加的身份证创建一个新的数字范围。数字不应重复出现。一个号码只能使用一次。
我怎样才能做到这一点?任何人都可以帮助我吗?
您可以为此使用而不是插入触发器
create table Cards_Types (Color nvarchar(128) primary key, Start int);
create table Cards (ID int primary key, Color nvarchar(128));
insert into Cards_Types
select 'RED', 0 union all
select 'BLUE', 50000 union all
select 'GREEN', 100000;
create trigger utr_Cards_Insert on Cards
instead of insert as
begin
insert into Cards (id, Color)
select
isnull(C.id, CT.Start) + row_number() over(partition by i.Color order by i.id),
i.Color
from inserted as i
left outer join Cards_Types as CT on CT.Color = i.Color
outer apply (
select max(id) as id
from Cards as C
where C.Color = i.Color
) as C
end
它允许您一次插入多行:
insert into Cards (Color)
select 'GREEN' union all
select 'GREEN' union all
select 'RED' union all
select 'BLUE'
请注意,您最好在 Cards columns 上有索引Color, ID
。
另请注意,您只能为每种类型插入 50000 条记录。您可以使用不同的种子,例如 1 代表 'RED',2 代表 'BLUE' 等等,并为例如 100种卡片预留位置:
create table Cards_Types (Color nvarchar(128) primary key, Start int);
create table Cards (ID int primary key, Color nvarchar(128));
insert into Cards_Types
select 'RED', 1 union all
select 'BLUE', 2 union all
select 'GREEN', 3;
create trigger utr_Cards_Insert on Cards
instead of insert as
begin
insert into Cards (id, Color)
select
isnull(C.id, CT.Start - 100) + row_number() over(partition by i.Color order by i.id) * 100,
i.Color
from inserted as i
left outer join Cards_Types as CT on CT.Color = i.Color
outer apply (
select max(id) as id
from Cards as C
where C.Color = i.Color
) as C
end;
这样,“RED”的 ID 将始终以 1 结束,“BLUE”的 ID 以 2 结束,依此类推。
从设计的角度来看,我强烈反对将额外的逻辑编码到标识符中,即将卡片颜色分配给特定的范围。我宁愿使用能够很好地处理唯一性和并发性的 IDENTITY 列,使 ID 完全替代并将给定 ID 的卡片颜色信息存储在另一个属性中。可能在该附加属性上创建索引以检索给定颜色的记录。
还想一想,如果红卡的所有者要求将其更改为蓝卡,需要什么?对于范围,要保留颜色分配,您需要创建一个新的 id,并可能在其他地方存储有关从旧到新的 id 序列的信息。如果有人多次更改它怎么办?使用代理 ID,您可以一直拥有一个 ID,以便能够在整个历史记录中跟踪同一个人,也许只需将日期信息添加到您的表格中以按顺序订购更改。这只是一个简单场景的示例。
为此,您可以利用 SQL Server 的IDENTITY机制,因为它易于使用并且可以很好地处理并发性。
更具体地说,您可以使用以下脚本创建三个仅包含标识(自动递增)Id
列的表:
create table RedCardIds(Id int identity(1, 1) primary key)
create table BlueCardIds(Id int identity(50001, 1) primary key)
create table GreenCardIds(Id int identity(100001, 1) primary key)
GO
这三个表的标识值设置为与您的区间下限匹配。
然后,对于每个请求,您都将插入到相应的表中,并使用OUTPUT 子句来获取新生成的标识值。
例如,如果请求是红牌,你可以写:
insert RedCardIds
output inserted.Id
default values
这将输出:
Id
-----------
1
(1 row(s) affected)
在下一次运行时它会返回2
,依此类推。
同样,蓝卡的第一个请求将触发语句:
insert BlueCardIds
output inserted.Id
default values
结果:
Id
-----------
500001
(1 row(s) affected)
理想情况下,您必须维护一个表来存储此信息。
CardCategry MinNumber MaxNumber RunningNumber
然后你可以编写一个 SP 来获取下一个数字并将卡片类别作为参数传递。示例查询如下。
SELECT @count=count(RunningNumber)
FROM IdentificationTable
WHERE CardCategry=@param
IF (@count=1)
SELECT @RunningNumber=RunningNumber
FROM IdentificationTable
WHERE CardCategry=@param
ELSE
SELECT TOP 1 @min=MinNumber,@max=MaxNumber
FROM IdentificationTable
ORDER BY MinNumber DESC
INSERT INTO IdentificationTable VALUES (@param,@max+1,@max+(@max-@min),1)
SET @RunningNumber=1
RETURN @RunningNumber
这不是一个完整的工作。显然,您必须对检查边界限制等进行一些错误处理。
我会尝试这样的事情:
declare @cat2start int = 50000
declare @cat3start int = 100000
declare @catName varchar(10) = 'Red'
if @catName = 'Green'
begin
select (max(cardnumber) + 1) as [This is the next number]
from yourTable
where
cardnumber < @cat2start
end
if @catName = 'Blue'
begin
select (max(cardnumber) + 1) as [This is the next number]
from yourTable
where
cardumber >= @cat2start and cardnumber < @cat3start
end
if @catName = 'Red'
begin
select (max(cardnumber) + 1) as [This is the next number]
from yourTable
end
有很多答案,但我会加我的 2 美分。请注意,我假设我在对您原始帖子的评论中所写的内容:
create table cardTypes(cardTypeName varchar(100) primary key, [50kSlot] int unique)
create table cards (identificationNumber bigint primary key);
--add slot if needed
declare @cardToBeAdded varchar(100) = 'green'
declare @tbl50kSlot table (i int)
merge into cardTypes as t
using (select @cardToBeAdded as newCard) as s
on t.[cardTypeName] = s.newCard
when not matched by target then
insert (cardTypeName, [50kSlot]) values (s.newCard, isnull((select max([50kSlot]) + 1 from cardTypes),1))
when matched then
update set [50kSlot] = [50kSlot]
output inserted.[50kSlot] into @tbl50kSlot;
declare @50kSlot int = (Select i from @tbl50kSlot)
insert into cards (identificationNumber) values (isnull(
(select max(identificationNumber)+1 from cards where identificationNumber between ((@50kSlot-1)*50000+1) and @50kSlot*50000),
(@50kSlot-1)*50000+1)
)
当然,您需要将一些实际数据添加到卡表中。请注意,如果存在足够有效的索引,则可以相对快速地执行最后一个查询。如果存在性能问题,可能值得解决对标识号进行索引的工作。例如,如果您将有很多行,请考虑在此列上创建过滤索引。
或者,您可以将 maxInt 保留在 cardTypes 表中,并使合并表稍微复杂一些。不利的一面是,如果查询之间出现某种错误,则永远不会使用该数字,因此我的解决方案会保持序列紧密。
MS SQL Server 2008 架构设置:
CREATE TABLE Table1
([color] varchar(10), [id] int)
;
INSERT INTO Table1
([color], [id])
VALUES
('Red',(select isnull(case when (max(id)/50000)%3 = 1 and
max(id)%50000 = 0 then max(id)+100000 else
max(id) end,0)+1
from Table1 where color = 'Red'));
INSERT INTO Table1 ([color], [id]) VALUES ('Red',50000);
INSERT INTO Table1
([color], [id])
VALUES
('Red',(select isnull(case when (max(id)/50000)%3 = 1 and
max(id)%50000 = 0 then max(id)+100000 else
max(id) end,0)+1
from Table1 where color = 'Red'));
INSERT INTO Table1
([color], [id])
VALUES
('Blue',(select isnull(case when (max(id)/50000)%3 = 2 and
max(id)%50000 = 0 then max(id)+100000 else
max(id) end,50000)+1
from Table1 where color = 'Blue'));
INSERT INTO Table1
([color], [id])
VALUES
('Green',(select isnull(case when (max(id)/50000)%3 = 0 and
max(id)%50000 = 0 then max(id)+100000 else
max(id) end,100000)+1
from Table1 where color = 'Green'));
查询 1:
SELECT *
FROM Table1
结果:
| COLOR | ID |
|-------|--------|
| Red | 1 |
| Red | 50000 |
| Red | 150001 |
| Blue | 50001 |
| Green | 100001 |
这是我对挑战的贡献。不需要额外的表,应该是并发安全的并且可以处理批量更新。可能不是最快的,但它有效。它基本上将要插入的行复制到单独的表中,为每种颜色创建 ID,最后将所有内容移动到目标表中。
Create Trigger Trg_CreateID ON dbo.Cards instead of insert
as
begin
set nocount on
-- declare a working table holding intermediate results
declare @Tmp Table (cardID int, cardColor char(1), cardNumber char(20))
-- copy the data to be inserted in our working table
insert into @Tmp select * from inserted
declare @Id int
-- fill in the Id's once per color
select @Id=coalesce (max(cardID),0) from dbo.Cards where cardColor='Red'
update @Tmp set cardID = @Id, @Id=@id+1 where cardColor='Red'
select @Id=coalesce(max(cardID),50000) from dbo.Cards where cardColor='Blue'
update @Tmp set cardID = @Id, @Id=@id+1 where cardColor='Blue'
select @Id=coalesce(max(cardID),100000) from dbo.Cards where cardColor='Gree'
update @Tmp set cardID = @Id, @Id=@id+1 where cardColor='Green'
-- do the actual insert here
insert into dbo.Cards select * from @tmp
end
它假设Cards
像这样的表
CREATE TABLE [dbo].[Cards]
(
[cardID] [int] NOT NULL,
[cardColor] [char](1) NOT NULL,
[cardNumber] [char](20) NOT NULL
) ON [PRIMARY]
我在列中添加了一个约束以cardID
允许在插入语句中省略它
ALTER TABLE [dbo].[Cards]
ADD CONSTRAINT [DF_Cards_cardID] DEFAULT ((0)) FOR [cardID]
编辑 #1:我更新了触发器 ( IF UPDATE
)、存储过程和最后两个示例。
CREATE TABLE dbo.CustomSequence
(
CustomSequenceID INT IDENTITY(1,1) PRIMARY KEY,
SequenceName NVARCHAR(128) NOT NULL, -- or SYSNAME
UNIQUE(SequenceName),
RangeStart INT NOT NULL,
RangeEnd INT NOT NULL,
CHECK(RangeStart < RangeEnd),
CurrentValue INT NULL,
CHECK(RangeStart <= CurrentValue AND CurrentValue <= RangeEnd)
);
GO
CREATE TRIGGER trgIU_CustomSequence_VerifyRange
ON dbo.CustomSequence
AFTER INSERT, UPDATE
AS
BEGIN
IF (UPDATE(RangeStart) OR UPDATE(RangeEnd)) AND EXISTS
(
SELECT *
FROM inserted i
WHERE EXISTS
(
SELECT * FROM dbo.CustomSequence cs
WHERE cs.CustomSequenceID <> i.CustomSequenceID
AND i.RangeStart <= cs.RangeEnd
AND i.RangeEnd >= cs.RangeStart
)
)
BEGIN
ROLLBACK TRANSACTION;
RAISERROR(N'Range overlapping error', 16, 1);
END
END;
GO
--TRUNCATE TABLE dbo.CustomSequence
INSERT dbo.CustomSequence (SequenceName, RangeStart, RangeEnd)
SELECT N'Red Card', 1, 50000 UNION ALL
SELECT N'Blue Card', 50001, 100000 UNION ALL
SELECT N'Green Card', 100001, 150000;
GO
-- Test for overlapping range
INSERT dbo.CustomSequence (SequenceName, RangeStart, RangeEnd)
VALUES (N'Yellow Card', -100, +100);
GO
/*
Msg 50000, Level 16, State 1, Procedure trgIU_CustomSequence_VerifyRange, Line 20
Range overlapping error
Msg 3609, Level 16, State 1, Line 1
The transaction ended in the trigger. The batch has been aborted.
*/
GO
-- This procedure tries to reserve
CREATE PROCEDURE dbo.SequenceReservation
(
@CustomSequenceID INT, -- You could use also @SequenceName
@IDsCount INT, -- How many IDs do we/you need ? (Needs to be greather than 0)
@LastID INT OUTPUT
)
AS
BEGIN
DECLARE @StartTranCount INT, @SavePoint VARCHAR(32);
SET @StartTranCount = @@TRANCOUNT;
IF @StartTranCount = 0 -- There is an active transaction ?
BEGIN
BEGIN TRANSACTION -- If not then it starts a "new" transaction
END
ELSE -- If yes then "save" a save point -- see http://technet.microsoft.com/en-us/library/ms188378.aspx
BEGIN
DECLARE @ProcID INT, @NestLevel INT;
SET @ProcID = @@PROCID;
SET @NestLevel = @@NESTLEVEL;
SET @SavePoint = CONVERT(VARCHAR(11), @ProcID) + ',' + CONVERT(VARCHAR(11), @NestLevel);
SAVE TRANSACTION @SavePoint;
END
BEGIN TRY
UPDATE dbo.CustomSequence
SET @LastID = CurrentValue = ISNULL(CurrentValue, 0) + @IDsCount
WHERE CustomSequenceID = @CustomSequenceID;
IF @@ROWCOUNT = 0
RAISERROR(N'Invalid sequence', 16, 1);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF @StartTranCount = 0
BEGIN
ROLLBACK TRANSACTION;
END
ELSE -- @StartTranCount > 0
BEGIN
ROLLBACK TRANSACTION @SavePoint
END
DECLARE @ErrorMessage NVARCHAR(2048), @ErrorSeverity INT, @ErrorState INT;
SELECT @ErrorMessage = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();
RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH;
END;
GO
SELECT * FROM dbo.CustomSequence;
GO
-- Example usage #1
DECLARE @LastID INT;
EXEC dbo.SequenceReservation
@CustomSequenceID = 1, -- Red Card
@IDsCount = 2, -- How many IDs ?
@LastID = @LastID OUTPUT;
SELECT @LastID - 2 + 1 AS [FirstID], @LastID AS [LastID];
GO
-- Example usage #2
DECLARE @LastID INT;
EXEC dbo.SequenceReservation
@CustomSequenceID = 1, -- Red Card
@IDsCount = 7, -- How many IDs ?
@LastID = @LastID OUTPUT;
SELECT @LastID - 7 + 1 AS [FirstID], @LastID AS [LastID];
SELECT * FROM dbo.CustomSequence;
GO
结果:
CustomSequenceID SequenceName RangeStart RangeEnd CurrentValue
---------------- ------------ ----------- ----------- ------------
1 Red Card 1 50000 9
2 Blue Card 50001 100000 NULL
3 Green Card 100001 150000 NULL
*此解决方案适用于单行插入,多个插入的并发需要不同的方法。在评论中讨论更多细节*
如果没有用于创建表的选项,那么您可以使用而不是触发器(像 oracle 中的触发器之前一样进行调整)。
使用触发器内的特定条件来设置Identity
列的范围。这是一个示例,您可以如何实施您的解决方案。
桌子
CREATE TABLE REQUEST_TABLE(
REQ_ID numeric(8, 0) NOT NULL,
REQ_COLOR VARCHAR(30) NOT NULL
); -- I have used this sample table
而不是触发器
CREATE TRIGGER tg_req_seq ON REQUEST_TABLE
INSTEAD OF INSERT AS
DECLARE @REQ_ID INT
DECLARE @REQ_COLOR VARCHAR(30)
DECLARE @REQ_START INT
BEGIN
SELECT @REQ_COLOR= (SELECT ISNULL(REQ_COLOR,'NA') FROM INSERTED)
SELECT @REQ_START = (SELECT CASE WHEN @REQ_COLOR = 'Red' THEN 0
WHEN @REQ_COLOR = 'Blue' THEN 50000
ELSE 100000 END)
SELECT @REQ_ID = ISNULL(MAX(REQ_ID),@REQ_START)+1 FROM REQUEST_TABLE
WHERE REQ_COLOR = @REQ_COLOR
INSERT INTO REQUEST_TABLE (REQ_ID,REQ_COLOR)
VALUES (@REQ_ID,@REQ_COLOR)
END;
现在在一些插入语句之后
INSERT INTO REQUEST_TABLE VALUES(NULL,'Red');
INSERT INTO REQUEST_TABLE VALUES(NULL,'Red');
INSERT INTO REQUEST_TABLE VALUES(NULL,'Red');
INSERT INTO REQUEST_TABLE VALUES(NULL,'Blue');
INSERT INTO REQUEST_TABLE VALUES(NULL,'Blue');
INSERT INTO REQUEST_TABLE VALUES(NULL,'Blue');
INSERT INTO REQUEST_TABLE VALUES(NULL,'Yellow');
INSERT INTO REQUEST_TABLE VALUES(NULL,'Yellow');
INSERT INTO REQUEST_TABLE VALUES(NULL,'Yellow');
我在SqlFiddle添加了相同的结果。让我知道我是否错过了要包括的内容。
编辑
更新了Fiddle以满足灵活的需求。