web-dev-qa-db-fra.com

Comment implémenteriez-vous des séquences dans Microsoft SQL Server?

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.

32
Nathan Lee

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/

49
sqljunkieshare

Une colonne Identity est à peu près analogue à une séquence.

5
matt b

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. 

5
Corey Trager

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;
}
4
George Siggouroglou

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)
3
James Cane

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é? 

3
user36804

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
2
Aleksey Bykov

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.

2
Paul Klotka

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 ;
  1. Dans l'explorateur Object, développez le dossier Programmability.
  2. Sous le dossier Programmability, cliquez avec le bouton droit sur le dossier Sequences Comme indiqué ci-dessous:

 enter image description here

  1. Les valeurs que vous voudriez mettre à jour pour obtenir l’équivalent De l’instruction SQL ci-dessus sont soulignées. Toutefois, j’envisagerais De les modifier en fonction de vos besoins (voir les notes ci-dessous).

 enter image description here

Remarques:

2
Tony L.

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.

0
John MacIntyre

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.

0

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"'
0
mike

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:

  1. Effectuer l'insertion via une procédure stockée qui renvoie la valeur de clé nouvellement insérée
  2. Implémentez la séquence côté client (pour que vous connaissiez la nouvelle clé avant de l'insérer)

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.

0
MusiGenesis

Si vous utilisez SQL Server 2005, vous avez la possibilité d'utiliser Row_Number.

0
bishop

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]
0
daniele3004