it-swarm.com.de

Wie würden Sie Sequenzen in Microsoft SQL Server implementieren?

Hat jemand eine gute Möglichkeit, so etwas wie eine Sequenz in SQL Server zu implementieren?

Manchmal möchten Sie einfach keine GUID verwenden, abgesehen davon, dass sie hässlich sind. Vielleicht ist die gewünschte Reihenfolge nicht numerisch? Außerdem erscheint das Einfügen einer Zeile und die anschließende Abfrage der Datenbank nach der Nummer einfach hackig.

32
Nathan Lee

SQL Server 2012 hat SEQUENCE objects eingeführt, mit denen Sie fortlaufende numerische Werte generieren können, die keiner Tabelle zugeordnet sind.

Sie zu erstellen ist einfach:

CREATE SEQUENCE Schema.SequenceName
AS int
INCREMENT BY 1 ;

Ein Beispiel für die Verwendung vor dem Einfügen:

DECLARE @NextID int ;
SET @NextID = NEXT VALUE FOR Schema.SequenceName;
-- Some work happens
INSERT Schema.Orders (OrderID, Name, Qty)
  VALUES (@NextID, 'Rim', 2) ;

In meinem Blog finden Sie einen detaillierten Einblick in die Verwendung von Sequenzen:

http://sqljunkieshare.com/2011/12/11/sequences-in-sql-server-2012-implementingmanaging-performance/

49
sqljunkieshare

Wie sqljunkieshare richtig gesagt, gibt es ab SQL Server 2012 ein eingebautes SEQUENCE Feature.

Die ursprüngliche Frage ist nicht klar, aber ich gehe davon aus, dass die Anforderungen für die Sequenz sind:

  1. Es muss eine Reihe einzigartiger wachsender Zahlen liefern
  2. Wenn mehrere Benutzer gleichzeitig den nächsten Wert der Sequenz anfordern, sollten sie alle unterschiedliche Werte erhalten. Mit anderen Worten, die Eindeutigkeit der generierten Werte ist auf jeden Fall garantiert.
  3. Aufgrund der Möglichkeit, dass einige Transaktionen zurückgesetzt werden können, ist es möglich, dass das Endergebnis der generierten Zahlen Lücken aufweist.

Ich möchte die Aussage in der ursprünglichen Frage kommentieren:

"Außerdem eine Zeile einfügen und dann die DB fragen, was die Nummer gerade so hackisch scheint."

Hier können wir nicht viel dagegen tun. Die Datenbank ist ein Anbieter der fortlaufenden Nummern, und die Datenbank behandelt all diese Parallelitätsprobleme, die Sie nicht selbst behandeln können. Ich sehe keine Alternative, um den DB nach dem nächsten Wert der Sequenz zu fragen. Es muss eine atomare Operation geben, "gib mir den nächsten Wert der Sequenz", und nur DB kann eine solche liefern atomare Operation. Kein Client-Code kann garantieren, dass er der einzige ist, der mit der Sequenz arbeitet.

Zur Beantwortung der Frage im Titel "Wie würden Sie Sequenzen implementieren?" - Wir verwenden 2008, das nicht über die Funktion SEQUENCE verfügt. Nach einigem Lesen zu diesem Thema kam ich zu folgendem Ergebnis.

Für jede Sequenz, die ich benötige, erstelle ich eine separate Hilfstabelle mit nur einer IDENTITY -Spalte (auf dieselbe Weise wie in 2012 würden Sie ein separates Sequenzobjekt erstellen).

CREATE TABLE [dbo].[SequenceContractNumber]
(
    [ContractNumber] [int] IDENTITY(1,1) NOT NULL,

    CONSTRAINT [PK_SequenceContractNumber] PRIMARY KEY CLUSTERED ([ContractNumber] ASC)
)

