我写了一份程序,可以申请身份证。
有不同类型的身份证(红色、蓝色、绿色)。
在申请过程中,程序应生成身份号码。号码的范围取决于所请求的卡片类型。
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'
Color, ID
上建立索引。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结尾,依此类推。
您可以利用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
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)
有很多答案,但我会加上我的两分钱。请注意,我假设我在您原始帖子的评论中所写的内容:
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)
)
当然,您需要向卡表添加一些实际数据。请注意,如果存在足够有效的索引,则最后一个查询可以相对快速地执行。如果性能存在问题,则可能值得绕过对identificationNumber进行索引。例如,如果您将拥有大量行,请考虑在此列上创建过滤索引。
或者,您可以将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 |
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
CardCategry MinNumber MaxNumber RunningNumber
然后,您可以编写一个存储过程以获取下一个数字并将卡类别作为参数传递。示例查询如下所示。
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
*这个解决方案适用于单行插入,对于多行插入的并发需要采用不同的方法。更多细节请参见评论 *
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以满足灵活的要求。
select @REQ_ID ...
和insert into REQUEST_TABLE...
之间添加 waitfor '00:00:05'
,然后运行两个相同类型的卡片的并发插入。 - Roman Pekar