在更新 SQL Server 时创建自定义 ID(触发?)

发布于 2024-11-30 01:17:49 字数 3093 浏览 1 评论 0原文

我有一个请求表,一旦请求被批准/拒绝,就需要为其分配一个自定义顺序 ID,例如:

MYCODE-11-0001
MYCODE-11-0002
MYCODE-11-0003 

其中 MYCODE 不会更改,但 11 是当前年份0001 是为年份分配的顺序生成的编号。所以每年都会从 0001 开始。

我从来没有在 SQL Sever 中处理过触发器或存储过程,所以我有通过 VB.net 中的 Linq-to-SQL 在代码中执行此操作的想法,并且我在 SQL Server 中有另一个表跟踪 Next ID

ID     NAME          NextID
1      Request2011    102
2      Request2012    1
3      Request2013    1

如果他们更改了网站上的状态,应用程序会根据当前年份选择名称并获取 NextID 并生成 RequestID。 LINQ 甚至会捕获 NextID 自获取该值、添加 1 并尝试保存该新值以来是否发生了更改。

很确定这会起作用,但我对此并不完全满意,因为它假设一切都来自我的应用程序。如果它是在数据库级别完成的,我会感觉好得多,如果它不依赖于我拥有的这个 NextID 表,我会感觉更好。

有没有什么触发器和存储过程不会太疯狂,可以让我生成这些自定义 ID?我有一个模糊的想法,当 StatusID 更新时我可以运行触发器来运行存储过程来完成一些繁重的工作(如果尚未设置,则生成 RequestID ),但如果我仍然必须依赖 NextID 表,那么我还必须担心锁定/解锁表,对吗?

只是希望能得到比我更了解 SQL Server 的人的一些建议。

注意: 自定义 ID 仅在批准/拒绝请求时分配,而不是在将请求插入数据库时​​分配。每个请求都有它自己唯一的自动生成的身份 PK。


更新:我可能必须坚持使用代码解决方案,因为就并发性而言,它为我提供了所有帮助,但我确实编写了一个触发器,以便我可以学习。

/* --------------------
        SETUP 
--------------------*/
CREATE TABLE [dbo].[TestData](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [data] [varchar](100) NULL,
    [RequestID] [varchar](25) NULL,
    [StatusID] [int] NULL
    )

    INSERT INTO TestData
           ([data],[StatusID])
     VALUES
           ('test',1)
GO


CREATE TABLE [dbo].[NextID](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [varchar](75) NULL,
    [NextID] [int] NULL,
    [DateModified] [date] NULL,
    [ModifiedBy] [varchar](75) NULL
    )
    INSERT INTO NextID
           ([Name],[NextID])          
     VALUES('RequestID2011',1)           
GO

/* --------------------
        TRIGGER
--------------------*/
IF EXISTS (SELECT name FROM sys.objects
      WHERE name = 'UpdateStatusID' AND type = 'TR')
   DROP TRIGGER dbo.UpdateStatusID;
GO

CREATE TRIGGER UpdateStatusID
ON dbo.TestData
AFTER UPDATE 
AS 
IF ( UPDATE (StatusID))
BEGIN
    DECLARE @nextId int
    DECLARE @nextIdName varchar(50)

    /* -- Get NextID based on year's count IE: RequestID2011 */
    SET @nextIdName = 'RequestID' + CONVERT(VARCHAR, YEAR(GetDate()), 50)
    SET @nextid = (Select NextID from dbo.NextID where Name = @nextIdName)

    /* -- Increment NextID */
    UPDATE dbo.NextID set NextID = @nextid + 1 WHERE Name=@nextIdName

    /* -- Set New RequestID */
    UPDATE dbo.TestData
    SET RequestID = 'MYCODE-' + RIGHT(@nextIdName,2) + '-' + CONVERT(VARCHAR, REPLICATE('0', (4- LEN(@nextid))) + @nextid, 50)
    FROM inserted i INNER JOIN dbo.TestData t
    ON i.id = t.id 