Sie können den Startwert und das Inkrement dafür angeben. Dann erstelle ich eine gespeicherte Prozedur, die den nächsten Wert der Sequenz zurückgibt. Die Prozedur startet eine Transaktion, fügt eine Zeile in die Hilfstabelle ein, merkt sich den generierten Identitätswert und setzt die Transaktion zurück. Somit bleibt der Helfertisch immer leer.

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

Einige Anmerkungen zum Verfahren.

Erstens war es nicht offensichtlich, wie eine Zeile in eine Tabelle mit nur einer Identitätsspalte eingefügt werden sollte. Die Antwort lautet DEFAULT VALUES.

Dann wollte ich, dass die Prozedur korrekt funktioniert, wenn sie in einer anderen Transaktion aufgerufen wurde. Das einfache ROLLBACK setzt alles zurück, wenn geschachtelte Transaktionen vorhanden sind. In meinem Fall muss ich nur INSERT in die Hilfstabelle zurücksetzen, also habe ich SAVE TRANSACTION verwendet.

ROLLBACK TRANSACTION ohne einen Sicherungspunktnamen oder einen Transaktionsnamen führt ein Rollback zum Beginn der Transaktion durch. Beim Verschachteln von Transaktionen setzt dieselbe Anweisung alle inneren Transaktionen auf die äußerste BEGIN TRANSACTION-Anweisung zurück.

So benutze ich die Prozedur (innerhalb einer anderen großen Prozedur, die zum Beispiel einen neuen Vertrag erstellt):

DECLARE @VarContractNumber int;
EXEC @VarContractNumber = dbo.GetNewContractNumber;

Alles funktioniert einwandfrei, wenn Sie nacheinander Sequenzwerte generieren müssen. Bei Verträgen wird jeder Vertrag einzeln erstellt, sodass dieser Ansatz perfekt funktioniert. Ich kann sicher sein, dass alle Verträge immer eindeutige Vertragsnummern haben.

NB: Nur um mögliche Fragen zu vermeiden. Diese Vertragsnummern sind zusätzlich zu dem Ersatz-Identitätsschlüssel, den meine Vertragstabelle hat. Der Ersatzschlüssel ist ein interner Schlüssel, der für die referenzielle Integrität verwendet wird. Die generierte Vertragsnummer ist eine benutzerfreundliche Nummer, die auf dem Vertrag abgedruckt ist. Außerdem enthält dieselbe Tabelle Verträge sowohl endgültige Verträge als auch Vorschläge, die Verträge werden oder für immer als Vorschläge bleiben können. Sowohl die Angebote als auch die Verträge enthalten sehr ähnliche Daten. Deshalb werden sie in derselben Tabelle gespeichert. Das Angebot kann durch einfaches Ändern der Flagge in einer Reihe zu einem Vertrag werden. Vorschläge werden mit einer separaten Folge von Nummern nummeriert, für die ich eine zweite Tabelle SequenceProposalNumber und eine zweite Prozedur GetNewProposalNumber habe.


Kürzlich bin ich jedoch auf ein Problem gestoßen. Ich musste Sequenzwerte in einem Stapel und nicht einzeln generieren.

Ich brauche ein Verfahren, das alle Zahlungen, die in einem bestimmten Quartal eingegangen sind, auf einmal verarbeitet. Das Ergebnis einer solchen Verarbeitung könnten ~ 20.000 Transaktionen sein, die ich in der Tabelle Transactions aufzeichnen möchte. Ich habe hier ein ähnliches Design. Die Tabelle Transactions verfügt über eine interne Spalte IDENTITY, die der Endbenutzer niemals sieht, und sie verfügt über eine benutzerfreundliche Transaktionsnummer, die auf der Anweisung gedruckt wird. Ich brauche also eine Möglichkeit, eine bestimmte Anzahl eindeutiger Werte in einem Stapel zu generieren.

Im Wesentlichen habe ich den gleichen Ansatz gewählt, aber es gibt nur wenige Besonderheiten.

