Entwurfsmuster - eine von vielen übergeordneten Tabellen

13

In der Datenbank stößt man häufig auf eine Situation, in der eine bestimmte Tabelle mit einer von mehreren übergeordneten Tabellen verknüpft werden kann. Ich habe zwei Lösungen für das Problem gesehen, aber keine ist persönlich befriedigend. Ich bin gespannt, welche anderen Muster du da draußen gesehen hast. Gibt es einen besseren Weg, dies zu tun?

Ein erfundenes Beispiel
Nehmen wir an, mein System hatAlerts . Benachrichtigungen können für eine Vielzahl von Objekten empfangen werden - Kunden, Nachrichten und Produkte. Eine bestimmte Warnung kann nur für einen Artikel gelten. Aus welchen Gründen auch immer, Kunden, Artikel und Produkte sind schnelllebig (oder lokalisiert), sodass die erforderlichen Texte / Daten bei der Erstellung eines Alarms nicht in Alerts abgerufen werden können. Angesichts dieses Setups habe ich zwei Lösungen gesehen.

Hinweis: Die unten stehende DDL gilt für SQL Server, aber meine Frage sollte für jedes DBMS zutreffen.

Lösung 1 - Mehrere Nullable FKeys

In dieser Lösung enthält die Tabelle, die mit einer von vielen Tabellen verknüpft ist, mehrere FK-Spalten (der Kürze halber zeigt die folgende DDL die FK-Erstellung nicht an). DAS GUTE - In dieser Lösung ist es schön, dass ich Fremdschlüssel habe. Die Null-Optimalität der FKs macht es praktisch und relativ einfach, genaue Daten hinzuzufügen. DAS SCHLECHTE Abfragen ist nicht besonders gut, da es N LEFT JOINS- oder N UNION-Anweisungen erfordert , um die zugehörigen Daten abzurufen . In SQL Server schließen insbesondere die LEFT JOINS das Erstellen einer indizierten Ansicht aus.

CREATE TABLE Product (
    ProductID    int identity(1,1) not null,
    CreateUTC    datetime2(7) not null,
     Name        varchar(100) not null
    CONSTRAINT   PK_Product Primary Key CLUSTERED (ProductID)
)
CREATE TABLE Customer (
    CustomerID  int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
     Name       varchar(100) not null
    CONSTRAINT  PK_Customer Primary Key CLUSTERED (CustomerID)
)
CREATE TABLE News (
    NewsID      int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    Name        varchar(100) not null
    CONSTRAINT  PK_News Primary Key CLUSTERED (NewsID)
)

CREATE TABLE Alert (
    AlertID     int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    ProductID   int null,
    NewsID      int null,
    CustomerID  int null,
    CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

ALTER TABLE Alert WITH CHECK ADD CONSTRAINT CK_OnlyOneFKAllowed 
CHECK ( 
    (ProductID is not null AND NewsID is     null and CustomerID is     null) OR 
    (ProductID is     null AND NewsID is not null and CustomerID is     null) OR 
    (ProductID is     null AND NewsID is     null and CustomerID is not null) 
)

Lösung 2 - Eine FK in jeder
übergeordneten Tabelle In dieser Lösung hat jede übergeordnete Tabelle eine FK in der Alarmtabelle. Es erleichtert das Abrufen von Warnungen, die einem übergeordneten Element zugeordnet sind. Auf der anderen Seite gibt es keine echte Kette von Alerts, auf die verwiesen wird. Darüber hinaus ermöglicht das Datenmodell verwaiste Warnungen, bei denen eine Warnung nicht mit einem Produkt, einer Nachricht oder einem Kunden verknüpft ist. Wieder mehrere LEFT JOINs, um die Assoziation herauszufinden.

CREATE TABLE Product (
    ProductID    int identity(1,1) not null,
    CreateUTC    datetime2(7) not null,
     Name        varchar(100) not null
    AlertID     int null,
    CONSTRAINT   PK_Product Primary Key CLUSTERED (ProductID)
)
CREATE TABLE Customer (
    CustomerID  int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
     Name       varchar(100) not null
    AlertID     int null,
    CONSTRAINT  PK_Customer Primary Key CLUSTERED (CustomerID)
)
CREATE TABLE News (
    NewsID      int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    Name        varchar(100) not null
    AlertID     int null,
    CONSTRAINT  PK_News Primary Key CLUSTERED (NewsID)
)

CREATE TABLE Alert (
    AlertID     int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

Ist das nur das Leben in einer Beziehungsdatenbank? Gibt es alternative Lösungen, die für Sie zufriedenstellender sind?

EBarr
quelle
1
Können Sie eine übergeordnete Tabelle, Alertable, mit den folgenden Spalten erstellen: ID, CreateDate, Name, Type. Sie können Ihre drei Kindertabellen FK dazu haben, und Ihre Warnung wird FK nur zu einer Tabelle, Alertable.
AK
In einigen Fällen machen Sie einen guten Punkt - effektiv Lösung # 3. Jedes Mal, wenn sich die Daten schnell bewegen oder lokalisiert sind, funktionieren sie jedoch nicht. Angenommen, Produkt, Kunde und News haben jeweils eine entsprechende "Lang" -Tabelle, um den Namen in mehreren Sprachen zu unterstützen. Wenn ich den Namen in der Muttersprache des Benutzers angeben muss, kann ich ihn nicht speichern Alertable. Ist das sinnvoll?
EBarr
1
@EBarr: "Wenn ich den Namen in der Muttersprache des Benutzers angeben muss, kann ich ihn nicht in Alertable speichern. Ist das sinnvoll?" Nein, das tut es nicht. Können Sie Ihr aktuelles Schema in der Tabelle "Produkt", "Kunde" oder "News" speichern, wenn Sie "Name" in der Muttersprache des Benutzers angeben müssen?
ypercubeᵀᴹ
@ypercube Ich habe hinzugefügt, dass jeder dieser Tabellen eine Sprachtabelle zugeordnet ist. Ich habe versucht, ein Szenario zu erstellen, in dem der "Name" -Text je nach Anforderung variieren und daher nicht in Alertable gespeichert werden kann.
EBarr
Sofern es noch keinen akzeptierten Namen hat, schlage ich den Begriff "Octopus Join" für die Abfrage vor, mit der Sie alle Warnungen und die zugehörigen Eltern anzeigen möchten. :)
Nathan Long

Antworten:

4

Ich verstehe die zweite Lösung als nicht zutreffend, da sie keine Beziehung zwischen einem (Objekt) und vielen (Alarm) bietet.

Aufgrund der strengen 3NF-Konformität bleiben Sie bei nur zwei Lösungen.

Ich würde ein geringeres Kopplungsschema entwerfen:

CREATE TABLE Product  (ProductID  int identity(1,1) not null, ...)
CREATE TABLE Customer (CustomerID int identity(1,1) not null, ...)
CREATE TABLE News     (NewsID     int identity(1,1) not null, ...)

CREATE TABLE Alert (
  -- See (1)
  -- AlertID     int identity(1,1) not null,

  AlertClass char(1) not null, -- 'P' - Product, 'C' - Customer, 'N' - News
  ForeignKey int not null,
  CreateUTC  datetime2(7) not null,

  -- See (2)
  CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertClass, ForeignKey)
)

