0

Ich versuche, eine Datenbank zu entwerfen. Ich habe ein Design, das mir auf Fourth Normal Form normalisiert zu sein scheint - aber immer noch denke ich, ist gebrochen, und ich kann nicht für das Leben von mir erarbeiten, wie man es repariert.Wie sollte ich dieses Datenbankdesign normalisieren?

Hintergrund: Wir haben vier Arten von Tests und ein paar Dutzend Tests für jeden Typ. Wir führen die Tests in Stapeln durch, wobei jeder Stapel Tests nur eines Typs enthält. Ein Testergebnis gehört also zu einer Charge und gehört ebenfalls zu einem Test. Welche gibt eine Datenbank Plan etwas wie folgt aus:

Diagram with Result going Many-to-One to Test and Result also going Many-to-One to Batch, and both Batch and Test going Many-to-One to TypeOfTest

Das Problem ist, dass dieser Entwurf für ein Ergebnis ermöglicht es, die für einen Test von Typ A ist, aber das Ergebnis ist in einem Batch für Typ B.

Eine Sache, die ich nicht tun kann, ist die Kombination der Test- und Batch-Tabellen in einer Tabelle. Jede Woche gibt es einen neuen Batch, während ein Test Monate oder Jahre dauert. Und ein Batch kann viele Tests enthalten (obwohl immer vom selben Typ), und ein Test wird normalerweise viele Male in vielen Batches durchgeführt.

Ich könnte eine Viele-zu-Viele-Verbindung zwischen Test und Batch einfügen, aber ich kann nicht sofort sehen, wie das alles helfen würde.

Gibt es eine saubere Möglichkeit, dies neu zu organisieren, so dass wir den kreisförmigen Join-Pfad nicht haben? Ist das notwendig? Oder wünschenswert?

Oder sollte ich einfach mit dem gehen, was ich habe, und aufhören, mir darüber Gedanken zu machen? :-)

[Edit 1] Beachten Sie, dass der Test Details darüber enthält, wie er läuft, wer gefundene Fehler behebt, usw., die über mehrere Batches konstant bleiben, so dass der Test unabhängig von Batches existieren muss nicht) laufen in

[Edit 2] es wurde darauf hingewiesen, dass es besser wäre, einen TestBatch Tisch zu haben, die uns diese Struktur gibt.

Now with TestBatch in the place where Result used to be, and Result going Many-to-One to TestBatch

ich stimme dies eine gut Idee, aber das behebt das Problem nicht wirklich. Es verschiebt das Problem nur von Ergebnis zu TestBatch. Wir können jetzt einen TestBatch haben, der für einen Test vom Typ A ist, aber diesen TestBatch in einem Batch für Typ B hat.

[Edit 3] Dank @ philip-kelleys ausgezeichnetem Vorschlag, glaube ich, dass wir eine haben Antworten. Zunächst verbinden wir TestBatch direkt zurück nach Typ, also:

enter image description here

Dies gilt nicht sofort das Problem beheben. Tatsächlich macht es es noch schlimmer - es könnte jetzt einen Typ für den Test geben, einen anderen Typ für den Stapel und einen dritten Typ, der direkt aus dem TestBatch stammt.

Aber der zweite Schritt besteht darin, den Fremdschlüssel von TestBatch zu Test zu ändern, so dass es sowohl den Typ als auch die TestID enthält. Und den Fremdschlüssel in Batch zu ändern, um sowohl den Typ als auch die BatchID einzuschließen.

Auf diese Weise können wir sicher sein, dass der TestBatch den gleichen Typ wie der Test und der Batch hat.

+0

Warten Sie, ich vermisse etwas. Wenn 'jeder Stapel Tests nur eines Typs 'enthält und' ein Testergebnis zu einem Stapel gehört ', wie kann dann ein' Ergebnis, das für einen Test vom Typ A ist 'in einem Stapel für Typ B sein? –

+0

In der realen Welt - kann es nicht. Jedes Ergebnis ist für einen bestimmten Test und einen bestimmten Batch, und dieser Test und dieser Batch * müssen * vom gleichen Typ sein. In meinem Datenbank-Design - es ist einfach, ein Ergebnis für einen Test des Typs A zu haben, aber das Ergebnis ist in einer Charge von Typ B. Und deshalb denke ich, mein Design ist gebrochen. –