Erstens gibt es keine direkte Möglichkeit, mehrere Zeilen in eine Tabelle mit nur einer Spalte IDENTITY einzufügen. Obwohl es eine Problemumgehung durch (ab) mit MERGE gibt, habe ich sie am Ende nicht verwendet. Ich entschied, dass es einfacher ist, eine Dummy-Spalte Filler hinzuzufügen. Meine Sequenztabelle wird immer leer sein, sodass zusätzliche Spalten keine Rolle spielen.

Die Hilfstabelle sieht folgendermaßen aus:

CREATE TABLE [dbo].[SequenceS2TransactionNumber]
(
    [S2TransactionNumber] [int] IDENTITY(1,1) NOT NULL,
    [Filler] [int] NULL,
    CONSTRAINT [PK_SequenceS2TransactionNumber] 
    PRIMARY KEY CLUSTERED ([S2TransactionNumber] ASC)
)

Die Prozedur sieht folgendermaßen aus:

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

Und so wird es verwendet (innerhalb einer großen gespeicherten Prozedur, die Transaktionen berechnet):

-- 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;

Es gibt einige Dinge, die einer Erklärung bedürfen.

Ich muss eine bestimmte Anzahl von Zeilen in die Tabelle SequenceS2TransactionNumber Einfügen. Ich benutze dafür eine Hilfstabelle Numbers. Diese Tabelle enthält einfach Ganzzahlen von 1 bis 100.000. Es wird auch an anderen Stellen im System verwendet. Ich überprüfe, ob die Tabelle Numbers genügend Zeilen enthält, und erweitere sie auf 100.000 * 100.000, indem ich sie bei Bedarf über Kreuz mit sich selbst verbinde.

Ich muss das Ergebnis der Masseneinfügung irgendwo speichern und es irgendwie an den Anrufer weitergeben. Eine Möglichkeit, eine Tabelle außerhalb der gespeicherten Prozedur zu übergeben, besteht darin, eine temporäre Tabelle zu verwenden. Ich kann hier keinen tabellenwertigen Parameter verwenden, da dieser leider schreibgeschützt ist. Außerdem kann ich die generierten Sequenzwerte nicht direkt in die temporäre Tabelle #NewS2TransactionNumbers Einfügen. Ich kann #NewS2TransactionNumbers Nicht in der OUTPUT -Klausel verwenden, da ROLLBACK es aufräumen wird. Glücklicherweise sind die Tabellenvariablen vom ROLLBACK nicht betroffen.

Daher verwende ich die Tabellenvariable @TableTransactionNumbers Als Ziel der OUTPUT -Klausel. Dann ROLLBACK ich die Transaktion zum Aufräumen der Sequenztabelle. Kopieren Sie dann die generierten Sequenzwerte aus der Tabellenvariablen @TableTransactionNumbers In die temporäre Tabelle #NewS2TransactionNumbers, Da nur die temporäre Tabelle #NewS2TransactionNumbers Für den Aufrufer der gespeicherten Prozedur sichtbar sein kann. Die Tabellenvariable @TableTransactionNumbers Ist für den Aufrufer der gespeicherten Prozedur nicht sichtbar.

Sie können auch die OUTPUT -Klausel verwenden, um die generierte Sequenz direkt an den Aufrufer zu senden (wie Sie in der kommentierten Variante sehen können, die MERGE verwendet). Es funktioniert von selbst, aber ich brauchte die generierten Werte in einer Tabelle für die weitere Verarbeitung in der aufrufenden gespeicherten Prozedur. Als ich so etwas ausprobiert habe:

INSERT INTO @TableTransactions (ID)
EXEC dbo.GetNewS2TransactionNumbers @ParamCount = @VarTransactionCount;

Ich habe einen Fehler bekommen

Die ROLLBACK-Anweisung kann nicht in einer INSERT-EXEC-Anweisung verwendet werden.

