it-swarm.com.de

Bestes Design, um mehrere Tabellen aus einer Spalte zu referenzieren?

Vorgeschlagenes Schema

In erster Linie ist hier ein Beispiel für mein vorgeschlagenes Schema, auf das ich in meinem Beitrag verweisen soll:

Clothes
---------- 
ClothesID (PK) INT NOT NULL
Name VARCHAR(50) NOT NULL
Color VARCHAR(50) NOT NULL
Price DECIMAL(5,2) NOT NULL
BrandID INT NOT NULL
...

Brand_1
--------
ClothesID (FK/PK) int NOT NULL
ViewingUrl VARCHAR(50) NOT NULL
SomeOtherBrand1SpecificAttr VARCHAR(50) NOT NULL

Brand_2
--------
ClothesID (FK/PK) int NOT NULL
PhotoUrl VARCHAR(50) NOT NULL
SomeOtherBrand2SpecificAttr VARCHAR(50) NOT NULL

Brand_X
--------
ClothesID (FK/PK) int NOT NULL
SomeOtherBrandXSpecificAttr VARCHAR(50) NOT NULL

Problemstellung

Ich habe eine Kleidungstabelle mit Spalten wie Name, Farbe, Preis, Brandid und so weiter, um die Attribute für ein bestimmtes Kleidungsstück zu beschreiben .

Hier ist mein Problem: Unterschiedliche Marke Kleidungsstücke erfordern unterschiedliche Informationen. Was ist die beste Vorgehensweise, um mit einem solchen Problem umzugehen?

Beachten Sie, dass es für meine Zwecke erforderlich ist, markenspezifische Informationen ab einem Kleidungseintrag zu finden. Dies liegt daran, dass ich dem Benutzer zuerst die Informationen aus einem Kleidungseintrag anzeige. Danach muss ich seine markenspezifischen Informationen verwenden, um den Artikel zu kaufen. Zusammenfassend muss es eine Richtungsbeziehung zwischen Kleidung (von) und den brand_x Tabellen geben.

Vorgeschlagene/aktuelle Lösung

Um damit fertig zu werden, habe ich an folgendes Entwurfsschema gedacht:

Die Tabelle Kleidung enthält eine Spalte Marke, die ID-Werte zwischen 1 und x haben kann, wobei eine bestimmte ID einer markenspezifischen entspricht Tabelle. Zum Beispiel entspricht der ID-Wert 1 der Tabelle brand_1 (die möglicherweise eine url Spalte enthält), die ID 2 entspricht brand_2 (die möglicherweise eine Lieferant Spalte haben) usw.

Um einen bestimmten Kleidungseintrag mit seinen markenspezifischen Informationen zu verknüpfen, stelle ich mir vor, dass die Logik auf Anwendungsebene ungefähr so ​​aussieht:

clothesId = <some value>
brand = query("SELECT brand FROM clothes WHERE id = clothesId")

if (brand == 1) {
    // get brand_1 attributes for given clothesId
} else if (brand == 2) {
    // get brand_2 attributes for given clothesId
} ... etc.

Andere Kommentare & Gedanken

Ich versuche, meine gesamte Datenbank in BCNF zu normalisieren, und obwohl ich mir das ausgedacht habe, macht mir der resultierende Anwendungscode große Sorgen. Es gibt keine Möglichkeit, Beziehungen zu erzwingen, außer auf Anwendungsebene. Daher fühlt sich das Design sehr hackig und, wie ich vermute, sehr fehleranfällig an.

Forschung

Ich habe die vorherigen Einträge durchgesehen, bevor ich einen Beitrag verfasst habe. Hier ist ein Beitrag mit einem nahezu identischen Problem, das ich gefunden habe. Ich habe diesen Beitrag trotzdem verfasst, da die einzige Antwort anscheinend keine SQL- oder designbasierte Lösung enthält (dh OOP erwähnt) , Vererbung und Schnittstellen).

Ich bin auch ein Anfänger, wenn es um Datenbankdesign geht, und daher würde ich mich über Erkenntnisse freuen.


Es scheint, dass es weitere hilfreiche Antworten zum Stapelüberlauf gibt:

Ich habe auf die dortigen Lösungen verwiesen und anderen vorgeschlagen, meine Frage ebenfalls zu finden.

Trotz der oben angegebenen Links bin ich immer noch auf der Suche nach Antworten und würde mich über alle angebotenen Lösungen freuen!