Antwort

3

@ HLGEM Antwort beschreibt das logische Modell, mit einigen physikalischen Modell Detail zeigen würde, s. Eine physische Implementierung, die unterstützt und Ihre Geschäftsregeln erzwingt, würde in etwa so aussehen. (Dies ist Pseudo-Code, zeigt nur die Schlüsselspalten - Sie möchten Spalten für Attribute wie Name, Score, etc. hinzufügen. Tatsächliche Implementierungsdetails sind systemabhängig und können etwas knifflig sein, aber jedes RDBMS sollte in der Lage sein Um dies zu unterstützen, beachten Sie, dass alle aufgeführten Spalten NICHT NULL-fähig sind.)

CREATE TABLE TestType 
    TestType int 
    <primary key on TestType> 


CREATE TABLE Test 
    TestId  int 
    TestType  int 
    <primary key on TestId> 
    <foreign key into TestType on column TestType> 


CREATE TABLE Batch 
    BatchId  int 
    TestType  int 
    <primary key on BatchId> 
    <foreign key into TestType on column TestType> 


CREATE TABLE TestInBatch 
    TestInBatchId int 
    TestId   int 
    BatchId  int 
    TestType  int 
    <primary key on TestInBatchId> 
    <unique constraint on TestId, BatchId> 
    <foreign key on (TestId, TestType) into Test, columns (TestId, TestType)> 
    <foreign key on (BatchId, TestType) into Batch, columns (BatchId, TestType)> 


CREATE TABLE Result 
    ResultId  int 
    TestInBatchId int 
    <primary key on ResultId> 
    <foreign key into TestInBatch on column TestInBatchId> 
+0

Natürlich! Sie haben ein TestType-Feld in TestInBatch hinzugefügt und es als zweites Feld in den Fremdschlüsseln für Test und Batch verwendet. Dies bedeutet, dass der TestType immer für ein bestimmtes TestInBatch festgelegt ist und immer sowohl dem Test als auch dem Batch entspricht. Perfekt! :-) –

+0

Wenn wir ein TestType-Feld in der TestInBatch-Tabelle haben, um es in den Fremdschlüsseln für Test und Batch zu verwenden, werde ich dieses Feld wahrscheinlich als direkten Fremdschlüssel-Link zurück zur TestType-Tabelle verwenden Gut. Ich benutze Laravel, es ist also praktisch, direkt zu verlinken. –

+0

Mit dem Fremdschlüssel zwischen TestType und Test und Batch, und die Fremdschlüssel von ihnen zu TestInBatch, ein Fremdschlüssel zwischen TestInBatch und TestType ist nicht unbedingt erforderlich ... aber wenn es bei der Implementierung oder Leistung hilft, gehen Sie voran. –

1

Erstellen Sie eine TestBatch-Tabelle, die die Tests enthält, die einem bestimmten Batch zugeordnet sind. Verwenden Sie die PK dieser Tabelle als FK in der Ergebnistabelle.

Sie benötigen TestBatch in jedem Fall, da die Tests, die mit einer bestimmten Charge verbunden sind, ein historischer Zeitpunkt sind, den Sie erfassen müssen. Jedes Mal, wenn Sie einen neuen Stapel erstellen, können neue Tests hinzugefügt werden, die jedoch nicht mit früheren, abgeschlossenen Stapeln verknüpft werden sollen.

TestBatch verbindet sich zu Test und Batch und enthält BatchID, TestID und eine eigene ID. Dann enthält die Ergebnistabelle die ID von TestBatch als ihren Fremdschlüssel.

So die Ergebnisse dagegen zu sehen, Sie Ergebnisse zu TestBatch anschließen würden und dann beschreibende Details greifen beide aus dem Test und die Batch-Tabellen wie folgt:

Select r.ResultId, R.Col1, r.col2, b.BatchId, b.batchdate, t.testId, t.Test_description 
From Results r 
join TestBatch tb on r.TestBatchid = tb.TestBatchid 
join Batch b on tb.batchid = b.batchid 
join Test t on tb.testid = t.testid 