END;
GO
/* --------------------
        TEST
--------------------*/
UPDATE dbo.TestData
SET StatusID = 3
WHERE ID = 1;
GO

这样做的作用是防止有人多次更改 StatusID(他们可以这样做,但我只需要生成一次 ID)或防止人们获取 NextID > 同时存在竞争条件问题。

这也仍然需要我在未来几年的数据库中保留几条“RequestIDXXXX”记录。

警告:这不处理并发性,只是我理解触发器和 SQL Server 过程中的一个正在进行的工作。

I have a table for requests and once they are approved/denied they need to be assigned a custom sequential ID like:

MYCODE-11-0001
MYCODE-11-0002
MYCODE-11-0003 

where MYCODE doesn't change but 11 is the current year and 0001 is sequentially generated number assigned for the year. So each year it will start at 0001.

I've never dealt with triggers or stored procedures in SQL Sever so I had the idea of doing this in code via Linq-to-SQL in VB.net and I have another table in SQL Server that keeps track of the Next ID

ID     NAME          NextID
1      Request2011    102
2      Request2012    1
3      Request2013    1

If they changed the status on the website the app selects the name based on the current year and grabs the NextID and generates the RequestID. LINQ will even catch if NextID has changed since grabbing the value, adding one, and trying to save that new value.

Pretty sure this will work but not I'm exactly satisfied with this because it assumes everything will come from my app. I'd feel much better about this if it was done on the database level and even better if it didn't rely on this NextID table I had.

Is there anything with triggers and store procedures that wouldn't be too crazy that would allow me to generate these custom IDs? I have a vague idea that i could run a trigger when the StatusID is updated to run a stored procedure to do some of the heavy duty work(generated RequestID if not already set), but if I still have to rely on the NextID table then I have to worry about locking/unlocking the table as well correct?

Just hoping from some advice from people who know SQL Server far better than I do.

Note: The Custom ID is only assigned at time of approving/denying the request not when it was inserted into the database. Each request does have it own unique auto-generated identity PK.


UPDATE: I may have to stick with the code solution as it does all the hand holding for me as far as concurrency goes, but i did write up a trigger just so i could learn.

/* --------------------
        SETUP 
--------------------*/
CREATE TABLE [dbo].[TestData](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [data] [varchar](100) NULL,
    [RequestID] [varchar](25) NULL,
    [StatusID] [int] NULL
    )

    INSERT INTO TestData
           ([data],[StatusID])
     VALUES
           ('test',1)
GO


CREATE TABLE [dbo].[NextID](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [varchar](75) NULL,
    [NextID] [int] NULL,
    [DateModified] [date] NULL,
    [ModifiedBy] [varchar](75) NULL
    )
    INSERT INTO NextID
           ([Name],[NextID])          
     VALUES('RequestID2011',1)           
GO

/* --------------------
        TRIGGER
--------------------*/
IF EXISTS (SELECT name FROM sys.objects
      WHERE name = 'UpdateStatusID' AND type = 'TR')
   DROP TRIGGER dbo.UpdateStatusID;
GO

CREATE TRIGGER UpdateStatusID
ON dbo.TestData
AFTER UPDATE 
AS 
IF ( UPDATE (StatusID))
BEGIN
    DECLARE @nextId int
    DECLARE @nextIdName varchar(50)

    /* -- Get NextID based on year's count IE: RequestID2011 */
    SET @nextIdName = 'RequestID' + CONVERT(VARCHAR, YEAR(GetDate()), 50)
    SET @nextid = (Select NextID from dbo.NextID where Name = @nextIdName)

    /* -- Increment NextID */
    UPDATE dbo.NextID set NextID = @nextid + 1 WHERE Name=@nextIdName

    /* -- Set New RequestID */
    UPDATE dbo.TestData
    SET RequestID = 'MYCODE-' + RIGHT(@nextIdName,2) + '-' + CONVERT(VARCHAR, REPLICATE('0', (4- LEN(@nextid))) + @nextid, 50)
    FROM inserted i INNER JOIN dbo.TestData t
    ON i.id = t.id 

