在更新 SQL Server 时创建自定义 ID(触发?)
我有一个请求表,一旦请求被批准/拒绝,就需要为其分配一个自定义顺序 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
是的,如果做得正确的话,拥有这样一个用于下一个 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.您确实可以使用桌子上的触发器来完成此操作,请参见下文:
这是一种非常可靠的方法。请注意,触发器被编写为允许一次插入多条记录。
参考:
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:
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
您可以尝试这样的操作:
编辑
我不知道您希望如何获取序列,但是如果您想从表上的行计数中提取它,您可以执行以下操作:
You can try something like this:
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:
我建议这不是主键思路中的“id”。
您可以改用持久计算列。其效果是:
这样,只要将新记录添加到表中,工作就会为您完成。
这里需要注意的是,如果您更新了 [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:
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), asgetdate()
(or other non-deterministic values) cannot be used.