Typ wahrscheinlich meist verwendet wird, würde das schaffen Record für TestBatch zu dem Zeitpunkt, zu dem der Batch erstellt wurde. Und zu den oben genannten zu filtern nach Typ In diesem Fall möchten Sie im Allgemeinen nur zu Typ zu Batch oder Test, aber nicht beide zur gleichen Zeit beitreten.

Um Ihnen ein wenig davon zu zeigen, wie dies mit Daten funktioniert (Vergessen Sie die Ergebnistabelle für jetzt und die offiziellen FKs und PKs, die Sie in @PhillipKelleys ausgezeichnete Antwort sehen können) Code für SQL-Server geschrieben wurde, verwendete ich temporäre Tabellen so Sie können ein wenig herumspielen, bevor Sie sich an eine Struktur binden, aber die Zeichen # entfernen, wenn Sie echte Tabellen erstellen möchten. Identität ist das, was SQL Server verwendet automatisch generierten Felder Ersatz mit dem Code für Ihre Datenbank-Backend erstellen etwas ähnliches zu tun .:

Create table #type (Typeid int identity, TypeDescription varchar(100)) 

Insert into #type (TypeDescription) 
values ('Geography'), ('History'), ('Biology'), ('Math') 

Create table #Batch (BatchID int identity, TypeID int, BatchDate datetime) 

insert into #Batch (TypeID, BatchDate) 
values (1, getdate()-1), (1, getdate() +2) , (4, getdate()) 

Create table #Test (testId int identity, TestDescription varchar(50), TypeId int) 
Insert into #Test (TestDescription, TypeId) 
values ('fall midterm', 1), ('fall final', 1), ('fall midterm', 3), ('fall final', 3), ('fall final', 2), ('fall midterm', 4), ('fall final', 4) 

Create table #TESTBATCH (TestBatchID int identity, TestID int, BATCHID int) 

Insert into #testBatch (BATCHID, TestID) 
values(1, 1), (1, 2), (2,1), (2,2), (3,6), (3, 7) 

select * from #type 
select * from #Batch 
select * from #test 
select * from #testBatch 

Hier werden die Details aller aktuellen Chargen zeigen würde

select B.batchdate, t.TypeDescription, te.TestDescription, t2.TypeDescription 
from #testBatch tb 
join #batch b on b.batchid = tb.batchid 
join #type t on t.typeid = b.typeid 
join #test te on te.testid = tb.testid 
join #type t2 on t2.typeid = te.typeid 

Diese alle aktuellen Tests auch die, die ohne aktuellen Charge

select te.TestDescription, t2.TypeDescription, B.batchdate, t.TypeDescription 
from #test te 
join #type t2 on t2.typeid = te.typeid 
left join #testBAtch tb on te.testID = tb.testId 
left join #batch b on b.batchid = tb.batchid 
left join #type t on t.typeid = b.typeid 
+0

Guter Punkt - Ich kann sehen, wie eine TestBatch Tabelle wert wäre. Allerdings würde es das ursprüngliche Problem nicht beheben. Es würde nur bedeuten, dass ich das Problem auf TestBatch anstelle von Ergebnis haben würde. Zum Beispiel - Sie schlagen vor, dass ich im Allgemeinen nur bei Typ zu Batch oder Test beitreten möchte, aber nicht beide gleichzeitig. Meine Frage ist - Was passiert, wenn ich von TestBatch zu Batch komme, um eine Funktion einzugeben, aber ich schließe mich von TestBatch zu Test an, um eine andere einzugeben? Und was, wenn der Batch einen anderen Testtyp hat? Ich hätte zwei verschiedene Typen für den gleichen TestBatch, je nachdem, in welcher Funktion ich bin. –

+0

Typ ist eine Nachschlagetabelle. Sie möchten es nur in einem Join verwenden, wenn Sie den Textwert eines angegebenen Typs suchen möchten. Wenn Sie das für Test und Batch in derselben Abfrage benötigen, verbinden Sie sich zweimal damit, aber behalten Sie die reale Beziehung durch Teststapel bei. Im Allgemeinen, da Sie sagen, dass ein Stapel nur für einen Typ zu einem Zeitpunkt bestimmt ist, möchten Sie nur über den Stapel zu Typ eingeben, es sei denn, Stapel ist überhaupt nicht Teil der Abfrage. – HLGEM

Verwandte Themen