5

我编写了一个程序,可以在其中请求身份证。

有不同类型的身份证(红色,蓝色,绿色)

在请求的同时,程序应该生成标识号。数字(数字范围)取决于请求的卡。

Red Card: 1 - 50000 
Blue Card: 50001 - 100000 
Green Card: 100001 - 150000

如果我向系统添加新的身份证,那么序列应该自动为新添加的身份证创建一个新的数字范围。数字不应重复出现。一个号码只能使用一次。

我怎样才能做到这一点?任何人都可以帮助我吗?

4

10 回答 10

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'

请注意,您最好在 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;

sql fiddle demo

这样,“RED”的 ID 将始终以 1 结束,“BLUE”的 ID 以 2 结束,依此类推。

于 2013-09-23T13:04:56.977 回答
2

从设计的角度来看,我强烈反对将额外的逻辑编码到标识符中,即将卡片颜色分配给特定的范围。我宁愿使用能够很好地处理唯一性和并发性的 IDENTITY 列,使 ID 完全替代并将给定 ID 的卡片颜色信息存储在另一个属性中。可能在该附加属性上创建索引以检索给定颜色的记录。

还想一想,如果红卡的所有者要求将其更改为蓝卡,需要什么?对于范围,要保留颜色分配,您需要创建一个新的 id,并可能在其他地方存储有关从旧到新的 id 序列的信息。如果有人多次更改它怎么办?使用代理 ID,您可以一直拥有一个 ID,以便能够在整个历史记录中跟踪同一个人,也许只需将日期信息添加到您的表格中以按顺序订购更改。这只是一个简单场景的示例。

于 2013-09-28T11:24:18.193 回答
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

这三个表的标识值设置为与您的区间下限匹配。

然后,对于每个请求,您都将插入到相应的表中,并使用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)
于 2013-09-23T08:56:39.213 回答
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

这不是一个完整的工作。显然,您必须对检查边界限制等进行一些错误处理。

于 2013-09-19T10:27:42.437 回答
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
于 2013-09-19T10:44:04.510 回答
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 表中,并使合并表稍微复杂一些。不利的一面是,如果查询之间出现某种错误,则永远不会使用该数字,因此我的解决方案会保持序列紧密。

于 2013-09-24T14:32:24.403 回答
0

SQL小提琴

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 |
于 2013-09-26T05:47:32.693 回答
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]
于 2013-09-28T08:15:02.600 回答
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
于 2013-09-28T15:18:43.543 回答
-1

*此解决方案适用于单行插入,多个插入的并发需要不同的方法。在评论中讨论更多细节*


如果没有用于创建表的选项,那么您可以使用而不是触发器(像 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以满足灵活的需求。

于 2013-09-23T10:55:06.467 回答