Ich benutze PostgreSQL.

18
youngrrrr

Ich persönlich verwende zu diesem Zweck kein Schema mit mehreren Tabellen.

  • Es ist schwer, Integrität zu gewährleisten.
  • Es ist schwer zu pflegen.
  • Es ist schwierig, Ergebnisse zu filtern.

Ich habe eine dbfiddle gesetzt Beispiel .

Mein vorgeschlagenes Tabellenschema:

CREATE TABLE #Brands
(
BrandId int NOT NULL PRIMARY KEY,
BrandName nvarchar(100) NOT NULL 
);

CREATE TABLE #Clothes
(
ClothesId int NOT NULL PRIMARY KEY,
ClothesName nvarchar(100) NOT NULL 
);

-- Lookup table for known attributes
--
CREATE TABLE #Attributes
(
AttrId int NOT NULL PRIMARY KEY,
AttrName nvarchar(100) NOT NULL 
);

-- holds common propeties, url, price, etc.
--
CREATE TABLE #BrandsClothes
(
BrandId int NOT NULL REFERENCES #Brands(BrandId),
ClothesId int NOT NULL REFERENCES #Clothes(ClothesId),
VievingUrl nvarchar(300) NOT NULL,
Price money NOT NULL,
PRIMARY KEY CLUSTERED (BrandId, ClothesId),
INDEX IX_BrandsClothes NONCLUSTERED (ClothesId, BrandId)
);

-- holds specific and unlimited attributes 
--
CREATE TABLE #BCAttributes
(
BrandId int NOT NULL REFERENCES #Brands(BrandId),
ClothesId int NOT NULL REFERENCES #Clothes(ClothesId),
AttrId int NOT NULL REFERENCES #Attributes(AttrId),
AttrValue nvarchar(300) NOT NULL,
PRIMARY KEY CLUSTERED (BrandId, ClothesId, AttrId),
INDEX IX_BCAttributes NONCLUSTERED (ClothesId, BrandId, AttrId)
);

Lassen Sie mich einige Daten einfügen:

INSERT INTO #Brands VALUES 
(1, 'Brand1'), (2, 'Brand2');

INSERT INTO #Clothes VALUES 
(1, 'Pants'), (2, 'T-Shirt');

INSERT INTO #Attributes VALUES
(1, 'Color'), (2, 'Size'), (3, 'Shape'), (4, 'Provider'), (0, 'Custom');

INSERT INTO #BrandsClothes VALUES
(1, 1, 'http://mysite.com?B=1&C=1', 123.99),
(1, 2, 'http://mysite.com?B=1&C=2', 110.99),
(2, 1, 'http://mysite.com?B=2&C=1', 75.99),
(2, 2, 'http://mysite.com?B=2&C=2', 85.99);

INSERT INTO #BCAttributes VALUES
(1, 1, 1, 'Blue, Red, White'),
(1, 1, 2, '32, 33, 34'),
(1, 2, 1, 'Pearl, Black Widow'),
(1, 2, 2, 'M, L, XL'),
(2, 1, 4, 'Levis, G-Star, Armani'),
(2, 1, 3, 'Slim fit, Regular fit, Custom fit'),
(2, 2, 4, 'G-Star, Armani'),
(2, 2, 3, 'Slim fit, Regular fit'),
(2, 2, 0, '15% Discount');

Wenn Sie allgemeine Attribute abrufen müssen:

SELECT     b.BrandName, c.ClothesName, bc.VievingUrl, bc.Price
FROM       #BrandsClothes bc
INNER JOIN #Brands b
ON         b.BrandId = bc.BrandId
INNER JOIN #Clothes c
ON         c.ClothesId = bc.ClothesId
ORDER BY   bc.BrandId, bc.ClothesId;

BrandName   ClothesName   VievingUrl                  Price
---------   -----------   -------------------------   ------
Brand1      Pants         http://mysite.com?B=1&C=1   123.99
Brand1      T-Shirt       http://mysite.com?B=1&C=2   110.99
Brand2      Pants         http://mysite.com?B=2&C=1    75.99
Brand2      T-Shirt       http://mysite.com?B=2&C=2    85.99

Oder Sie können ganz einfach Kleidung nach Marke bekommen:

Gib mir alle Klamotten von Brand2

