2010-02-05 26 views
11

Załóżmy tabeli z dwoma kolumnami:SQL Server kolumna unikatowy automatycznego przyrostu w kontekście innej kolumnie

ParentEntityId int foreign key 
Number int 

ParentEntityId jest klucz obcy do innej tabeli.

Number to tożsamość lokalna lokalna, tj. Jest unikalna w obrębie pojedynczego ParentEntityId.

Wyjątkowość można łatwo osiągnąć za pomocą unikalnego klucza w tych dwóch kolumnach.

Jak automatycznie dokonać inkrementacji Number w kontekście wkładki ParentEntityId?


Uzupełnienie 1

Aby wyjaśnić ten problem, tu jest abstrakcyjne.

ParentEntity ma wielokrotność ChildEntity, a każdy ChiildEntity powinien posiadać unikalny przyrostową Number w kontekście jego ParentEntity.


Uzupełnienie 2

Treat ParentEntity jako Klienta.

Treat ChildEntity jako Zakonu.

Zamówienia dla każdego klienta powinny być ponumerowane 1, 2, 3 i tak dalej.

+0

Proszę wyjaśnić „w kontekście swojego jednostka dominująca” w kategoriach SQL ... Jedyny kontekst jest to, że z tabeli rekord rezyduje w stosunkach uzyskuje się poprzez ograniczenia FK. i już ją masz, jeśli w rzeczywistości 1: M jest tym, czego szukasz. Jaki jest cel numeru "kontekstowego"? –

+0

@Sky: proszę spojrzeć na Addendum 2. Mam nadzieję, że wyjaśniono zamiar. –

Odpowiedz

9

Cóż, nie ma natywne wsparcie dla tego typu kolumny, ale można go realizować przy użyciu wyzwalacza:

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 

Nie testowałem, ale jestem pewien, że to zadziała. Jeśli masz klucz podstawowy, możesz również zastosować go jako wyzwalacz AFTER (nie podoba mi się użycie wyzwalaczy INSTEAD OF, trudniej jest zrozumieć, kiedy trzeba je zmodyfikować 6 miesięcy później).

Wystarczy, aby wyjaśnić, co się dzieje tutaj:

  • SERIALIZABLE jest najsurowsze Tryb izolacji; gwarantuje to, że tylko jedna transakcja bazy danych na raz może wykonywać te instrukcje, które są nam potrzebne w celu zagwarantowania integralności tej "sekwencji". Zwróć uwagę, że to nieodwracalnie promuje całą transakcję, więc nie będziesz chciał jej używać w ramach długotrwałej transakcji.

  • CTE odbiera najwyższy numer już wykorzystany dla każdego identyfikatora nadrzędnego;

  • ROW_NUMBER generuje unikalną sekwencję dla każdego identyfikatora nadrzędnego (PARTITION BY) rozpoczynając od cyfry 1; dodajemy to do poprzedniego maksimum, jeśli istnieje taki, aby uzyskać nową sekwencję.

pewnie powinien również wspomnieć, że jeśli tylko kiedykolwiek trzeba wstawić jeden nowy podmiot dzieci na raz, jesteś lepiej tylko skupiania te operacje za pośrednictwem procedury przechowywanej, zamiast przy użyciu wyzwalacza - będziesz zdecydowanie lepsze wyniki z tego. Tak właśnie jest obecnie wykonywane z kolumnami hierarchyid w SQL '08.

+0

@Aaronaught: tak, zamierzam mieć klucz główny tożsamości. W jaki sposób można by uprościć ten kod w przypadku wyzwalacza "AFTER"? Szczerze mówiąc, nie w pełni rozumiem twój kod aktywacyjny "INSTEAD OF". –

+0

@Anton: Nie powiedziałbym, że uczynienie tego przełącznika 'AFTER' upraszcza, dokładnie - po prostu zmienia' INSERT' na 'UPDATE' z' JOIN'. Ale wyzwalacze 'INSTEAD OF' mają różne denerwujące ograniczenia, tj. Możesz mieć tylko jeden na tabelę i zawsze wygląda dziwnie na to, że ponownie wstawiam dane wstawione. Ale tak to działa. Mam nadzieję, że dodane adnotacje pomogą ci lepiej to zrozumieć. – Aaronaught

+0

Dziękuję, @Aaronaught. Użyję spustu, tak jak sugerowałeś. Akceptacja. –

0

To rozwiązuje kwestię jak rozumiem: :-)

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

Czy to podejście gwarantuje bezpieczeństwo wątku? Chodzi mi o to, czy nie pojawią się dwa zapisy z tą samą wartością "Liczba"? Prawdopodobnie powinno się to odbywać w trybie wyzwalającym ... –

+0

@roufamatic: Anton pyta, czy możliwe jest zwiększenie liczby ** automatycznie **. Wywołanie 'select MAX (...)' nie wygląda jak automatyczne generowanie numeru zgodnie z tym, jak rozumiem pytanie: – Sung

+0

@Anton - możemy otoczyć to apletem dla bezpieczeństwa wątków. @Sung Meister - Zgadzam się nie zgodzić się na definicję "automatycznego". :-) To automatycznie aktualizuje pole Numer po wywołaniu. – roufamatic

2

Musisz dodać klauzulę o nazwie OUTPUT, aby wywołać zgodność Linq z SQL.

Na przykład:

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 
Powiązane problemy