END;
GO
/* --------------------
        TEST
--------------------*/
UPDATE dbo.TestData
SET StatusID = 3
WHERE ID = 1;
GO

What this doesn't do is protect against someone changing the StatusID more than once (they can do that but i only need to generate the ID once) or protect against people grabbing NextID at the same time and having a race condition problem.

This also still requires me to have several records of 'RequestIDXXXX' in the database for future years.

Warning: This doesn't handle concurrency and is just a work in process on my path to understanding triggers and SQL Server.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

腹黑女流氓 2024-12-07 01:17:49

是的,如果做得正确的话,拥有这样一个用于下一个 ID 的表是一个热点,而且通常也是一个瓶颈。如果没有,那么您将得到重复的 ID...

现在,SQL Server 2008 R2 并没有真正为您提供好的答案。这可能会随着代号为“Denali”的 SQL Server 2011(或可能:SQL Server 2012)而改变。 Denali 将引入序列,它允许您在数据库核心的控制下创建顺序 ID,并且保证这些 ID 是唯一的。您还可以在每年新年的 1 月 1 日将序列重置回 1。

查看 Aaron Bertrand 关于 SQL Server vNext 的博客文章(Denali):使用 SEQUENCE 了解有关序列的更多信息。

就目前而言,是的,我想您使用 Linq-to-SQL 从客户端设置这些 ID 或在触发器内设置这些 ID 的方法可能是最合适的方法(我自己更喜欢触发器解决方案)。我认为该表上的一个 INSTEAD OF INSERT 触发器就足够了 - 如果您插入一个新行,获取您需要的新请求 ID 并将其设置为您刚刚插入的值。

Yes, having such a table for the next ID is a hotspot and often a bottleneck, too - if done correctly. If not, then you'll get duplicate ID's....

Right now, SQL Server 2008 R2 doesn't really have a good answer for you. This might change with SQL Server 2011 (or possibly: SQL Server 2012), codenamed "Denali". Denali will introduce sequences, which allow you to create sequential ID's under the database core's control, and those are guaranteed to be unique. You can also reset sequences back to 1 on Jan 1 of each new year.

Check out Aaron Bertrand's blog post on SQL Server vNext (Denali): Using SEQUENCE for more information on sequences.

For now, yes, I guess your approach with either setting those ID's from your client-side using Linq-to-SQL, or inside a trigger, are probably the most suitable approaches (I would prefer the trigger solution, myself). I would think a single INSTEAD OF INSERT trigger on that table would suffice - if you insert a new row, get the new request ID you need and set it to the values you've just inserted.

笑忘罢 2024-12-07 01:17:49

您确实可以使用桌子上的触发器来完成此操作,请参见下文:

create table NextID (
    id  int,
    name    varchar(10),
    nextid  int
)

create table TestData (
    sequence    varchar(14) NOT NULL,
    data    varchar(10)
)

insert into NextID
values(1, 'Req2011', 1)


--Important stuff starts here
ALTER TRIGGER MySequence
ON TestData
INSTEAD OF INSERT
AS
DECLARE @nextid int

SET @nextid = (select nextid from NextID ) - 1

UPDATE NextID
SET nextid = nextid + (select COUNT(*) from inserted)
WHERE id = 1;

WITH cte AS (
    select ROW_NUMBER() OVER (ORDER BY (select 1)) + @nextid as sequence, i.data
    from inserted i
)
insert into TestData
select 'MYCODE-' + RIGHT(YEAR(GETDATE()), 2) + '-' + RIGHT('000' + CAST(sequence as varchar), 4), data
from cte;

这是一种非常可靠的方法。请注意,触发器被编写为允许一次插入多条记录。

参考:
http://msdn.microsoft.com/en-us/library/ms189799.aspx

http://www.sqlmag.com/article/sql-server/nondeterministic-row-numbers

You can indeed do it with a trigger on your table, see below:

create table NextID (
    id  int,
    name    varchar(10),
    nextid  int
)

create table TestData (
    sequence    varchar(14) NOT NULL,
    data    varchar(10)
)

