2009-08-05 1 views
4

Kann eine One-To-Constant-Beziehung in Oracle vollständig mit Einschränkungen modelliert werden? Mit anderen Worten, die PARENT-Entität hat IMMER GENAU n-CHILDREN der untergeordneten Entität, und jedes Kind hat nur einen Elternteil.Modellierung einer Beziehung zwischen zwei Konstanten

Betrachten Sie n als eine Datenbankkonstante.

Antwort

5

Dies zu tun, damit es auch dann fehlerfrei und korrekt ist, wenn mehrere Sitzungen Aktualisierungen durchführen, ist nicht einfach. Sie werden sich selbst in Schwierigkeiten bringen, wenn Sie dies mit Triggern versuchen, und die deklarativen Beschränkungen von Oracle sind nicht stark genug, um dies auszudrücken.

es kann getan werden, wie folgt: -

  1. ein Protokoll materialisierte Ansicht erstellen sowohl auf die Eltern und das Kind Tabellen
  2. erstellen eine materialisierte Ansicht verbinden, die sie miteinander verbindet und zählt die Anzahl der Kinder gruppiert von den Eltern. Dies muss REFRESH sein FAST ON COMMIT
  3. eine Einschränkung für die Ansicht beitreten materialisierte Setzen, dass die Anzahl der untergeordneten Datensätze müssen „n“ (Datenbank konstant) gleich

Sie dann eine Reihe von insert/update tun können/lösche Anweisungen. Beim Commit wird die materialisierte Ansicht aktualisiert und wenn die Bedingung nicht erfüllt wird, erhalten Sie an dieser Stelle einen Constraint-Verletzungsfehler.

Ein zusätzlicher Trick besteht darin, nur Zeilen einzufügen, bei denen die Einschränkung in der materialisierten Ansicht fehlschlägt (HAVING count (ChildId) <> 5), so dass Sie keinen Speicherplatz verschwenden.

1

Ich sehe nicht wie. Es ist die alte Frage "Wer kam zuerst, das Huhn oder das Ei?". Wie können Sie die Eltern einschränken, wenn noch keine Kinder hinzugefügt wurden, und wie können Sie Kinder ohne Eltern hinzufügen?

Sie könnten eine neue Tabelle erstellen, die so etwas wie "ValidParents" heißt und nur übergeordnete IDs mit N untergeordneten Elementen enthält, und sie mit Triggern synchron halten.

+0

Ausgezeichneter Punkt. – JohnFx

+4

Verschiebbare Einschränkungen. – cagcowboy

+1

Es ist sehr schwer, es mit Triggern richtig zu machen. Sie müssen Tabellensperren entfernen, damit sie unter mehreren gleichzeitigen Transaktionen in der Tabelle ordnungsgemäß funktionieren. Alle Transaktionen müssen aufeinander warten und es wird schlecht funktionieren. –

0

Dies ist möglicherweise nicht was Sie wollen, aber ich habe eine Methode, die etwas Ähnliches tut.

Die übliche Anordnung für eine Eins-zu-viele so etwas wie dieses:

Primary Table: 
primary_id (PK) 
primary_stuff 

Secondary Table: 
secondary_id (PK) 
primary_id (FK) 
secondary_stuff 

Die Alternative wäre eine strikte Eins-zu-Eins-zu modellieren:

Primary Table: 
primary_id (PK) 
secondary_id (FK, non-null) 
primary_stuff 

Secondary Table: 
secondary_id (PK) 
secondary_stuff 

Es könnte sein, ein bisschen seltsam, aber es funktioniert. Eine Variante davon kann nützlich sein, wenn es eine Eins-zu-Viele-Eins-zu-Eins-Beziehung gibt, beispielsweise mehrere Adressen für einen Kunden, aber genau eine Rechnungsadresse.

+0

müssen Sie vor der Einfügung der übergeordneten Zeile in die untergeordneten Zeilen einfügen. –

+0

Das ist richtig, weshalb die Einfügung in einer Transaktion erfolgen sollte. –

