Fremdschlüssel mit einer Konstante

11

Angenommen, ich habe eine Tabelle A mit zwei Spalten: Eine ist eine ID für ThingAund eine ist eine ID für ThingB. Der Primärschlüssel ist (ThingA, ThingB).

Als nächstes habe ich eine zweite Tabelle, aber diesmal ist sie auf Einträge in Tabellen beschränkt A, die haben ThingB = 3. Der Primärschlüssel ist ThingA, weil ThingBeine Konstante von 3 ist.

Anfangs hatte ich gedacht, ich könnte einfach:

FOREIGN KEY (ThingA, 3) REFERENCES A(ThingA, ThingB)

Aber ich habe gelernt, dass dies nicht der Fall ist, und ich muss eine Spalte erstellen für ThingB:

ThingB INT NOT NULL DEFAULT(3) CHECK(ThingB = 3)

Dann,

FOREIGN KEY (ThingA, ThingB) REFERENCES A (ThingA, ThingB)

Gibt es eine Alternative dazu, für die keine zusätzliche Spalte erforderlich ist, oder die DEFAULT + CHECK? Eine Alternative ist eine persistierte, berechnete Spalte , aber ich hasse diese Idee auch, da es sich im Grunde genommen um einen Cheat handelt und immer noch eine neue Spalte mit physischem Speicher hinzugefügt wird. Obwohl es alleine INTnicht groß sein wird, gibt es mehrere Millionen Zeilen, die es für mehrere Tabellen benötigen, und ich möchte die zusätzlichen Spalten lieber nicht beibehalten.

Hier ist eine Beispiel-DDL, um die Situation zu veranschaulichen:

CREATE TABLE Test1
(
    ThingA INT NOT NULL,
    ThingB INT NOT NULL,
    PRIMARY KEY (ThingA, ThingB)
);

CREATE TABLE Test2
(
    ThingAVal INT NOT NULL,
    ThingBVal INT NOT NULL DEFAULT(3) CHECK(ThingBVal = 3),
    Val INT NOT NULL,
    FOREIGN KEY (ThingAVal, ThingBVal) REFERENCES Test1 (ThingA, ThingB)
);

Und ich habe eine db <> -Fiedel erstellt, die meine (aktuelle) Lösung demonstriert:

Wenn die Antwort "Nein" lautet, werde ich sie akzeptieren, aber ich bin gespannt, ob es noch andere Alternativen gibt.

Der Kommissar
quelle
1
Ich befürchte, dass es in aktuellen Implementierungen von SQL-DBMS keine perfekte Lösung gibt. Aber Sie könnten verwenden TINYINT, wenn die Anzahl der möglichen Werte von ThingBklein ist.
Ypercubeᵀᴹ
@ ypercubeᵀᴹ Ja, es ist ein SMALLINTOn-Prod, ich wünschte nur, es gäbe einen besseren Weg, es zu assoziieren. Ich werde möglicherweise Aganz los , da ich es eigentlich nicht brauche, und mache einfach die referenzielle Integrität über ThingA, sodass ich es entfernen kann ThingB.
Der Kommissar
1
Wenn ich Zeit habe, werde ich später eine Antwort mit meinen Gedanken dazu hinzufügen. Wenn nicht, am Wochenende. Ich hatte vor einigen Jahren eine ähnliche / verwandte Frage. Gibt es DBMS, die einen Fremdschlüssel zulassen, der auf eine Ansicht verweist (und nicht nur Basistabellen)?
Ypercubeᵀᴹ
@ ypercubeᵀᴹ Ich weiß das zu schätzen. Ich kratzte mir hier am Kopf und meine Lösungen werden immer komplizierter.
Der Kommissar
Gibt es einen Grund, warum Sie sich dafür entscheiden, keinen identitätsbasierten Ersatzschlüssel in Verbindung mit einer UNIQUEEinschränkung zu verwenden (ThingA, ThingB)?
John Eisbrener

Antworten:

0

Ich denke, die Kombination aus einem Ersatzschlüssel dbo.Test1und einem nach beiden ausgeführten TriggerINSERT und UPDATEAnweisungen, insbesondere einem INSTEAD OFTrigger, ist hier die Antwort.

Und könnte ungefähr so ​​aussehen ( Geige ):