insert into NextID
values(1, 'Req2011', 1)


--Important stuff starts here
ALTER TRIGGER MySequence
ON TestData
INSTEAD OF INSERT
AS
DECLARE @nextid int

SET @nextid = (select nextid from NextID ) - 1

UPDATE NextID
SET nextid = nextid + (select COUNT(*) from inserted)
WHERE id = 1;

WITH cte AS (
    select ROW_NUMBER() OVER (ORDER BY (select 1)) + @nextid as sequence, i.data
    from inserted i
)
insert into TestData
select 'MYCODE-' + RIGHT(YEAR(GETDATE()), 2) + '-' + RIGHT('000' + CAST(sequence as varchar), 4), data
from cte;

This is a pretty reliable way of doing it. Take note that the trigger is written to allow the insert of more than one record at a time.

Reference:
http://msdn.microsoft.com/en-us/library/ms189799.aspx

http://www.sqlmag.com/article/sql-server/nondeterministic-row-numbers

じее 2024-12-07 01:17:49

您可以尝试这样的操作:

DECLARE @CustomID VARCHAR(255)
SELECT @CustomID = 'MYCODE-' + CAST((YEAR( GETDATE()) % 100) AS CHAR(2)) + '-' + REPLICATE('0', (4 - LEN((<Sequence> + 1)))) + CAST((<Sequence> + 1) AS VARCHAR(5))

编辑

我不知道您希望如何获取序列,但是如果您想从表上的行计数中提取它,您可以执行以下操作:

SELECT COUNT(SomethingID) --Optionally add +1
FROM   SomethingTable
WHERE  DATEDIFF(YEAR, SomeDateColumn, GETDATE()) = 0

You can try something like this:

DECLARE @CustomID VARCHAR(255)
SELECT @CustomID = 'MYCODE-' + CAST((YEAR( GETDATE()) % 100) AS CHAR(2)) + '-' + REPLICATE('0', (4 - LEN((<Sequence> + 1)))) + CAST((<Sequence> + 1) AS VARCHAR(5))

EDIT

I don't know how you're looking to get the sequence, but if you're looking to pull it from a row count on the table, you can do something like this:

SELECT COUNT(SomethingID) --Optionally add +1
FROM   SomethingTable
WHERE  DATEDIFF(YEAR, SomeDateColumn, GETDATE()) = 0
会发光的星星闪亮亮i 2024-12-07 01:17:49

我建议这不是主键思路中的“id”。

您可以改用持久计算列。其效果是:

ALTER TABLE myTable
ADD CustomID AS (

    'MYCODE-' + CAST(RIGHT(100 + year([date_field]),2) as varchar(2)) + CAST(RIGHT(10000 + [pk_field],4) as varchar(4))

) PERSISTED

这样,只要将新记录添加到表中,工作就会为您完成。

这里需要注意的是,如果您更新了 [pk_field] 列(这不太可能......?),您的计算列将重新计算。但除此之外,只要您使用“PERSISTED”,它将保持不变。

编辑
我用上面的 [date_field] 替换了 getdate() 。我仍然认为计算列对您来说是一个不错的选择,但需要注意的是您需要记录上的日期字段(date_inserted,或者您有什么),如 getdate() (或其他非-确定性值)不能使用。

I would suggest that this not be the "id", in the primary key line of thought.

You could use a persisted computed column instead. Something to the effect of:

ALTER TABLE myTable
ADD CustomID AS (

    'MYCODE-' + CAST(RIGHT(100 + year([date_field]),2) as varchar(2)) + CAST(RIGHT(10000 + [pk_field],4) as varchar(4))

) PERSISTED

This way, the work is done for you whenever a new record is added to the table.

The caution here is just that if you ever update the [pk_field] column (which is unlikely...?), your computed column will re-compute. But otherwise, as long as you use 'PERSISTED', it will remain un-changed.

EDIT
I replaced getdate() with [date_field] above. I still think a computed column is a good option for you, but the caveat is that you need a date field on the record (date_inserted, or what have you), as getdate() (or other non-deterministic values) cannot be used.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文