3

Aufbauend auf den "huhn + ei" -Punkten können Sie aufschiebbare Einschränkungen erstellen, die erst nach der Commit-Zeit validiert werden ... diese könnten helfen?

z.B.

ALTER TABLE AGREEMENTS ADD CONSTRAINT name FOREIGN KEY (column) REFERENCES table (column) DEFERRABLE INITIALLY DEFERRED; 
0

Eine alternative Lösung für das Problem Huhn und Ei ist INSERT ALL. Da es sich um eine einzelne Anweisung handelt, entfällt die Notwendigkeit für aufschiebbare Fremdschlüsseleinschränkungen. Es bietet auch einen Mechanismus zum Einfügen einer genauen Anzahl von abhängigen Zeilen. Zusätzliche Einschränkungen verhindern das Einfügen zusätzlicher Zeilen.Wir benötigen jedoch eine Untertabelle mit Fremdschlüsseln, um das versehentliche Löschen der Zeilen von Interesse zu verhindern.

In diesem Beispiel n = 3.

SQL> create table parent 
    2 (pk_col number not null 
    3  , col1 varchar2(20) 
    4  , constraint par_pk primary key (pk_col) 
    5 ) 
    6/

Table created. 

SQL> 
SQL> create table child 
    2 (pk_col number not null 
    3  , seqno number(1,0) not null 
    4  , col2 date 
    5  , constraint ch_pk primary key 
    6   (pk_col, seqno) 
    7  , constraint ch_par_fk foreign key 
    8   (pk_col) references parent (pk_col) 
    9  , constraint ch_ck check (seqno between 1 and 3) 
10 ) 
11/

Table created. 

SQL> 
SQL> create table child_lock 
    2 (pk_col_1 number not null 
    3  , seqno_1 number(1,0) not null 
    4  , pk_col_2 number not null 
    5  , seqno_2 number(1,0) not null 
    6  , pk_col_3 number not null 
    7  , seqno_3 number(1,0) not null 
    8  , constraint chlk_pk primary key 
    9   (pk_col_1, seqno_1, pk_col_2, seqno_2, pk_col_3, seqno_3) 
10  , constraint chlk_par_1_fk foreign key 
11   (pk_col_1, seqno_1) references child (pk_col, seqno) 
12  , constraint chlk_par_2_fk foreign key 
13   (pk_col_2, seqno_2) references child (pk_col, seqno) 
14  , constraint chlk_par_3_fk foreign key 
15   (pk_col_3, seqno_3) references child (pk_col, seqno) 
16 ) 
17/

Table created. 

SQL> 
SQL> insert all 
    2  into parent values (pk_val, val_1) 
    3  into child values (pk_val, 1, sysdate) 
    4  into child values (pk_val, 2, sysdate+1) 
    5  into child values (pk_val, 3, sysdate+2) 
    6  into child_lock values (pk_val, 1, pk_val, 2, pk_val, 3) 
    7 select 999 as pk_val 
    8   , 'APPLE PIE' as val_1 
    9 from dual 
10/

5 rows created. 

SQL> 
SQL> insert into child values (999, 4, sysdate+4) 
    2/
insert into child values (999, 4, sysdate+4) 
* 
ERROR at line 1: 
ORA-02290: check constraint (APC.CH_CK) violated 


SQL> insert into child values (999, 3, sysdate+4) 
    2/
insert into child values (999, 3, sysdate+4) 
* 
ERROR at line 1: 
ORA-00001: unique constraint (APC.CH_PK) violated 


SQL> insert into child values (999, 2.5, sysdate+4) 
    2/
insert into child values (999, 2.5, sysdate+4) 
* 
ERROR at line 1: 
ORA-00001: unique constraint (APC.CH_PK) violated 


SQL> delete from child 
    2/
delete from child 
* 
ERROR at line 1: 
ORA-02292: integrity constraint (APC.CHLK_PAR_1_FK) violated - child record found 


SQL> 