Schema

create table dbo.Test1 (
    Id int identity primary key,
    ThingA int not null,
    ThingB int not null
);

create table dbo.Test2 (
    Id int identity primary key,
    Test1Id int not null foreign key references Test1 (Id),
    Val int not null
);

Nach dem INSERTAuslösen

create trigger test2ThingBCheck_Insert 
on dbo.Test2  
instead of INSERT 
as  
begin

    insert into dbo.Test2 (Test1Id, Val)
    select
        t.Id
        ,i.Val
    from 
        Test1 t 
    join 
        inserted i 
        on i.Test1Id = t.Id
    where
        t.ThingB = 3;
end;

Nach dem UPDATEAuslösen

create trigger test2ThingBCheck_Update
on dbo.Test2  
instead of Update 
as  
begin
    update
        t2
    set
        Val = i.Val
    from
        dbo.Test2 t2
    join
        inserted i
        on i.Id = t2.Id        
    join
        dbo.Test1 t1
        on t1.Id = i.Test1Id
    where
        t1.ThingB = 3;
end; 
Zuhälter
quelle
0

Wenn Sie den konstanten Wert von ThingB kennen, empfehle ich, die Spalte "ThingB" wegzulassen. Lassen Sie stattdessen die Geschäftslogik den konstanten Wert hinzufügen. Was genau der Wert ist, kann in einer anderen Tabelle oder in einer Einstellung gespeichert werden.

Peter Zilz
quelle
Dann gibt es keine referenzielle Integrität für ThingA
Lennart
0

Sie sagten, Sie wollten einer Reihe von Tabellen, die auf diese Weise wieder mit Test1 verknüpft werden müssen, keine zusätzliche Spalte hinzufügen (z. B. ThingA, 3).

Wie wäre es, wenn Sie TestA eine persistierte berechnete Spalte hinzufügen, die den Wert ThingA anzeigt, wenn ThingB 3 ist, andernfalls null?

Dann verweist Ihr Fremdschlüssel nur auf die neue Spalte, basierend auf ThingA in der Referenzierungstabelle.

alter table Test1 add SpecialThingA as
    (case ThingB when 3 then ThingA else null end) persisted;

und

FOREIGN KEY (ThingA) REFERENCES Test1 (SpecialThingA)

Mit anderen Worten - eine neue Spalte in Test1 anstelle einer neuen Spalte in Test2 (um '3' zu halten) und Test3 und ....

youcantryreachingme
quelle
Hat bei mir auf SQL Server nicht funktioniert, da die Spalte in 'nchar' beibehalten wurde und die Spalte, in der ich versuche, die Beziehung zu erstellen, 'int' ist
juanora
@juanora - Wenn Ihre 'ThingA'-Spalte eine Ganzzahl ist, aktualisieren Sie die case-Anweisung auf: (case ThingB when 3 then cast(ThingA as int) else null end) persisted;- Dies sollte sicherstellen, dass die berechnete Spalte als definiert ist int.
youcantryreachingme
0

Ich denke die Antwort ist nein.

Der Fremdschlüssel muss auf eine EINZIGARTIGE EINSCHRÄNKUNG in der Tabelle verweisen, auf die gemäß Books Online verwiesen wird

Es scheint, als könnte die Lösung ein gefilterter eindeutiger Index sein, aber das zählt nicht als Einschränkung.

Sie können es hier überprüfen:

SELECT 
    i.name
    , i.is_primary_key          -- can be referenced by FK
    , i.is_unique_constraint    -- can be referenced by FK
    , i.is_unique               -- cannot be referenced by FK
FROM sys.indexes AS i 

Ich glaube nicht, dass Sie dies sicher tun können, ohne Daten zu duplizieren - Ihre Lösung scheint am besten zu sein (CHECK-Einschränkung für die Referenzierungstabelle)

Eine indizierte Ansicht basierend auf Test1 mit einem Filter für ThingB = 3 würde wahrscheinlich auch funktionieren, aber Sie würden eine andere Spalte beibehalten.

Normalerweise vermeide ich Trigger, da sie die referenzielle Integrität sowie andere Einschränkungen nicht erzwingen, aber es könnte der richtige Weg sein.

Zikato
quelle