-- (1) you don't need to specify an ID 'just because'. If it's meaningless, just don't.
-- (2) I do believe in composite keys

Oder wenn eine Integritätsbeziehung obligatorisch sein soll, entwerfe ich:

CREATE TABLE Product  (ProductID  int identity(1,1) not null, ...)
CREATE TABLE Customer (CustomerID int identity(1,1) not null, ...)
CREATE TABLE News     (NewsID     int identity(1,1) not null, ...)

CREATE TABLE Alert (
  AlertID     int identity(1,1) not null,
  AlertClass char(1) not null, /* 'P' - Product, 'C' - Customer, 'N' - News */
  CreateUTC  datetime2(7) not null,
  CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

CREATE TABLE AlertProduct  (AlertID..., ProductID...,  CONSTRAINT FK_AlertProduct_X_Product(ProductID)    REFERENCES Product)
CREATE TABLE AlertCustomer (AlertID..., CustomerID..., CONSTRAINT FK_AlertCustomer_X_Customer(CustomerID) REFERENCES Customer)
CREATE TABLE AlertNews     (AlertID..., NewsID...,     CONSTRAINT FK_AlertNews_X_News(NewsID)             REFERENCES News)

Wie auch immer...

Drei gültige Lösungen und eine weitere, die für viele (Objekte) -zu-eins (Alarm-) Beziehungen berücksichtigt werden müssen ...

Diese präsentiert, was ist die Moral?

Sie unterscheiden sich geringfügig und gewichten die folgenden Kriterien gleich:

  • Leistung bei Einfügungen und Aktualisierungen
  • Komplexität bei der Abfrage
  • Lagerraum

So wählen Sie das bequemere für Sie.

Marcus Vinicius Pompeu
quelle
1
Danke für die Eingabe; Ich stimme den meisten Ihrer Kommentare zu. Ich habe die erforderliche Beziehung (mit Ausnahme von Lösung 2 in Ihren Augen) wahrscheinlich kunstvoll beschrieben, da es sich um ein erfundenes Beispiel handelte. fn1 - verstanden, ich habe nur Tabellen stark vereinfacht, um mich auf das Problem zu konzentrieren. fn2 - zusammengesetzte Schlüssel und ich gehe weit zurück! Bezüglich des geringeren Kopplungsschemas verstehe ich die Einfachheit, versuche aber persönlich, wo immer möglich, mit DRI zu entwerfen.
EBarr
Nachdem ich dies durchgesehen hatte, begann ich an der Richtigkeit meiner Lösung zu zweifeln. Trotzdem wurde sie zweimal bewertet, was ich danke. Obwohl ich glaube, mein Design ist gültig, aber nicht für das angegebene Problem geeignet, da die "n" Gewerkschaften / Beitritte nicht angesprochen werden ...
Marcus Vinicius Pompeu
Das DRI-Akronym hat mich erwischt. Für Sie alle steht es für Declarative Referential Integrity, die Technik hinter referentieller Datenintegrität, die üblicherweise als ... (drum roll) ... DDL FOREIGN KEY-Anweisungen implementiert wird. Mehr auf en.wikipedia.org/wiki/Declarative_Referential_Integrity und msdn.microsoft.com/en-us/library/…
Marcus Vinicius Pompeu
1

Ich habe von Triggern gepflegte Join-Tabellen verwendet. Die Lösung funktioniert ziemlich gut als letzter Ausweg, wenn eine Umgestaltung der Datenbank nicht möglich oder wünschenswert ist. Die Idee ist, dass Sie eine Tabelle haben, die nur dazu da ist, die RI-Probleme zu behandeln, und alle DRI sind dagegen.

Chris Travers
quelle