SELECT     c.ClothesName, b.BrandName, a.AttrName, bca.AttrValue
FROM       #BCAttributes bca
INNER JOIN #BrandsClothes bc
ON         bc.BrandId = bca.BrandId
AND        bc.ClothesId = bca.ClothesId
INNER JOIN #Brands b
ON         b.BrandId = bc.BrandId
INNER JOIN #Clothes c
ON         c.ClothesId = bc.ClothesId
INNER JOIN #Attributes a
ON         a.AttrId = bca.AttrId
WHERE      bca.ClothesId = 2
ORDER BY   bca.ClothesId, bca.BrandId, bca.AttrId;

ClothesName   BrandName   AttrName   AttrValue
-----------   ---------   --------   ---------------------
T-Shirt       Brand1      Color      Pearl, Black Widow
T-Shirt       Brand1      Size       M, L, XL
T-Shirt       Brand2      Custom     15% Discount
T-Shirt       Brand2      Shape      Slim fit, Regular fit
T-Shirt       Brand2      Provider   G-Star, Armani

Aber für mich ist eines der besten dieses Schemas, dass Sie nach Attibutes filtern können:

Gib mir alle Kleider, die das Attribut haben: Größe

SELECT     c.ClothesName, b.BrandName, a.AttrName, bca.AttrValue
FROM       #BCAttributes bca
INNER JOIN #BrandsClothes bc
ON         bc.BrandId = bca.BrandId
AND        bc.ClothesId = bca.ClothesId
INNER JOIN #Brands b
ON         b.BrandId = bc.BrandId
INNER JOIN #Clothes c
ON         c.ClothesId = bc.ClothesId
INNER JOIN #Attributes a
ON         a.AttrId = bca.AttrId
WHERE      bca.AttrId = 2
ORDER BY   bca.ClothesId, bca.BrandId, bca.AttrId;

ClothesName   BrandName   AttrName   AttrValue
-----------   ---------   --------   ----------
Pants         Brand1      Size       32, 33, 34
T-Shirt       Brand1      Size       M, L, XL

Die Verwendung eines Schemas mit mehreren Tabellen für alle vorherigen Abfragen erfordert die Verarbeitung einer unbegrenzten Anzahl von Tabellen oder von XML- oder JSON-Feldern.

Eine weitere Option bei diesem Schema besteht darin, dass Sie Vorlagen definieren können, z. B. eine neue Tabelle BrandAttrTemplates hinzufügen können. Jedes Mal, wenn Sie einen neuen Datensatz hinzufügen, können Sie einen Trigger oder ein SP) verwenden, um einen Satz vordefinierter Attribute für diesen Zweig zu generieren.

Es tut mir leid, ich möchte meine Erklärungen erweitern, indem ich denke, dass es klarer ist als mein Englisch.

Update

Meine aktuelle Antwort sollte auf jedem RDBMS funktionieren. Laut Ihren Kommentaren würde ich kleine Änderungen vorschlagen, wenn Sie Attributwerte filtern müssen.

Soweit MS-Sql keine Arrays zulässt, habe ich ein neues Beispiel eingerichtet, das dasselbe Tabellenschema enthält, aber AttrValue in einen ARRAY-Feldtyp ändert.

Mit POSTGRES können Sie dieses Array mithilfe eines GIN-Index nutzen.

(Lassen Sie mich sagen, dass @EvanCarrol ein gutes Wissen über Postgres hat, sicherlich besser als ich. Aber lassen Sie mich mein bisschen hinzufügen.)

CREATE TABLE BCAttributes
(
BrandId int NOT NULL REFERENCES Brands(BrandId),
ClothesId int NOT NULL REFERENCES Clothes(ClothesId),
AttrId int NOT NULL REFERENCES Attrib(AttrId),
AttrValue text[],
PRIMARY KEY (BrandId, ClothesId, AttrId)
);

CREATE INDEX ix_attributes on BCAttributes(ClothesId, BrandId, AttrId);
CREATE INDEX ix_gin_attributes on BCAttributes using GIN (AttrValue);


INSERT INTO BCAttributes VALUES
(1, 1, 1, '{Blue, Red, White}'),
(1, 1, 2, '{32, 33, 34}'),
(1, 2, 1, '{Pearl, Black Widow}'),
(1, 2, 2, '{M, L, XL}'),
(2, 1, 4, '{Levis, G-Star, Armani}'),
(2, 1, 3, '{Slim fit, Regular fit, Custom fit}'),
(2, 2, 4, '{G-Star, Armani}'),
(2, 2, 3, '{Slim fit, Regular fit}'),
(2, 2, 0, '{15% Discount}');

