我写了一个程序,我可以在那里申请身份证。
有不同类型的身份证(红色,蓝色,绿色)
在请求时,程序应生成标识号。数字(数字的范围)取决于所请求的卡。
Red Card: 1 - 50000
Blue Card: 50001 - 100000
Green Card: 100001 - 150000
如果我向系统添加新的身份证,那么序列应自动为新添加的身份证创建新的数字范围。数字不应再发生。一个号码只能使用一次。
我该怎么做?任何人都可以帮我吗?
答案 0 :(得分:2)
您可以使用而不是插入触发器来实现此
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
<强> sql fiddle demo 强>
它允许您一次插入多行:
insert into Cards (Color)
select 'GREEN' union all
select 'GREEN' union all
select 'RED' union all
select 'BLUE'
请注意,您最好在卡片列Color, ID
上设置索引。
另请注意,您可以为每种类型仅插入50000条记录。您可以使用不同的种子,例如1种用于“红色”,2种用于“蓝色”种子。依此类推,并保留例如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;
<强> sql fiddle demo 强>
以这种方式识别&#39; RED&#39;将始终以1结尾,ID为&#39; BLUE&#39;结束于2,依此类推。
答案 1 :(得分:2)
从设计角度来看,我强烈反对将附加逻辑编码为标识符,即将卡颜色分配给特定范围。我宁愿使用IDENTITY列来处理唯一性和并发性,使ID完全代理并在另一个属性中存储给定ID的卡颜色信息。可能在该附加属性上创建索引以检索给定颜色的记录。
还要考虑一下,如果红卡的所有者要求将其改为蓝色的那个,会有什么需要?使用范围,要保留颜色分配,您需要创建一个新的ID,并可能在其他地方存储有关从旧到新的id序列的信息。如果有人多次更改怎么办?使用代理ID,您可以随时拥有一个ID,以便能够在整个历史记录中跟踪同一个人,也许只需在表中添加日期信息即可按顺序进行更改。这只是一个简单场景的例子。
答案 2 :(得分:1)
您可以利用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
这三个表的标识值设置为与您的间隔下限匹配。
然后,对于每个请求,您都要插入相应的表并使用the OUTPUT clause来获取新生成的标识值。
例如,如果请求是红牌,您可以写:
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)
答案 3 :(得分:0)
理想情况下,您必须维护一个表来存储此信息。
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
这不是一项完整的工作。显然,您必须对检查边界限制等进行一些错误处理。
答案 4 :(得分:0)
我会尝试这样的事情:
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
答案 5 :(得分:0)
有很多答案,但我会加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表中,并使合并表稍微复杂一些。缺点是如果查询之间会出现某种错误,那么这个数字永远不会被使用,所以我的解决方案会保持顺序。
答案 6 :(得分:0)
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
<强> Results 强>:
| COLOR | ID |
|-------|--------|
| Red | 1 |
| Red | 50000 |
| Red | 150001 |
| Blue | 50001 |
| Green | 100001 |
答案 7 :(得分:0)
这是我对挑战的贡献。不需要额外的表,应该是并发安全的,并且可以处理批量更新。可能不是最快,但它的工作原理。它基本上将要插入的行复制到一个单独的表中,为每种颜色创建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]
答案 8 :(得分:0)
编辑#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
答案 9 :(得分:-2)
* 此解决方案适用于单行插入,多个插入的并发需要不同的方法。在评论中讨论了更多细节*
如果没有创建表的选项,那么你可以使用而不是触发器(在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以满足灵活需求。