Aber ich brauche ROLLBACK im EXEC, deshalb habe ich so viele temporäre Tabellen.

Wie schön wäre es nach all dem, auf die neueste Version von SQL Server zu wechseln, die ein richtiges SEQUENCE -Objekt hat.

14

Eine Identitätsspalte ist ungefähr analog zu einer Sequenz.

5
matt b

Sie können nur einfache alte Tabellen verwenden und sie als Sequenzen verwenden. Das heißt, Ihre Einsätze wären immer: 

BEGIN TRANSACTION  
SELECT number from plain old table..  
UPDATE plain old table, set the number to be the next number  
INSERT your row  
COMMIT  

Aber mach das nicht. Die Verriegelung wäre schlecht ...

Ich habe mit SQL Server angefangen und für mich sah das Oracle-Sequenzschema wie ein Hack aus. Ich denke, Sie kommen aus der anderen Richtung und zu Ihnen, und scope_identity () sieht aus wie ein Hack.

Komm darüber hinweg. Wenn du in Rom bist, mach wie es die Römer tun. 

5
Corey Trager

Die Art und Weise, wie ich dieses Problem gelöst habe, war eine Tabelle 'Sequences', in der alle meine Sequenzen und eine 'nextval' gespeicherte Prozedur gespeichert sind. 

SQL-Tabelle: 

CREATE TABLE Sequences (  
    name VARCHAR(30) NOT NULL,  
    value BIGINT DEFAULT 0 NOT NULL,  
    CONSTRAINT PK_Sequences PRIMARY KEY (name)  
);

Die PK_Sequences wird nur verwendet, um sicherzustellen, dass es niemals Sequenzen mit demselben Namen gibt. 

SQL gespeicherte Prozedur: 

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 [email protected]  
        COMMIT TRANSACTION  
        SELECT @value AS nextval  
    END;  

Fügen Sie einige Sequenzen ein: 

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);  

Holen Sie sich schließlich den nächsten Wert einer Sequenz. 

execute nextval 'SEQ_Participant';

Etwas C # -Code, um den nächsten Wert aus der Sequenztabelle zu erhalten, 

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

In SQL Server 2012 können Sie einfach verwenden 

CREATE SEQUENCE

In 2005 und 2008 können Sie mithilfe eines allgemeinen Tabellenausdrucks eine beliebige Liste von fortlaufenden Nummern erhalten.

Hier ein Beispiel (Beachten Sie, dass die Option MAXRECURSION wichtig ist):

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

Sequenzen, wie sie von Oracle implementiert werden, erfordern einen Aufruf an die Datenbank, bevor die Kennungen für das Einfügen ., Die von SQL Server implementiert werden, nach dem Einfügen einen Aufruf an die Datenbank erfordern.

Einer ist nicht hackiger als der andere. Der Nettoeffekt ist derselbe - eine Zuverlässigkeit/Abhängigkeit vom Datenspeicher, um eindeutige künstliche Schlüsselwerte und (in den meisten Fällen) zwei Aufrufe des Speichers bereitzustellen.

Ich gehe davon aus, dass Ihr relationales Modell auf künstlichen Schlüsseln basiert, und in diesem Zusammenhang möchte ich folgende Beobachtung machen:

Wir sollten niemals versuchen, künstlichen Schlüsseln Bedeutung zu verleihen. Ihr einziger Zweck sollte darin bestehen, verwandte Datensätze zu verknüpfen. 

Was brauchen Sie für die Bestelldaten? Kann es in der Ansicht (Darstellung) behandelt werden oder handelt es sich um ein wahres Attribut Ihrer Daten, das beibehalten werden muss? 

3
user36804

Betrachten Sie den folgenden Ausschnitt.

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

Erstellen Sie eine Bühnentabelle mit einem Bezeichner. 

Vor dem Laden der Stufentabelle muss der Bezeichner gekürzt und erneut gesetzt werden, um bei 1 zu beginnen.

