Est-ce que quelqu'un a un bon moyen d'implémenter quelque chose comme une séquence dans SQL Server?
Parfois, vous ne voulez tout simplement pas utiliser un GUID, mis à part le fait qu'ils sont laids. Peut-être que la séquence que vous voulez n'est pas numérique? En outre, insérer une ligne et demander ensuite à la DB quel est le numéro semble tellement bidon.
Sql Server 2012 a introduit SEQUENCE
objets , qui vous permettent de générer des valeurs numériques séquentielles non associées à une table.
Leur création est facile:
CREATE SEQUENCE Schema.SequenceName
AS int
INCREMENT BY 1 ;
Un exemple d'utilisation avant l'insertion:
DECLARE @NextID int ;
SET @NextID = NEXT VALUE FOR Schema.SequenceName;
-- Some work happens
INSERT Schema.Orders (OrderID, Name, Qty)
VALUES (@NextID, 'Rim', 2) ;
Voir mon blog pour un regard en profondeur sur l'utilisation des séquences:
http://sqljunkieshare.com/2011/12/11/sequences-in-sql-server-2012-implementingmanaging-performance/
Une colonne Identity est à peu près analogue à une séquence.
Vous pouvez simplement utiliser de vieux tableaux simples et les utiliser comme séquences. Cela signifie que vos inserts seraient toujours:
BEGIN TRANSACTION
SELECT number from plain old table..
UPDATE plain old table, set the number to be the next number
INSERT your row
COMMIT
Mais ne fais pas ça. Le verrouillage serait mauvais ...
J'ai commencé sur SQL Server et pour moi, le schéma de "séquence" Oracle ressemblait à un hack. Je suppose que vous venez de l’autre direction et que vous vous rendez compte, et que scope_identity () ressemble à un hack.
Passer à autre chose. A Rome, fais comme les Romains.
La façon dont j'ai utilisé pour résoudre ce problème était un tableau 'Séquences' qui stockait toutes mes séquences et une procédure stockée 'nextval'.
Table SQL:
CREATE TABLE Sequences (
name VARCHAR(30) NOT NULL,
value BIGINT DEFAULT 0 NOT NULL,
CONSTRAINT PK_Sequences PRIMARY KEY (name)
);
Le PK_Sequences sert uniquement à s’assurer qu’il n’y aura jamais de séquences portant le même nom.
Procédure stockée 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;
Insérer des séquences:
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);
Enfin, obtenez la valeur suivante d'une séquence,
execute nextval 'SEQ_Participant';
Un code c # pour obtenir la valeur suivante de la table Sequence,
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;
}
Dans SQL Server 2012, vous pouvez simplement utiliser
CREATE SEQUENCE
En 2005 et 2008, vous pouvez obtenir une liste arbitraire de nombres séquentiels à l'aide d'une expression de table commune.
Voici un exemple (notez que l'option MAXRECURSION est importante):
DECLARE @MinValue INT = 1;
DECLARE @MaxValue INT = 1000;
WITH IndexMaker (IndexNumber) AS
(
SELECT
@MinValue AS IndexNumber
UNION ALL SELECT
IndexNumber + 1
FROM
IndexMaker
WHERE IndexNumber < @MaxValue
)
SELECT
IndexNumber
FROM
IndexMaker
ORDER BY
IndexNumber
OPTION
(MAXRECURSION 0)
Les séquences implémentées par Oracle nécessitent un appel à la base de données avant que les identités insérées . Implémentées par SQL Server nécessitent un appel à la base de données après l'insertion.
L'un n'est pas plus bidon que l'autre. L'effet net est le même: une dépendance sur le magasin de données pour fournir des valeurs de clé artificielles uniques et (dans la plupart des cas) deux appels au magasin.
Je suppose que votre modèle relationnel est basé sur des clés artificielles et, dans ce contexte, je ferai l'observation suivante:
Nous ne devrions jamais chercher à donner du sens à des clés artificielles; leur seul but devrait être de lier des enregistrements liés.
Quel est votre besoin lié aux données de commande? Peut-il être manipulé dans la vue (présentation) ou est-ce un attribut réel de vos données qui doit être conservé?
Considérez l'extrait suivant.
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
Créez une table de scène avec un identifiant.
Avant de charger la table d'étape, tronquez et réamorcez l'identificateur pour qu'il commence à 1.
Chargez votre table. Chaque ligne a maintenant une valeur unique de 1 à N.
Créez une table contenant des numéros de séquence. Cela pourrait être plusieurs lignes, une pour chaque séquence.
Recherchez le numéro de séquence dans la table de séquence que vous avez créée ..__ Mettez à jour le numéro de séquence en ajoutant le nombre de lignes de la table d'étape au numéro de séquence.
Mettez à jour l'identifiant de la table d'étape en ajoutant le numéro de séquence que vous avez recherché. Il s’agit d’un processus simple en une étape ..__ ou .__ Chargez votre table cible, ajoutez le numéro de séquence à l’identificateur lors du chargement dans ETL. Cela peut tirer parti du chargeur en bloc et permettre d'autres transformations.
Comme sqljunkiesshare indique , des séquences ont été ajoutées à SQL Server 2012. Voici comment procéder dans l'interface graphique. C'est l'équivalent de:
CREATE SEQUENCE Schema.SequenceName
AS int
INCREMENT BY 1 ;
Remarques:
La valeur initiale, la valeur minimale et la valeur maximale par défaut ont été déterminées par la plage du type de données, qui était un int dans ce cas. Voir ici pour plus de plages de types de données si vous voulez utiliser autre chose qu'un int .
Il est fort probable que vous souhaitiez que votre séquence commence à 1 et que vous voudriez également que votre valeur minimale soit égale à 1.
Je suis totalement d'accord et je l'ai fait l'année dernière sur un projet.
Je viens de créer une table avec le nom de la séquence, la valeur actuelle et le montant incrémenté.
Ensuite, j'ai créé 2 procs pour les ajouter et les supprimer. Et 2 fonctions pour passer à la suite, et obtenir le courant.
L’autre problème des colonnes d’identité est que, si vous avez plusieurs tables dont les numéros de séquence doivent être uniques, une colonne d’identité ne fonctionne pas. Et, comme le mentionne Corey Trager, une implémentation de séquence de type personnalisable peut présenter des problèmes de verrouillage.
La solution la plus simple semble être de créer une table SQL Server avec une seule colonne pour l'identité, qui remplace un type d'objet "séquence" séparé. Par exemple, si dans Oracle, vous avez deux tables d’une même séquence, telles que Dogs <- objet de séquence -> Chats, puis dans SQL Server, vous créez trois objets de base de données, toutes les tables telles que Dogs <- Animaux de compagnie avec colonne d’identité -> Chats. Vous insérez une ligne dans la table Pets pour obtenir le numéro de séquence où vous utiliseriez normalement NEXTVAL, puis vous l'insérez dans la table Chiens ou Chats comme vous le feriez normalement une fois que l'utilisateur obtenait le type réel d'animal. Toute colonne commune supplémentaire peut être déplacée des tables Chiens/Chats vers la table de sur-type Pets, avec les conséquences suivantes: 1) il y aurait une ligne pour chaque numéro de séquence, 2) toute colonne ne pouvant pas être renseignée lors de l'obtention du numéro de séquence 3) il faudrait une jointure pour obtenir toutes les colonnes.
TRANSACTION SAFE! Pour les versions de SQLServer antérieures à 2012 ... (merci Matt G.) Une chose qui manque dans cette discussion est la sécurité des transactions. Si vous obtenez un numéro d'une séquence, ce numéro doit être unique et aucune autre application ou code ne devrait pouvoir obtenir ce numéro. Dans mon cas, nous extrayons souvent des nombres uniques à partir de séquences, mais la transaction réelle peut durer très longtemps. Nous ne voulons donc pas que quiconque reçoive le même numéro avant de valider la transaction .Nous avions besoin de reproduire le comportement des séquences Oracle, où un numéro était réservé lorsqu’il a été extrait ..__ Ma solution consiste à utiliser xp_cmdshell pour obtenir une session/transaction séparée sur la base de données, afin que nous puissions immédiatement mettre à jour la séquence pour toute la base de données, même avant la fin de la transaction.
--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('');
La solution nécessite une table unique pour contenir les valeurs de séquence utilisées et une procédure créant une seconde transaction autonome} [ pour garantir que les sessions simultanées ne sont pas emmêlées. Vous pouvez avoir autant de séquences uniques que vous le souhaitez, elles sont référencées par nom. L'exemple de code ci-dessous est modifié pour omettre l'utilisateur demandeur et l'horodatage sur la table d'historique des séquences (pour l'audit), mais je pensais que moins complexe était préférable pour l'exemple ;-).
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;
Maintenant, pour que cette procédure fonctionne, vous devez activer xp_cmdshell, il y a beaucoup de bonnes descriptions sur la façon de procéder. Voici mes notes personnelles que j'ai prises lorsque j'essayais de faire fonctionner les choses. L'idée de base est que vous devez activer xp_cmdshell dans SQLServer Surface. Il est nécessaire de définir un compte d'utilisateur en tant que compte sous lequel la commande xp_cmdshell s'exécutera. Celui-ci accédera à la base de données pour insérer le numéro de séquence et le valider.
--- 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"'
Si vous souhaitez insérer des données avec une clé séquentielle, mais que vous ne voulez plus avoir à interroger la base de données pour obtenir la clé que vous venez d'insérer, je pense que vos deux seuls choix sont les suivants:
Si je crée des clés côté client, je aime GUID. Je pense qu'ils sont beaux comme diable.
row["ID"] = Guid.NewGuid();
Cette ligne devrait être posée quelque part sur le capot d’une voiture de sport.
Si vous utilisez SQL Server 2005, vous avez la possibilité d'utiliser Row_Number.
En SQL, vous pouvez utiliser cette stratégie.
CREATE SEQUENCE [dbo].[SequenceFile]
AS int
START WITH 1
INCREMENT BY 1 ;
et lire l'unique valeur suivante avec ce SQL
SELECT NEXT VALUE FOR [dbo].[SequenceFile]