如何在Microsoft SQL Server中实现序列?
有没有人有一种在SQLServer中实现序列之类的东西的好方法如何在Microsoft SQL Server中实现序列?,sql,sql-server,database,sequences,Sql,Sql Server,Database,Sequences,有没有人有一种在SQLServer中实现序列之类的东西的好方法 有时候你只是不想使用GUID,除了它们很难看之外。也许你想要的序列不是数字?此外,插入一行,然后询问数据库的数字是多少,这似乎太老套了。大致类似于序列。您可以使用普通的旧表,并将它们用作序列。这意味着您的插入始终是: BEGIN TRANSACTION SELECT number from plain old table.. UPDATE plain old table, set the number to be the n
有时候你只是不想使用GUID,除了它们很难看之外。也许你想要的序列不是数字?此外,插入一行,然后询问数据库的数字是多少,这似乎太老套了。大致类似于序列。您可以使用普通的旧表,并将它们用作序列。这意味着您的插入始终是:
BEGIN TRANSACTION
SELECT number from plain old table..
UPDATE plain old table, set the number to be the next number
INSERT your row
COMMIT
但不要这样做。锁会坏的
我从SQL Server开始,对我来说,Oracle的“序列”方案看起来像是一个黑客。我猜你是从另一个方向来的,对你来说,scope_identity()看起来像一个黑客
忘掉它。入乡随俗 我完全同意,并在去年的一个项目中这样做了 我刚刚创建了一个表,其中包含序列名称、当前值和增量金额
然后我创建了两个程序来添加和删除它们。和两个函数来获取下一个键和当前键。如果您想用顺序键插入数据,但不想再次查询数据库来获取刚刚插入的键,我认为您只有两个选择:
row["ID"] = Guid.NewGuid();
这条线应该放在跑车的引擎盖上。Oracle实现的序列需要在插入之前调用数据库。 SQL Server实现的标识需要在插入后调用数据库 一个并不比另一个更老练。净效果是一样的——依赖/依赖数据存储来提供唯一的人工键值和(在大多数情况下)对存储的两个调用 我假设您的关系模型基于人工键,在本文中,我将提供以下观察结果: 我们决不应该试图赋予人工钥匙以意义;它们的唯一目的应该是链接相关记录
与订购数据相关的需求是什么?它可以在视图(表示)中处理,还是必须持久化的数据的真实属性 如果您使用的是SQL Server 2005,您可以选择使用行号创建一个带有标识符的阶段表 在加载stage表之前,截断并重新设置标识符的种子,使其从1开始 装上你的桌子。现在,每行都有一个从1到N的唯一值 创建一个包含序列号的表。这可以是几行,每个序列一行 从创建的序列表中查找序列号。 通过将阶段表中的行数添加到序列号来更新序列号 通过添加查找到的序列号来更新阶段表标识符。这是一个简单的一步过程。 或
加载目标表,在ETL中加载时将序列号添加到标识符中。这可以利用批量加载程序并允许进行其他转换。标识列的另一个问题是,如果有多个表的序列号需要唯一,则标识列不起作用。正如Corey Trager提到的,您自己类型的序列实现可能会出现一些锁定问题
最直截了当的等效解决方案似乎是创建一个SQL Server表,其中包含一列标识,它取代了单独类型的“序列”对象。例如,如果在Oracle中,一个序列中有两个表,例如Dogs-Cats,那么在SQL Server中,您将创建三个数据库对象,所有表都像Dogs-Cats。您可以在Pets表中插入一行,以获取通常使用NEXTVAL的序列号,然后像从用户处获取实际类型的宠物一样,插入Dogs或Cats表。任何额外的公共列都可以从Dogs/Cats表移动到Pets超类型表中,其结果是1)每个序列号都有一行,2)获取序列号时无法填充的任何列都需要具有默认值,3)需要联接才能获取所有列。Sql Server 2012已引入,允许您生成与任何表都不关联的顺序数值 创建它们很容易:
CREATE SEQUENCE Schema.SequenceName
AS int
INCREMENT BY 1 ;
插入前使用它们的示例:
DECLARE @NextID int ;
SET @NextID = NEXT VALUE FOR Schema.SequenceName;
-- Some work happens
INSERT Schema.Orders (OrderID, Name, Qty)
VALUES (@NextID, 'Rim', 2) ;
请参阅我的博客,深入了解如何使用序列:
考虑以下代码片段
CREATE TABLE [SEQUENCE](
[NAME] [varchar](100) NOT NULL,
[NEXT_AVAILABLE_ID] [int] NOT NULL,
CONSTRAINT [PK_SEQUENCES] PRIMARY KEY CLUSTERED
(
[NAME] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE PROCEDURE CLAIM_IDS (@sequenceName varchar(100), @howMany int)
AS
BEGIN
DECLARE @result int
update SEQUENCE
set
@result = NEXT_AVAILABLE_ID,
NEXT_AVAILABLE_ID = NEXT_AVAILABLE_ID + @howMany
where Name = @sequenceName
Select @result as AVAILABLE_ID
END
GO
在SQL Server 2012中,您只需使用
CREATE SEQUENCE
在2005年和2008年,您可以使用公共表表达式获得任意序列号列表
下面是一个示例(请注意,MAXRECURSION选项很重要):
DECLARE@MinValue INT=1;
声明@MaxValue INT=1000;
使用IndexMaker(IndexNumber)作为
(
挑选
@作为索引编号的最小值
联合所有选择
指数编号+1
从…起
索引生成器
其中IndexNumber<@MaxValue
)
挑选
指数
从…起
索引生成器
订购人
指数
选项
(最大递归0)
我用来解决这个问题的方法是一个表'Sequences',它存储了我所有的序列和一个'nextval'存储过程
Sql表:
CREATE TABLE Sequences (
name VARCHAR(30) NOT NULL,
value BIGINT DEFAULT 0 NOT NULL,
CONSTRAINT PK_Sequences PRIMARY KEY (name)
);
使用PK_序列只是为了确保永远不会有同名的序列
Sql存储过程:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'nextVal') AND type in (N'P', N'PC')) DROP PROCEDURE nextVal;
GO
CREATE PROCEDURE nextval
@name VARCHAR(30)
AS
BEGIN
DECLARE @value BIGINT
BEGIN TRANSACTION
UPDATE Sequences
SET @value=value=value + 1
WHERE name = @name;
-- SELECT @value=value FROM Sequences WHERE name=@name
COMMIT TRANSACTION
SELECT @value AS nextval
END;
插入一些序列:
INSERT INTO Sequences(name, value) VALUES ('SEQ_Workshop', 0);
INSERT INTO Sequences(name, value) VALUES ('SEQ_Participant', 0);
INSERT INTO Sequences(name, value) VALUES ('SEQ_Invoice', 0);
最后得到序列的下一个值
execute nextval 'SEQ_Participant';
一些c#代码从序列表中获取下一个值
public long getNextVal()
{
long nextval = -1;
SqlConnection connection = new SqlConnection("your connection string");
try
{
//Connect and execute the select sql command.
connection.Open();
SqlCommand command = new SqlCommand("nextval", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@name", SqlDbType.NVarChar).Value = "SEQ_Participant";
nextval = Int64.Parse(command.ExecuteScalar().ToString());
command.Dispose();
}
catch (Exception) { }
finally
{
connection.Dispose();
}
return nextval;
}
正如正确所说,从SQLServer2012开始,有一个内置功能
原来的问题并不重要
CREATE TABLE [dbo].[SequenceContractNumber]
(
[ContractNumber] [int] IDENTITY(1,1) NOT NULL,
CONSTRAINT [PK_SequenceContractNumber] PRIMARY KEY CLUSTERED ([ContractNumber] ASC)
)
CREATE PROCEDURE [dbo].[GetNewContractNumber]
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @Result int = 0;
IF @@TRANCOUNT > 0
BEGIN
-- Procedure is called when there is an active transaction.
-- Create a named savepoint
-- to be able to roll back only the work done in the procedure.
SAVE TRANSACTION ProcedureGetNewContractNumber;
END ELSE BEGIN
-- Procedure must start its own transaction.
BEGIN TRANSACTION ProcedureGetNewContractNumber;
END;
INSERT INTO dbo.SequenceContractNumber DEFAULT VALUES;
SET @Result = SCOPE_IDENTITY();
-- Rollback to a named savepoint or named transaction
ROLLBACK TRANSACTION ProcedureGetNewContractNumber;
RETURN @Result;
END
DECLARE @VarContractNumber int;
EXEC @VarContractNumber = dbo.GetNewContractNumber;
CREATE TABLE [dbo].[SequenceS2TransactionNumber]
(
[S2TransactionNumber] [int] IDENTITY(1,1) NOT NULL,
[Filler] [int] NULL,
CONSTRAINT [PK_SequenceS2TransactionNumber]
PRIMARY KEY CLUSTERED ([S2TransactionNumber] ASC)
)
-- Description: Returns a list of new unique S2 Transaction numbers of the given size
-- The caller should create a temp table #NewS2TransactionNumbers,
-- which would hold the result
CREATE PROCEDURE [dbo].[GetNewS2TransactionNumbers]
@ParamCount int -- not NULL
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SET XACT_ABORT ON;
IF @@TRANCOUNT > 0
BEGIN
-- Procedure is called when there is an active transaction.
-- Create a named savepoint
-- to be able to roll back only the work done in the procedure.
SAVE TRANSACTION ProcedureGetNewS2TransactionNos;
END ELSE BEGIN
-- Procedure must start its own transaction.
BEGIN TRANSACTION ProcedureGetNewS2TransactionNos;
END;
DECLARE @VarNumberCount int;
SET @VarNumberCount =
(
SELECT TOP(1) dbo.Numbers.Number
FROM dbo.Numbers
ORDER BY dbo.Numbers.Number DESC
);
-- table variable is not affected by the ROLLBACK, so use it for temporary storage
DECLARE @TableTransactionNumbers table
(
ID int NOT NULL
);
IF @VarNumberCount >= @ParamCount
BEGIN
-- the Numbers table is large enough to provide the given number of rows
INSERT INTO dbo.SequenceS2TransactionNumber
(Filler)
OUTPUT inserted.S2TransactionNumber AS ID INTO @TableTransactionNumbers(ID)
-- save generated unique numbers into a table variable first
SELECT TOP(@ParamCount) dbo.Numbers.Number
FROM dbo.Numbers
OPTION (MAXDOP 1);
END ELSE BEGIN
-- the Numbers table is not large enough to provide the given number of rows
-- expand the Numbers table by cross joining it with itself
INSERT INTO dbo.SequenceS2TransactionNumber
(Filler)
OUTPUT inserted.S2TransactionNumber AS ID INTO @TableTransactionNumbers(ID)
-- save generated unique numbers into a table variable first
SELECT TOP(@ParamCount) n1.Number
FROM dbo.Numbers AS n1 CROSS JOIN dbo.Numbers AS n2
OPTION (MAXDOP 1);
END;
/*
-- this method can be used if the SequenceS2TransactionNumber
-- had only one identity column
MERGE INTO dbo.SequenceS2TransactionNumber
USING
(
SELECT *
FROM dbo.Numbers
WHERE dbo.Numbers.Number <= @ParamCount
) AS T
ON 1 = 0
WHEN NOT MATCHED THEN
INSERT DEFAULT VALUES
OUTPUT inserted.S2TransactionNumber
-- return generated unique numbers directly to the caller
;
*/
-- Rollback to a named savepoint or named transaction
ROLLBACK TRANSACTION ProcedureGetNewS2TransactionNos;
IF object_id('tempdb..#NewS2TransactionNumbers') IS NOT NULL
BEGIN
INSERT INTO #NewS2TransactionNumbers (ID)
SELECT TT.ID FROM @TableTransactionNumbers AS TT;
END
END
-- Generate a batch of new unique transaction numbers
-- and store them in #NewS2TransactionNumbers
DECLARE @VarTransactionCount int;
SET @VarTransactionCount = ...
CREATE TABLE #NewS2TransactionNumbers(ID int NOT NULL);
EXEC dbo.GetNewS2TransactionNumbers @ParamCount = @VarTransactionCount;
-- use the generated numbers...
SELECT ID FROM #NewS2TransactionNumbers AS TT;
INSERT INTO @TableTransactions (ID)
EXEC dbo.GetNewS2TransactionNumbers @ParamCount = @VarTransactionCount;
CREATE SEQUENCE Schema.SequenceName
AS int
INCREMENT BY 1 ;
CREATE SEQUENCE [dbo].[SequenceFile]
AS int
START WITH 1
INCREMENT BY 1 ;
SELECT NEXT VALUE FOR [dbo].[SequenceFile]
--it is used like this:
-- use the sequence in either insert or select:
Insert into MyTable Values (NextVal('MySequence'), 'Foo');
SELECT NextVal('MySequence');
--you can make as many sequences as you want, by name:
SELECT NextVal('Mikes Other Sequence');
--or a blank sequence identifier
SELECT NextVal('');
CREATE TABLE SequenceHolder(SeqName varchar(40), LastVal int);
GO
CREATE function NextVAL(@SEQname varchar(40))
returns int
as
begin
declare @lastval int
declare @barcode int;
set @lastval = (SELECT max(LastVal)
FROM SequenceHolder
WHERE SeqName = @SEQname);
if @lastval is null set @lastval = 0
set @barcode = @lastval + 1;
--=========== USE xp_cmdshell TO INSERT AND COMMINT NOW, IN A SEPERATE TRANSACTION =============================
DECLARE @sql varchar(4000)
DECLARE @cmd varchar(4000)
DECLARE @recorded int;
SET @sql = 'INSERT INTO SequenceHolder(SeqName, LastVal) VALUES (''' + @SEQname + ''', ' + CAST(@barcode AS nvarchar(50)) + ') '
SET @cmd = 'SQLCMD -S ' + @@servername +
' -d ' + db_name() + ' -Q "' + @sql + '"'
EXEC master..xp_cmdshell @cmd, 'no_output'
--===============================================================================================================
-- once submitted, make sure our value actually stuck in the table
set @recorded = (SELECT COUNT(*)
FROM SequenceHolder
WHERE SeqName = @SEQname
AND LastVal = @barcode);
--TRIGGER AN ERROR
IF (@recorded != 1)
return cast('Barcode was not recorded in SequenceHolder, xp_cmdshell FAILED!! [' + @cmd +']' as int);
return (@barcode)
end
GO
COMMIT;
--- LOOSEN SECURITY SO THAT xp_cmdshell will run
---- To allow advanced options to be changed.
EXEC sp_configure 'show advanced options', 1
GO
---- To update the currently configured value for advanced options.
RECONFIGURE
GO
---- To enable the feature.
EXEC sp_configure 'xp_cmdshell', 1
GO
---- To update the currently configured value for this feature.
RECONFIGURE
GO
—-Run SQLServer Management Studio as Administrator,
—- Login as domain user, not sqlserver user.
--MAKE A DATABASE USER THAT HAS LOCAL or domain LOGIN! (not SQL server login)
--insure the account HAS PERMISSION TO ACCESS THE DATABASE IN QUESTION. (UserMapping tab in User Properties in SQLServer)
—grant the following
GRANT EXECUTE on xp_cmdshell TO [domain\user]
—- run the following:
EXEC sp_xp_cmdshell_proxy_account 'domain\user', 'pwd'
--alternative to the exec cmd above:
create credential ##xp_cmdshell_proxy_account## with identity = 'domain\user', secret = 'pwd'
-—IF YOU NEED TO REMOVE THE CREDENTIAL USE THIS
EXEC sp_xp_cmdshell_proxy_account NULL;
-—ways to figure out which user is actually running the xp_cmdshell command.
exec xp_cmdshell 'whoami.exe'
EXEC xp_cmdshell 'osql -E -Q"select suser_sname()"'
EXEC xp_cmdshell 'osql -E -Q"select * from sys.login_token"'