Laden Sie Ihren Tisch. Jede Zeile hat jetzt einen eindeutigen Wert von 1 bis N.

Erstellen Sie eine Tabelle, die Sequenznummern enthält. Dies können mehrere Zeilen sein, eine für jede Sequenz.

Suchen Sie die Sequenznummer aus der von Ihnen erstellten Sequenztabelle. Aktualisieren Sie die Sequenznummer, indem Sie die Anzahl der Zeilen in der Stage-Tabelle zur Sequenznummer hinzufügen.

Aktualisieren Sie die Bezeichner der Stufentabellen, indem Sie die fortgeschriebene Folgenummer hinzufügen. Dies ist ein einfacher Prozess in einem Schritt . Oder Laden Sie Ihre Zieltabelle und fügen Sie die Sequenznummer zum Bezeichner hinzu, während Sie in ETL laden. Dies kann den Massenlader nutzen und andere Umwandlungen ermöglichen.

2
Paul Klotka

Wie in sqljunkiesshare angegeben, wurden zu SQL Server 2012 Sequenzen hinzugefügt. So wird es in der GUI gemacht. Dies ist das Äquivolent von:

CREATE SEQUENCE Schema.SequenceName
AS int
INCREMENT BY 1 ;
  1. Erweitern Sie im Object Explorer den Ordner Programmierbarkeit
  2. Klicken Sie unter dem Ordner Programmability mit der rechten Maustaste auf den Ordner Sequences (Siehe unten):

 enter image description here

  1. Unterstrichen sind die Werte, die Sie aktualisieren würden, um das Äquivalent von Der obigen SQL-Anweisung zu erhalten. Ich würde jedoch erwägen, Diese je nach Ihren Anforderungen zu ändern (siehe Anmerkungen unten).

 enter image description here

Anmerkungen:

2
Tony L.

Ich stimme völlig zu und habe dies letztes Jahr an einem Projekt gemacht.

Ich habe gerade eine Tabelle mit dem Namen der Sequenz, dem aktuellen Wert und dem Inkrementbetrag erstellt.

Dann erstellte ich 2 Procs, um sie hinzuzufügen und zu löschen. Und 2 Funktionen, um weiter zu kommen und auf den neuesten Stand zu kommen.

0
John MacIntyre

Das andere Problem bei Identitätsspalten besteht darin, dass eine Identitätsspalte nicht funktioniert, wenn Sie mehr als eine Tabelle haben, deren Sequenznummern eindeutig sein müssen. Und wie bei Corey Trager erwähnt, kann eine Roll-Your-Own-Sequenzimplementierung einige Sperrprobleme aufwerfen.

Die einfachsten gleichwertigen Lösungen scheinen die Erstellung einer SQL Server-Tabelle mit einer einzelnen Spalte für die Identität zu sein, die an die Stelle eines separaten Typs eines "Sequenz" -Objekts tritt. Wenn Sie beispielsweise in Oracle zwei Tabellen aus einer Sequenz haben, z. B. Dogs <- sequence object -> Cats, dann würden Sie in SQL Server drei Datenbankobjekte erstellen, alle Tabellen wie Dogs <- Pets mit Identitätsspalte -> Katzen Sie würden eine Zeile in die Pets-Tabelle einfügen, um die Sequenznummer zu erhalten, in der Sie normalerweise NEXTVAL verwenden würden, und dann in die Dogs- oder Cats-Tabelle einfügen, wie Sie es normalerweise tun würden, wenn Sie den tatsächlichen Haustiertyp vom Benutzer erhalten. Zusätzliche allgemeine Spalten könnten aus den Dogs/Cats-Tabellen in die Pets-Supertyptabelle verschoben werden, mit folgenden Konsequenzen: 1) Für jede Sequenznummer gibt es eine Zeile, 2) Spalten, die beim Abrufen der Sequenznummer nicht gefüllt werden können müssen Standardwerte haben und 3) es wäre ein Join erforderlich, um alle Spalten abzurufen.

