【问题标题】:How to create an auto increment column that is segmented by an other column如何创建由其他列分段的自动增量列
【发布时间】:2017-01-22 17:54:10
【问题描述】:

我需要创建一个包含增量 id 的表,但我希望根据其他列自动对 id 进行分段。这是我想要的:

CREATE TABLE dbo.MyTable (
    myKey INT IDENTITY PRIMARY KEY,
    category INT,
    incrementalId INT
);

INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (200);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (200);

SELECT *
FROM dbo.MyTable;

我希望它显示如下内容:

myKey       category    incrementalId
----------- ----------- -------------
1           100         1
2           200         1
3           100         2
4           100         3
5           100         4
6           200         2

意思是我希望incrementalId 每个类别自动递增,并从 1 重新开始插入任何新类别。我希望在表中的任何插入上自行完成(我不想在插入此表时记住这样做)。

我认为这可以通过窗口函数和触发器来完成,但我就是不知道怎么做。

编辑:

如果发生数据删除,我希望保留数据以避免增量 ID 被转移。此外,理想情况下,在删除行的情况下不会重新给出相同的 ID(与序列或 IDENTITY 的工作方式相同)

有什么想法吗?

【问题讨论】:

  • 类别是无限的吗?
  • @scsimon 是的,他们可以随时添加。今天有 50 个,下个月可能有 55 个,依此类推。
  • 只更新而不是处理插入恕我直言的逻辑可能更快。
  • 为什么您认为需要将记录的序号存储在类别中,而不是在查询时即时计算?使用窗口函数时,您是否面临严重的性能下降?
  • @ajeh 我希望保留数据。如果一行被删除,我不希望一个类别中的序列号被移动。

标签: sql sql-server tsql auto-increment


【解决方案1】:
CREATE TABLE dbo.MyTable (
  myKey INT IDENTITY PRIMARY KEY,
  category INT,
  incrementalId INT
);
GO

create table dbo.nextCategoryID (
  category int,
  nextidvalue int,
  constraint PK_nextCategoryID primary key clustered( category, nextidvalue )
);
GO

create trigger numberByCategory on dbo.MyTable
after insert as 

-- Automatically add any net new category
insert into dbo.nextCategoryID ( category, nextidvalue )
    select distinct category, 1 as nextidvalue
    from inserted
    where not exists ( select * from dbo.nextCategoryID s
        where s.category = inserted.category );


-- Number the new rows in each incoming category
with numberedrows as (
    select 
        i.myKey, 
        i.category, 
        n.nextidvalue - 1 + row_number() over ( partition by i.category order by i.category ) as incrementalId
    from inserted i
    join dbo.nextCategoryID n on i.category = n.category
)
update m
    set incrementalId = n.incrementalId
from dbo.MyTable m
join inserted i on m.myKey = i.myKey
join numberedrows n on n.myKey = i.myKey;


update dbo.nextCategoryID
    set nextidvalue = 1 + ( select max( m.incrementalId ) 
        from inserted i 
        join dbo.MyTable m on i.myKey = m.myKey
        where i.category = nextCategoryID.category 
    )
where exists ( select * 
    from inserted i
    where i.category = nextCategoryID.category
);

GO

-- Test data 

INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (200);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (200);

insert into dbo.MyTable (category) 
values 
    ( 200 ),
    ( 200 ),
    ( 100 ),
    ( 300 ),
    ( 400 ),
    ( 400 )


SELECT *
FROM dbo.MyTable;

【讨论】:

  • 很好的答案,谢谢。我想知道是否可以通过序列(或多个序列)以某种方式实现类似的结果以生成增量 ID
  • 可以,但每个类别都需要一个序列
  • 如果类别稳定,可能会更好地扩展。如果没有,那么很难说这是否可行
【解决方案2】:

您可以通过触发器轻松实现:

CREATE TRIGGER dbo.UpdateIncrementalID
ON dbo.MyTable
AFTER  INSERT
AS 
      UPDATE x 
      SET incrementalId = ROW_NUMBER() OVER(PARTITION BY category ORDER BY myKey DESC)
      FROM dbo.MyTable x

【讨论】:

    【解决方案3】:

    我认为您不需要在表中添加额外的列“IncrementalID”。

    您可以在您的选择语句中创建它。

    SELECT myKey,category,ROW_NUMBER() OVER(PARTITION BY category ORDER BY myKey )incrementalId
    FROM MyTable
    ORDER BY myKey
    

    样本输出。

    否则,您可以从实际表创建视图。

    CREATE VIEW dbo.VIEW_MyTable 
     AS
      SELECT myKey,category,ROW_NUMBER() OVER(PARTITION BY category ORDER BY myKey )incrementalId
        FROM MyTable
        ORDER BY myKey
    

    【讨论】:

    • 我想让数据持久化以便以后方便查询。
    • 那么我认为最好从实际表中创建一个view
    【解决方案4】:

    您可以使用下面的更新查询来更新同一个表

    ;with cte as (
    select mykey, category, incrementalid, row_number() over (partition by category order by mykey,category) as rn from MyTable
    )
    update cte
    set incrementalId = rn
    

    【讨论】:

      【解决方案5】:

      将@Kannan 的解决方案扩展为从计算列调用的 UDF:

      create function dbo.fnsPartId(@mykey int)
      returns int
      as
      begin
          declare @Ret int
          ;
          with
          enum as
          (
              select mykey, category, incrementalid, 
                  row_number() over (partition by category order by mykey, category) as rn 
                  from MyTable
          )
          select @Ret = rn from enum where mykey = @mykey
          return @Ret
      end 
      

      并将表格修改为:

      CREATE TABLE dbo.MyTable (
          myKey INT IDENTITY PRIMARY KEY,
          category INT,
          incrementalId AS ([dbo].[fnsPartId]([mykey]))
      );
      

      【讨论】:

        【解决方案6】:

        尝试在列上创建默认约束。使用函数为该行生成下一个值作为该函数返回的默认行。

        【讨论】:

          【解决方案7】:

          请试试这个(在此表上插入触发器后添加)-

          create trigger InsertIncrementalID
          on dbo.MyTable
          after insert
          as
          begin
              update  mt
              set     incrementalId = (select count(mt1.category) from dbo.MyTable mt1 where mt1.category = mt.category)
              from    dbo.MyTable mt
              inner join inserted i on i.myKey = mt.myKey
          end
          

          使用触发器时请记住两点- 1.我们正在从内部触发器更新表,因此如果您在此表上有任何其他触发器(更新后),该触发器也将被执行。 2. 单选查询在该表中插入多行时,该触发器只执行一次。

          【讨论】:

          • 错了,相同的incrementalId会被赋予同一类别的许多行。
          • 添加了我使用此触发器进行的测试的图像。有什么遗漏吗?
          • 如果我们一次插入多行会怎样:INSERT INTO dbo.MyTable(category) VALUES (100), (100), (100); ?
          • 另外:如果多个事务同时插入行会怎样?
          猜你喜欢
          • 2013-07-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-05-25
          相关资源
          最近更新 更多