您将如何在 Microsoft SQL Server 中实现序列?
有没有人有一个好的方法来实现 SQL Server 中的序列之类的东西?
有时你只是不想使用 GUID,而且事实上它们实在是太丑了。 也许您想要的序列不是数字? 此外,插入一行然后询问数据库数字是多少似乎很黑客。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(16)
考虑以下片段。
Consider the following snippet.
正如 sqljunkiesshare states 所示,序列已添加到 SQL Server 2012 中。以下是在 GUI 中执行此操作的方法。 这相当于:
文件夹如下所示:
相当于上面的 SQL 语句,但是,我会考虑
根据您的需要更改这些(请参阅下面的注释)。
注意:
默认的起始值、最小值和最大值由数据类型的范围决定,在本例中为 int。 如果您想使用 int 以外的其他类型,请参阅此处了解更多数据类型范围< /a>.
很有可能您希望序列从 1 开始,并且您
可能也希望最小值为 1。
As sqljunkiesshare states, sequences were added to SQL Server 2012. Here's how to do it in the GUI. This is the equivolent of:
folder as shown below:
equivalent of the SQL statement above, however, I would consider
changing these depending on your needs (see notes below).
Notes:
The default Start value, Minimum value, and Maximum value were determined by the range of the data type which was an int in this case. See here for more data type ranges if you want to use something other than an int.
Pretty good chance you'll want your sequence to start at 1 and you
might want you minimum value as 1, too.
我完全同意,并在去年的一个项目中做到了这一点。
我刚刚创建了一个表,其中包含序列名称、当前值和序列名称。 增量金额。
然后我创建了 2 个进程来添加 & 删除它们。 接下来还有 2 个函数,& 获取最新信息。
I totally agree and did this last year on a project.
I just created a table with the name of the sequence, current value, & increment amount.
Then I created a 2 procs to add & delete them. And 2 functions to get next, & get current.
如果您想使用顺序键插入数据,但又不想再次查询数据库来获取刚刚插入的键,我认为您唯一的两个选择是:
如果我正在执行客户端密钥生成,我喜欢 GUID。 我觉得它们真的很漂亮。
这条线应该放在跑车引擎盖上的某个地方。
If you want to insert data with a sequential key, but you don't want to have to query the database again to get the just-inserted key, I think your only two choices are:
If I'm doing client-side key generation, I love GUIDs. I think they're beautiful as heck.
That line should be laying on the hood of a sportscar somewhere.
如果您使用的是 SQL Server 2005,您可以选择使用 Row_Number
If you are using SQL Server 2005 you have the option of using Row_Number
标识列的另一个问题是,如果您有多个表,其中序列号需要唯一,则标识列不起作用。 正如 Corey Trager 提到的那样,自行设计的序列实现类型可能会出现一些锁定问题。
最直接的等效解决方案似乎是创建一个带有单列标识的 SQL Server 表,它取代了单独类型的“序列”对象。 例如,如果在 Oracle 中,您将拥有来自一个序列的两个表,例如 Dogs <-- 序列对象 --> 然后,在 SQL Server 中的 Cats 中,您将创建三个数据库对象,所有表都像 Dogs <-- Pets with Identity 列 --> 猫。 您可以在 Pets 表中插入一行以获取通常使用 NEXTVAL 的序列号,然后像通常从用户那里获取宠物的实际类型后那样插入到 Dogs 或 Cats 表中。 任何其他公共列都可以从 Dogs/Cats 表移至 Pets 超类型表,从而产生一些后果:1) 每个序列号将占一行,2) 获取序列号时无法填充的任何列将被删除。需要有默认值,并且 3) 需要连接才能获取所有列。
The other problem with an identity columns is that if you have more than one table where the sequence numbers need to be unique, an identity column doesn't work. And like Corey Trager mentions, a roll-your-own type of sequence implementation might present some locking issues.
The most straightforwardly equivalent solutions seems to be to create a SQL Server table with a single column for the identity, which takes the place of a separate type of "sequence" object. For example if in Oracle you would have two tables from one sequence such as Dogs <-- sequence object --> Cats then in SQL Server you would create three database objects, all tables like Dogs <-- Pets with identity column --> Cats. You would insert a row into the Pets table to get the sequence number where you would normally use NEXTVAL and then insert into the Dogs or Cats table as you normally would once you get the actual type of pet from the user. Any additional common columns could be moved from the Dogs/Cats tables into the Pets supertype table, with some consequences that 1) there would be one row for each sequence number, 2) any columns not able to be populated when getting the sequence number would need to have default values and 3) it would require a join to get all of the columns.
通过SQL你可以使用这个策略;
并通过此 SQL 读取唯一的下一个值
By SQL you can use this strategy;
and read the unique next value whit this SQL
交易安全!对于 2012 年之前的 SQLServer 版本...(感谢 Matt G.)
这次讨论中缺少的一件事是交易安全。 如果您从序列中获取数字,则该数字必须是唯一的,并且其他应用程序或代码不应能够获取该数字。 就我而言,我们经常从序列中提取唯一的数字,但实际的交易可能会跨越相当长的时间,因此我们不希望其他人在提交交易之前获得相同的数字。
我们需要模仿预言机序列的行为,其中一个数字在被拉取时被保留。
我的解决方案是使用 xp_cmdshell 在数据库上获取单独的会话/事务,以便我们可以立即更新整个数据库的序列,甚至在事务完成之前也是如此。
该解决方案需要一个表来保存使用的序列值,以及一个创建第二个自主事务的过程,以确保并发会话不会混乱。 您可以拥有任意数量的独特序列,它们通过名称引用。 下面的示例代码被修改为省略序列历史表上的请求用户和日期戳(用于审计),但我认为对于该示例来说,复杂性较低更好;-)。
现在要使该过程正常工作,您需要启用 xp_cmdshell,有很多关于如何执行此操作的很好的描述,以下是我在尝试使事情正常工作时所做的个人笔记。 基本思想是,您需要在 SQLServer Surface 配置中打开 xp_cmdshell,并且需要将用户帐户设置为 xp_cmdshell 命令将在其下运行的帐户,该帐户将访问数据库以插入序列号并提交它。
TRANSACTION SAFE ! For SQLServer versions before 2012... (thanks Matt G.)
One thing missing in this discussion is transaction safety. If you get a number from a sequence, that number must be unique, and no other app or code should be able to get that number. In my case, we often pull unique numbers from sequences, but the actual transaction may span a considerable amount of time, so we don't want anyone else getting the same number before we commit the transaction.
We needed to mimic the behavior of oracle sequences, where a number was reserved when it was pulled.
My solution is to use xp_cmdshell to get a separate session/transaction on the database, so that we can immediately update the sequence, for the whole database, even before the transaction is complete.
The solution requires a single table to hold used sequence values, and a procedure That creates a second autonomous transaction to insure that concurrent sessions don't get tangled up. You can have as many unique sequences as you like, they are referenced by name. Example code below is modified to omit requesting user and date stamp on the sequence history table (for audit) but I thought less-complex was better for the example ;-).
Now to get that procedure to work, you are going to need to enable xp_cmdshell, there are lots of good descriptions of how to do that, here are my personal notes that I made when I was trying to get things to work. Basic idea is that you need xp_cmdshell turned on in SQLServer Surface Are a configuration and you need to set a user account as the account that the xp_cmdshell command will run under, that will access the database to insert the sequence number and commit it.
Sql Server 2012 引入了
SEQUENCE
对象 ,它允许您生成不与任何表关联的连续数值。创建它们很容易:
在插入之前使用它们的示例:
Sql Server 2012 has introduced
SEQUENCE
objects, which allow you to generate sequential numeric values not associated with any table.Creating them are easy:
An example of using them before insertion:
正如 sqljunkieshare 正确所说,从 SQL Server 2012 开始,有一个内置的
SEQUENCE
功能。最初的问题没有澄清,但我假设序列的要求是:
我想评论一下原来问题中的陈述:
好吧,我们对此无能为力。数据库是序列号的提供者,数据库处理所有这些你自己无法处理的并发问题。除了询问之外,我没有其他选择序列的下一个值的数据库必须有一个“给我序列的下一个值”的原子操作,并且只有数据库可以提供这样的原子操作。没有客户端代码可以保证他是唯一使用该序列的人。
要回答标题“您将如何实现序列”中的问题 - 我们使用的是 2008,它没有
SEQUENCE
。因此,在阅读了该主题后,我最终得到了以下结果:对于我需要的每个序列,我创建了一个仅包含一个
IDENTITY
列的单独的辅助表(与 2012 年的方式相同)。创建一个单独的序列对象)。您可以为其指定起始值和增量。
然后我创建一个存储过程,它将返回序列的下一个值。
过程将启动一个事务,在辅助表中插入一行,记住生成的标识值并回滚事务。 因此,辅助表始终保持为空。
关于该过程的一些注释。
首先,如何将一行插入只有一个标识列的表中并不明显。 答案是
默认值
。然后,如果在另一个事务中调用该程序,我希望该程序能够正常工作。 简单的
ROLLBACK
会回滚所有内容(如果存在)是嵌套事务。 就我而言,我只需要回滚INSERT
到辅助表中,因此我使用了保存交易
。这就是我使用该过程的方式(在其他一些大过程中,例如创建一个新合约):
如果您需要一次生成一个序列值,那么一切都可以正常工作。 对于合同,每个合同都是单独创建的,因此这种方法非常有效。 我可以确定所有合同始终都有唯一的合同编号。
注意:只是为了防止可能出现的问题。 这些合同号是我的合同表所具有的代理身份密钥的补充。 代理键是用于引用完整性的内部键。 生成的合同号是打印在合同上的人性化号码。 此外,同一个合同表包含最终合同和提案,它们可以成为合同,也可以永远保留为提案。 提案和合同都保存非常相似的数据,这就是为什么它们保存在同一个表中。 只需更改一行中的标志即可将提案变成合同。 提案使用单独的数字序列进行编号,为此我有第二个表
SequenceProposalNumber
和第二个过程GetNewProposalNumber
。不过,最近我遇到了一个问题。
我需要批量生成序列值,而不是一个一个地生成。
我需要一个程序来一次性处理给定季度内收到的所有付款。 此类处理的结果可能是我想要在
Transactions
表中记录的约 20,000 笔交易。 我这里有类似的设计。Transactions
表具有最终用户永远看不到的内部IDENTITY
列,并且它具有一个人性化的交易编号,可以打印在报表上。 因此,我需要一种方法来批量生成给定数量的唯一值。本质上,我使用了相同的方法,但有一些特殊之处。
首先,没有直接的方法可以在只有一个 IDENTITY 列的表中插入多行。 虽然有一个解决方法(ab)使用
MERGE
,我最终没有使用它。 我认为添加虚拟
Filler
列会更容易。 我的序列表将始终为空,因此额外的列并不重要。辅助表看起来像这样:
过程看起来像这样:
这就是它的使用方式(在一些计算事务的大型存储过程中):
这里没有什么需要解释的。
我需要将给定数量的行插入
SequenceS2TransactionNumber
表中。 我为此使用了一个辅助Numbers
表。 该表仅保存 1 到 100,000 之间的整数。 它也用在系统的其他地方。 我检查Numbers
表中是否有足够的行,并根据需要通过自身交叉连接将其扩展为 100,000 * 100,000。我必须将批量插入的结果保存在某处,并以某种方式将其传递给调用者。 在存储过程外部传递表的一种方法是使用临时表。 我不能在这里使用表值参数,因为不幸的是它是只读的。 另外,我无法直接将生成的序列值插入临时表
#NewS2TransactionNumbers
。 我无法在OUTPUT
子句中使用#NewS2TransactionNumbers
,因为ROLLBACK
会清理它。 幸运的是,表变量不受ROLLBACK 的影响。因此,我使用表变量@TableTransactionNumbers 作为
OUTPUT
子句的目标。 然后我ROLLBACK
事务来清理序列表。 然后将生成的序列值从表变量@TableTransactionNumbers
复制到临时表#NewS2TransactionNumbers
,因为只有临时表#NewS2TransactionNumbers
对存储过程的调用者。 表变量@TableTransactionNumbers 对于存储过程的调用者不可见。此外,还可以使用 OUTPUT 子句将生成的序列直接发送给调用者(正如您在使用 MERGE 的注释变体中看到的那样)。 它本身工作得很好,但我需要在某个表中生成的值,以便在调用存储过程中进行进一步处理。 当我尝试这样的事情时:
我收到错误
但是,我需要在
EXEC
中使用ROLLBACK
,这就是为什么我最终有这么多临时表。毕竟,如果切换到具有正确
SEQUENCE
对象的最新版本的 SQL Server,该多好啊。As sqljunkieshare correctly said, starting from SQL Server 2012 there is a built-in
SEQUENCE
feature.The original question doesn't clarify, but I assume that requirements for the Sequence are:
I'd like to comment the statement in the original question:
Well, there is not much we can do about it here. The DB is a provider of the sequential numbers and DB handles all these concurrency issues that you can't handle yourself. I don't see alternative to asking the DB for the next value of the sequence. There has to be an atomic operation "give me the next value of the sequence" and only DB can provide such atomic operation. No client code can guarantee that he is the only one working with the sequence.
To answer the question in the title "how would you implement sequences" - We are using 2008, which doesn't have the
SEQUENCE
feature, so after some reading on this topic I ended up with the following.For each sequence that I need I create a separate helper table with just one
IDENTITY
column (in the same fashion as in 2012 you would create a separate Sequence object).You can specify starting value and increment for it.
Then I create a stored procedure that would return the next value of the sequence.
Procedure would start a transaction, insert a row into the helper table, remember the generated identity value and roll back the transaction. Thus the helper table always remains empty.
Few notes about the procedure.
First, it was not obvious how to insert a row into a table that has only one identity column. The answer is
DEFAULT VALUES
.Then, I wanted procedure to work correctly if it was called inside another transaction. The simple
ROLLBACK
rolls back everything if there are nested transactions. In my case I need to roll back onlyINSERT
into the helper table, so I usedSAVE TRANSACTION
.This is how I use the procedure (inside some other big procedure that, for example, creates a new contract):
It all works fine if you need to generate sequence values one at a time. In case of contracts, each contract is created individually, so this approach works perfectly. I can be sure that all contracts always have unique contract numbers.
NB: Just to prevent possible questions. These contract numbers are in addition to surrogate identity key that my Contracts table has. The surrogate key is internal key that is used for referential integrity. The generated contract number is a human-friendly number that is printed on the contract. Besides, the same Contracts table contains both final contracts and Proposals, which can become contracts or can remain as proposals forever. Both Proposals and Contracts hold very similar data, that's why they are kept in the same table. Proposal can become a contract by simply changing the flag in one row. Proposals are numbered using a separate sequence of numbers, for which I have a second table
SequenceProposalNumber
and second procedureGetNewProposalNumber
.Recently, though, I came across a problem.
I needed to generate sequence values in a batch, rather than one-by-one.
I need a procedure that would process all payments that were received during a given quarter in one go. The result of such processing could be ~20,000 transactions that I want to record in the
Transactions
table. I have similar design here.Transactions
table has internalIDENTITY
column that end user never sees and it has a human-friendly Transaction Number that would be printed on the statement. So, I need a way to generate a given number of unique values in a batch.Essentially, I used the same approach, but there are few peculiarities.
First, there is no direct way to insert multiple rows in a table with only one
IDENTITY
column. Though there is a workaround by (ab)usingMERGE
, I didn't use it in the end. I decided that it was easier to add a dummyFiller
column. My Sequence table is going to be always empty, so extra column doesn't really matter.The helper table looks like this:
The procedure looks like this:
And this is how it is used (inside some big stored procedure that calculates transactions):
There are few things here that require explanation.
I need to insert a given number of rows into the
SequenceS2TransactionNumber
table. I use a helperNumbers
table for this. This table simply holds integer numbers from 1 to 100,000. It is used in other places in the system as well. I check if there is enough rows in theNumbers
table and expand it to 100,000 * 100,000 by cross joining with itself if needed.I have to save the result of the bulk insert somewhere and pass it to the caller somehow. One way to pass a table outside of the stored procedure is to use a temporary table. I can't use table-valued parameter here, because it is read-only unfortunately. Also, I can't directly insert the generated sequence values into the temporary table
#NewS2TransactionNumbers
. I can't use#NewS2TransactionNumbers
in theOUTPUT
clause, becauseROLLBACK
will clean it up. Fortunately, the table variables are not affected by theROLLBACK
.So, I use table variable
@TableTransactionNumbers
as a destination of theOUTPUT
clause. Then IROLLBACK
the transaction to clean up the Sequence table. Then copy the generated sequence values from table variable@TableTransactionNumbers
to the temporary table#NewS2TransactionNumbers
, because only temporary table#NewS2TransactionNumbers
can be visible to the caller of the stored procedure. The table variable@TableTransactionNumbers
is not visible to the caller of the stored procedure.Also, it is possible to use
OUTPUT
clause to send the generated sequence directly to the caller (as you can see in the commented variant that usesMERGE
). It works fine by itself, but I needed the generated values in some table for further processing in the calling stored procedure. When I tried something like this:I was getting an error
But, I need
ROLLBACK
inside theEXEC
, that's why I ended up having so many temporary tables.After all this, how nice would it be to switch to the latest version of SQL server which has a proper
SEQUENCE
object.身份列大致类似于序列。
An Identity column is roughly analogous to a sequence.
您可以只使用普通的旧表并将它们用作序列。 这意味着您的插入内容将始终是:
但不要这样做。 锁定会很糟糕......
我开始使用 SQL Server,对我来说,Oracle“序列”方案看起来就像一个 hack。 我猜你是从另一个方向来的,scope_identity() 看起来像是一个黑客。
克服它。 在罗马做到入乡随俗。
You could just use plain old tables and use them as sequences. That means your inserts would always be:
But don't do this. The locking would be bad...
I started on SQL Server and to me, the Oracle "sequence" scheme looked like a hack. I guess you are coming from the other direction and to you, and scope_identity() looks like a hack.
Get over it. When in Rome, do as the Romans do.
我用来解决这个问题的方法是使用一个表“序列”来存储我的所有序列和一个“nextval”存储过程。
Sql 表:
使用 PK_Sequences 只是为了确保永远不会出现同名的序列。
Sql 存储过程:
插入一些序列:
最后获取序列的下一个值,
一些 C# 代码从序列表中获取下一个值,
The way that i used to solve this problem was a table 'Sequences' that stores all my sequences and a 'nextval' stored procedure.
Sql Table:
The PK_Sequences is used just to be sure that there will never be sequences with the same name.
Sql Stored Procedure:
Insert some sequences:
Finally get next value of a sequence,
Some c# code to get the next value from Sequence table,
在 SQL Server 2012 中,您可以简单地使用
在 2005 和 2008 中,您可以使用公共表表达式获取任意序列号列表。
这是一个示例(请注意 MAXRECURSION 选项很重要):
In SQL Server 2012, you can simply use
In 2005 and 2008, you can get an arbitrary list of sequential numbers using a common table expression.
Here's an example (note that the MAXRECURSION option is important):
Oracle 实现的序列需要在插入之前调用数据库。
SQL Server 实现的身份需要在插入后调用数据库。
其中一个并不比另一个更黑客。 最终效果是相同的 - 依赖/依赖数据存储来提供唯一的人工键值以及(在大多数情况下)对存储的两次调用。
我假设您的关系模型基于人工键,在这种情况下,我将提供以下观察结果:
我们永远不应该寻求赋予人工键以意义; 它们的唯一目的应该是链接相关记录。
您对订购数据有何需求? 它可以在视图(演示文稿)中处理吗?还是它是必须保留的数据的真实属性?
Sequences as implemented by Oracle require a call to the database before the insert.
identities as implemented by SQL Server require a call to the database after the insert.
One is no more hackish than the other. The net effect is the same - a reliance/dependency on the data store to provide unique artificial key values and (in most cases) two calls to the store.
I'm assuming that your relational model is based on artificial keys, and in this context, I'll offer the following observation:
We should never seek to imbue artificial keys with meaning; their only purpose should be to link related records.
What is your need related to ordering data? can it be handled in the view (presentation) or is it a true attribute of your data which must be persisted?
创建一个带有标识符的阶段表。
在加载阶段表之前,截断并重新设定标识符以从 1.
加载表开始。 现在,每一行都有一个从 1 到 N 的唯一值。
创建一个包含序列号的表。 这可能是多行,每个序列一行。
从您创建的序列表中查找序列号。
通过将阶段表中的行数添加到序列号来更新序列号。
通过添加您查找的序列号来更新阶段表标识符。 这是一个简单的一步过程。
或者
加载目标表,在 ETL 中加载时将序列号添加到标识符中。 这可以利用批量加载器并允许其他转换。
Create a stage table with an identifier on it.
Before loading the stage table, truncate and reseed the identifier to start at 1.
Load your table. Each row now has a unique value from 1 to N.
Create a table that holds sequence numbers. This could be several rows, one for each sequence.
Lookup the sequence number from the sequence table you created.
Update the seqence number by adding the number of rows in the stage table to the sequence number.
Update the stage table identifier by adding the seqence number you looked up to it. This is an easy one step process.
or
Load your target table, add the sequence number to the identifier as you load in ETL. This can take advantage of the bulk loader and allow for other transformations.