2010-02-05 2 views
11

Angenommen, die Tabelle mit zwei Spalten:SQL Server eindeutigen Autoinkrement-Spalte in Zusammenhang mit einer anderen Spalte

ParentEntityId int foreign key 
Number int 

ParentEntityId ein Fremdschlüssel zu einer anderen Tabelle ist.

Number ist eine lokale Identität, d. H. Sie ist einzigartig innerhalb einzelner ParentEntityId.

Eindeutigkeit wird einfach über einen eindeutigen Schlüssel über diese beiden Spalten erreicht.

Wie wird Number automatisch im Zusammenhang mit dem ParentEntityId auf Einfügen erhöht?


Addendum 1

das Problem zu klären, hier ist eine Zusammenfassung.

ParentEntity hat mehrere ChildEntity und jede ChiildEntity sollte einen einzigartigen inkrementalen Number im Rahmen seiner ParentEntity haben.


Addendum 2

Treat ParentEntity als Kunden.

Behandeln ChildEntity als Bestellung.

Also, Bestellungen für jeden Kunden sollte nummeriert 1, 2, 3 und so weiter.

+0

Bitte klären ‚im Zusammenhang mit der es übergeordnete Entität‘ in SQL-Ausdrücke ... Der einzige Zusammenhang ist, dass ein Datensatz der Tabelle befindet sich in Beziehungen über FK Einschränkungen erreicht. und du hast schon eins, wenn tatsächlich 1: M das ist, wonach du bist. Was ist der Zweck der 'kontextsensitiven' Nummer? –

+0

@Sky: bitte, schauen Sie sich Addendum 2 an. Hoffe es verdeutlicht die Absicht. –

Antwort

9

Nun, es gibt keine native Unterstützung für diese Art der Spalte, aber man könnte es mit einem Trigger implementieren:

CREATE TRIGGER tr_MyTable_Number 
ON MyTable 
INSTEAD OF INSERT 
AS 

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

BEGIN TRAN; 

WITH MaxNumbers_CTE AS 
(
    SELECT ParentEntityID, MAX(Number) AS Number 
    FROM MyTable 
    WHERE ParentEntityID IN (SELECT ParentEntityID FROM inserted) 
) 
INSERT MyTable (ParentEntityID, Number) 
    SELECT 
     i.ParentEntityID, 
     ROW_NUMBER() OVER 
     (
      PARTITION BY i.ParentEntityID 
      ORDER BY (SELECT 1) 
     ) + ISNULL(m.Number, 0) AS Number 
    FROM inserted i 
    LEFT JOIN MaxNumbers_CTE m 
     ON m.ParentEntityID = i.ParentEntityID 

COMMIT 

Nicht getestet, aber ich bin mir ziemlich sicher, dass es funktionieren wird. Wenn Sie einen Primärschlüssel haben, können Sie dies auch als AFTER Trigger implementieren (ich mag keine INSTEAD OF Trigger, sie sind schwerer zu verstehen, wenn Sie sie 6 Monate später ändern müssen).

Nur um zu erklären, was hier los:

  • SERIALIZABLE ist der strengste Isolationsmodus; Es garantiert, dass immer nur eine Datenbanktransaktion diese Anweisungen ausführen kann, die wir benötigen, um die Integrität dieser "Sequenz" zu gewährleisten. Beachten Sie, dass dadurch die gesamte Transaktion irreversibel gefördert wird. Daher sollten Sie dies nicht in einer lang laufenden Transaktion verwenden.

  • Der CTE nimmt die höchste Nummer auf, die bereits für jede Eltern-ID verwendet wurde;

  • ROW_NUMBER generiert eine eindeutige Sequenz für jede Eltern-ID (PARTITION BY), beginnend mit der Nummer 1; Wir fügen dieses zum vorherigen Maximum hinzu, wenn es einen gibt, um die neue Sequenz zu erhalten.

ich sollte wohl auch erwähnen, dass, wenn Sie jemals in einer Zeit, ein neues Kind Einheit eingelegt werden muss, sind Sie nur besser diese Vorgänge durch eine gespeicherte Prozedur schleusen stattdessen einen Trigger verwenden - werden Sie auf jeden Fall bessere Leistung daraus. So wird es derzeit mit hierarchyid Spalten in SQL '08 gemacht.

+0

@Aaronaught: ja, ich bin goning, um einen Primärschlüssel der Identität zu haben. Wie würde dieser Code im Fall des 'AFTER' Triggers vereinfacht? Um ehrlich zu sein, ich verstehe deinen "INSTEAD OF" Triggercode nicht vollständig. –

+0

@Anton: Ich würde nicht sagen, dass es ein 'AFTER'-Trigger ist, der vereinfacht, genau - es ändert nur das' INSERT' in ein 'UPDATE' mit einem' JOIN'. Aber "INSTEAD OF" -Trigger haben verschiedene lästige Einschränkungen, d. H. Sie können nur einen pro Tabelle haben, und es sieht immer komisch aus, dass ich die "eingefügten" Daten wieder einfüge. Aber so funktioniert es. Ich hoffe, dass die Anmerkungen, die ich hinzugefügt habe, Ihnen helfen, es besser zu verstehen. – Aaronaught

+0

Danke, @Aaronaught. Ich werde den Trigger verwenden, wie Sie es vorgeschlagen haben. Akzeptieren. –

0

Dies löst die Frage, wie ich es verstehe: :-)

DECLARE @foreignKey int 
SET @foreignKey = 1 -- or however you get this 

INSERT Tbl (ParentEntityId, Number) 
VALUES (@foreignKey, ISNULL((SELECT MAX(Number) FROM Tbl WHERE ParentEntityId = @foreignKey), 0) + 1) 
+0

Gewährleistet dieser Ansatz eine Fadensicherheit? Ich meine, werden nicht zwei Datensätze mit dem gleichen 'Number'-Wert angezeigt? Wahrscheinlich sollte es in einem Trigger getan werden ... –

+0

@roufamatic: Anton fragt, ob es möglich ist, die Zahl ** automatisch ** zu erhöhen. Der Aufruf von 'select MAX (...)' sieht nicht wie eine automatische Nummerngenerierung aus, je nachdem, wie ich die Frage – Sung

+0

@Anton verstanden habe - wir können dies mit einem Applock für Thread-Sicherheit umgeben. @Sung Meister - Ich stimme der Definition von "automatisch" nicht zu. :-) Dies aktualisiert automatisch das Zahlenfeld beim Aufruf. – roufamatic

2

Benötigen Sie OUTPUT Klausel, um für Linq zu SQL-Kompatibilität auszulösen.

Zum Beispiel:

INSERT MyTable (ParentEntityID, Number) 
OUTPUT inserted.* 
SELECT 
    i.ParentEntityID, 
    ROW_NUMBER() OVER 
    (
    PARTITION BY i.ParentEntityID 
    ORDER BY (SELECT 1) 
) + ISNULL(m.Number, 0) AS Number 
FROM inserted i 
LEFT JOIN MaxNumbers_CTE m 
ON m.ParentEntityID = i.ParentEntityID 
Verwandte Themen