0

TRANSACTION SAFE! Für SQLServer-Versionen vor 2012 ... (Danke Matt G.) Eine Sache, die in dieser Diskussion fehlt, ist die Transaktionssicherheit. Wenn Sie eine Nummer aus einer Sequenz erhalten, muss diese Nummer eindeutig sein, und keine andere App oder Code sollte diese Nummer erhalten können. In meinem Fall ziehen wir häufig eindeutige Zahlen aus Sequenzen, aber die tatsächliche Transaktion kann eine beträchtliche Zeitspanne umfassen. Daher möchten wir nicht, dass andere Personen die gleiche Nummer erhalten, bevor wir die Transaktion festlegen .. _.We erforderlich, um das Verhalten von Oracle-Sequenzennachzuahmen, wobei eine Nummer reserviert wurde, als sie abgerufen wurde .. Meine Lösung besteht darin, xp_cmdshell zu verwenden, um eine separate Sitzung/Transaktion in der Datenbank abzurufen, damit wir sie sofort aktualisieren können Sequenz für die gesamte Datenbank, noch bevor die Transaktion abgeschlossen ist.

--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('');

Die Lösung erfordert, dass eine einzige Tabelle die verwendeten Sequenzwerte enthält, und eine Prozedur, die eine zweite autonome Transaktionerstellt, um sicherzustellen, dass gleichzeitige Sitzungen nicht verwickelt werden. Sie können so viele eindeutige Sequenzen haben, wie Sie möchten, sie werden nach Namen referenziert. Der folgende Beispielcode wurde geändert, um die Anforderung von Benutzer- und Datumsstempeln in der Sequenzprotokolltabelle (für das Audit) auszulassen, aber ich dachte, weniger komplex wäre für das Beispiel besser ;-).

  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;

Damit dieses Verfahren funktionieren kann, müssen Sie xp_cmdshell aktivieren. Es gibt viele gute Beschreibungen, wie das geht. Hier sind meine persönlichen Notizen, die ich gemacht habe, als ich versuchte, die Dinge zum Laufen zu bringen. Grundidee ist, dass xp_cmdshell in SQLServer aktiviert sein muss. Surface Sind eine Konfiguration, und Sie müssen ein Benutzerkonto als Konto festlegen, unter dem der Befehl xp_cmdshell ausgeführt wird. Dabei wird auf die Datenbank zugegriffen, um die Sequenznummer einzufügen und zu bestätigen.

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

Wenn Sie Daten mit einem sequentiellen Schlüssel einfügen möchten, die Datenbank jedoch nicht erneut abfragen müssen, um den soeben eingefügten Schlüssel zu erhalten, sind Ihrer Meinung nach Ihre einzigen zwei Möglichkeiten:

  1. Führen Sie das Einfügen durch eine gespeicherte Prozedur durch, die den neu eingefügten Schlüsselwert zurückgibt
  2. Implementieren Sie die Sequenz clientseitig (damit Sie den neuen Schlüssel vor dem Einfügen kennen).

Wenn ich clientseitige Schlüsselerzeugung durchführe, love GUIDs. Ich denke, dass sie so schön sind.

row["ID"] = Guid.NewGuid();

Diese Linie sollte irgendwo auf der Motorhaube eines Sportwagens liegen.

0
MusiGenesis

Wenn Sie SQL Server 2005 verwenden, haben Sie die Möglichkeit, Row_Number zu verwenden

0
bishop

Mit SQL können Sie diese Strategie verwenden.

CREATE SEQUENCE [dbo].[SequenceFile]
AS int
START WITH 1
INCREMENT BY 1 ;

und lesen Sie den eindeutigen nächsten Wert mit dieser SQL

SELECT NEXT VALUE FOR [dbo].[SequenceFile]
0
daniele3004