Jetzt können Sie zusätzlich mit einzelnen Attributen Werte abfragen wie:

Gib mir eine Liste aller Hosen Größe: 33

AttribId = 2 AND ARRAY['33'] && bca.AttrValue

SELECT     c.ClothesName, b.BrandName, a.AttrName, array_to_string(bca.AttrValue, ', ')
FROM       BCAttributes bca
INNER JOIN BrandsClothes bc
ON         bc.BrandId = bca.BrandId
AND        bc.ClothesId = bca.ClothesId
INNER JOIN Brands b
ON         b.BrandId = bc.BrandId
INNER JOIN Clothes c
ON         c.ClothesId = bc.ClothesId
INNER JOIN Attrib a
ON         a.AttrId = bca.AttrId
WHERE      bca.AttrId = 2
AND        ARRAY['33'] && bca.AttrValue
ORDER BY   bca.ClothesId, bca.BrandId, bca.AttrId;

Das ist das Ergebnis:

clothes name | brand name | attribute | values 
------------- ------------ ----------  ---------------- 
Pants          Brand1       Size        32, 33, 34
7
McNets

Was Sie beschreiben, ist zumindest teilweise ein Produktkatalog. Sie haben mehrere Attribute, die allen Produkten gemeinsam sind. Diese gehören in eine gut normalisierte Tabelle.

Darüber hinaus haben Sie eine Reihe von Attributen, die markenspezifisch sind (und ich erwarte, dass sie produktspezifisch sein könnten). Was muss Ihr System mit diesen spezifischen Attributen tun? Haben Sie eine Geschäftslogik, die vom Schema dieser Attribute abhängt, oder listen Sie sie nur in einer Reihe von "label": "value" -Paaren auf?

Andere Antworten schlagen vor, einen CSV-Ansatz zu verwenden (unabhängig davon, ob dies JSON oder ARRAY oder auf andere Weise ist). Diese Ansätze verzichten auf die reguläre Behandlung relationaler Schemas, indem das Schema aus Metadaten in die Daten verschoben wird selbst.

Hierfür gibt es ein tragbares Entwurfsmuster, das sehr gut zu relationalen Datenbanken passt. Es ist EAV (Entity-Attribut-Wert). Ich bin sicher, Sie haben an vielen, vielen Orten gelesen, dass "EAV ist böse" (und es ist). Es gibt jedoch eine bestimmte Anwendung, bei der die Probleme mit EAV nicht wichtig sind, nämlich Produktattributkataloge.

Alle üblichen Argumente gegen EAV gelten nicht für einen Produktfeature-Katalog, da Produktfeature-Werte im Allgemeinen nur in eine Liste oder im schlimmsten Fall in eine Vergleichstabelle zurückgeführt werden.

Durch die Verwendung eines Spaltentyps JSON können Sie Dateneinschränkungen aus der Datenbank erzwingen und in Ihre Anwendungslogik erzwingen. Die Verwendung einer Attributtabelle für jede Marke hat außerdem die folgenden Nachteile:

  • Es lässt sich nicht gut skalieren, wenn Sie schließlich Hunderte von Marken (oder mehr) haben.
  • Wenn Sie die zulässigen Attribute für eine Marke ändern, müssen Sie eine Tabellendefinition ändern, anstatt nur Zeilen in einer Markenfeld-Steuertabelle hinzuzufügen oder zu entfernen.
  • Es kann sein, dass Sie immer noch dünn besetzte Tabellen haben, wenn die Marke viele potenzielle Merkmale aufweist, von denen nur eine kleine Teilmenge bekannt ist.

Es ist nicht besonders schwierig, Daten zu einem Produkt mit markenspezifischen Funktionen abzurufen. Es ist wohl einfacher, ein dynamisches SQL mit dem EAV-Modell zu erstellen, als mit dem Modell für Tabellen pro Kategorie. In der Tabelle pro Kategorie benötigen Sie eine Reflexion (oder Ihr JSON), um herauszufinden, wie die Namen der Feature-Spalten lauten. Anschließend können Sie eine Liste mit Elementen für eine where-Klausel erstellen. Im EAV-Modell ist das WHERE X AND Y AND Z wird INNER JOIN X INNER JOIN Y INNER JOIN Z, daher ist die Abfrage etwas komplizierter, aber die Logik zum Erstellen der Abfrage ist immer noch vollständig tabellengesteuert und mehr als skalierbar genug, wenn Sie die richtigen Indizes erstellt haben.