Ich akzeptiere die Lösung ein wenig gekünstelt und auch unflexibel ist, aber dann ist so die ursprüngliche Anforderung. Es ist auch bei weitem nicht kugelsicher - lösche die Zeile von CHILD_LOCK und du kannst einen oder mehrere CHILD-Datensätze löschen.

0

Sie können Ihre Tabellen normal mit einer 1: M-Beziehung erstellen und dann in der untergeordneten Tabelle eine Zählspalte mit einer Prüfbedingung, die bestimmt, wie viele Kinder für ein Elternelement existieren können, sowie eine eindeutige Einschränkung über die Eltern-ID + Zählungsspalte. z.B .:

CREATE TABLE Parent (PID NUMBER PRIMARY KEY); 

CREATE TABLE Child (
    PID NUMBER NOT NULL, 
    Count NUMBER(1) NOT NULL, 
    CONSTRAINT count_check CHECK (Count BETWEEN 1 AND 5), 
    CONSTRAINT parent_child_fk FOREIGN KEY (PID) REFERENCES Parent (PID), 
    CONSTRAINT count_unique UNIQUE (PID, Count)); 

Das einzige, was nicht ist garantiert, dass für jeden Elternteil gibt es mindestens fünf Kinder; Um dies zu umgehen, können Sie eine materialisierte Ansicht mit einer Einschränkung erstellen, wie WW es vorschlägt, oder Sie können etwas extra in der Anwendung erstellen (z. B. einen "Fehler" -Bericht).

1

Es gibt eine alternative Lösung, um zu erzwingen, dass jedes übergeordnete Element genau 0 oder n untergeordnete Elemente ohne materialisierte Ansichten hat, indem nur die Bedingungen für Überprüfung, Fremdschlüssel und Eindeutigkeit verwendet werden. Dazu muss man die Kinder nummerieren und ein Feld mit der Nummer des nächsten Geschwisters hinzufügen. Hier ein Beispiel für n = 5, die in PostgreSQL, für andere DBS arbeitet man hat wahrscheinlich die Art serielle anzupassen:

create table Tree(
    id serial, 
    parent_id integer not null references Tree(id), 
    child_nr integer check(child_nr between 1 and 5), 
    next_sibling_nr integer, 
    unique (parent_id, child_nr), 
    check(next_sibling_nr in (child_nr+1, child_nr-4)), 
    check(((parent_id is null) and (child_nr is null) and 
    (next_sibling_nr is null)) or ((parent_id is not null) 
    and (child_nr is not null) and (next_sibling_nr is not null))), 
    foreign key (parent_id, next_sibling_nr) references Tree(parent_id, child_nr), 
    primary key (id) 
); 

Die letzte (lang) überprüfen Einschränkung stellt sicher, dass die Felder PARENT_ID, child_nr und next_sibling_nr sind alle null oder alle nicht null. Die Eindeutigkeitsbedingung und die Überprüfung für das Feld child_nr sorgen dafür, dass ein Elternteil höchstens 5 Kinder hat. Die andere Prüfbedingung und die Fremdschlüsseleinschränkung für das Paar (parent_id, next_sibling_nr) stellen sicher, dass nicht weniger als 5 Kinder vorhanden sind.

Nachdem eine Wurzel mit automatisch erzeugten ID 1 mit dem Befehl Einfügen

insert into Tree (parent_id) 
    values (null); 

man Kinder immer in Packungen von 5 Anzahl:

insert into Tree (parent_id, child_nr, next_sibling_nr) 
    values (1, 1, 2), 
     (1, 2, 3), 
     (1, 3, 4), 
     (1, 4, 5), 
     (1, 5, 1); 

Diese Lösung aus den Antworten auf eine ähnliche abgeleitet ist Frage Ich fragte einige Wochen ago.

+0

Ich sah gerade, dass andere die Eltern und Kinder in verschiedenen Tabellen verstanden, während ich dachte, dass sie in der gleichen Tabelle sind. Es ist nicht schwer, meine Antwort an zwei Tabellen anzupassen. –