Es gibt viele Gründe, EAV nicht als allgemeinen Ansatz zu verwenden. Diese Gründe gelten nicht für einen Produktfeature-Katalog, sodass EAV in dieser speziellen Anwendung nichts auszusetzen hat.

Dies ist allerdings eine kurze Antwort auf ein komplexes und kontroverses Thema. Ich habe ähnliche Fragen bereits beantwortet und bin näher auf die allgemeine Abneigung gegen EAV eingegangen. Zum Beispiel:

Ich würde sagen, dass EAV in letzter Zeit aus meist guten Gründen seltener verwendet wird als früher. Ich denke jedoch, dass es auch nicht gut verstanden wird.

4
Joel Brown

Hier ist mein Problem: Verschiedene Kleidungsmarken erfordern unterschiedliche Informationen. Was ist die beste Vorgehensweise, um mit einem solchen Problem umzugehen?

Verwenden von JSON und PostgreSQL

Ich denke, du machst das schwieriger als es sein muss und du wirst später damit gebissen. Sie benötigen kein Entity-Attribut-Wert-Modell , es sei denn, Sie benötigen tatsächlich EAV.

CREATE TABLE brands (
  brand_id     serial PRIMARY KEY,
  brand_name   text,
  attributes   jsonb
);
CREATE TABLE clothes (
  clothes_id   serial        PRIMARY KEY,
  brand_id     int           NOT NULL REFERENCES brands,
  clothes_name text          NOT NULL,
  color        text,
  price        numeric(5,2)  NOT NULL
);

An diesem Schema ist absolut nichts auszusetzen.

INSERT INTO brands (brand_name, attributes)
VALUES
  ( 'Gucci', $${"luxury": true, "products": ["purses", "tawdry bougie thing"]}$$ ),
  ( 'Hugo Boss', $${"Origin": "Germany", "known_for": "Designing uniforms"}$$ ),
  ( 'Louis Vuitton', $${"Origin": "France", "known_for": "Designer Purses"}$$ ),
  ( 'Coco Chanel', $${"known_for": "Spying", "smells_like": "Banana", "luxury": true}$$ )
;

INSERT INTO clothes (brand_id, clothes_name, color, price) VALUES
  ( 1, 'Purse', 'orange', 100 ),
  ( 2, 'Underwear', 'Gray', 10 ),
  ( 2, 'Boxers', 'Gray', 10 ),
  ( 3, 'Purse with Roman Numbers', 'Brown', 10 ),
  ( 4, 'Spray', 'Clear', 100 )
;

Jetzt können Sie es mit einem einfachen Join abfragen

SELECT *
FROM brands
JOIN clothes
  USING (brand_id);

Und jeder der JSON-Operatoren arbeitet in einer where-Klausel.

SELECT *
FROM brands
JOIN clothes
  USING (brand_id)
WHERE attributes->>'known_for' ILIKE '%Design%';

Als Randnotiz: Fügen Sie die URLs nicht in die Datenbank ein. Sie ändern sich im Laufe der Zeit. Erstellen Sie einfach eine Funktion, die sie übernimmt.

generate_url_brand( brand_id );
generate_url_clothes( clothes_id );

oder Wasauchimmer. Wenn Sie PostgreSQL verwenden, können Sie sogar Hashids verwenden.

Von besonderer Bedeutung ist auch, dass jsonb als Binärdatei (also das -'b ') gespeichert ist und auch indexfähig oder SARGable oder wie auch immer die coolen Kids es heutzutage nennen: CREATE INDEX ON brands USING gin ( attributes );

Der Unterschied liegt hier in der Einfachheit der Abfrage.

Gib mir alle Klamotten von Brand2

SELECT * FROM clothes WHERE brand_id = 2;

Gib mir alle Kleider, die das Attribut haben: Größe

SELECT * FROM clothes WHERE attributes ? 'size';

Wie wäre es mit einem anderen ..

Gib mir alle Kleider und Attribute für alle Kleider, die in großen Mengen erhältlich sind.

SELECT * FROM clothes WHERE attributes->>'size' = 'large';
3
Evan Carroll