Tek sütundan birden fazla tabloya referans vermek için en iyi tasarım?


18

Önerilen şema

İlk ve en önemlisi, benim yazı boyunca referans için önerilen şema örneği:

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

Sorun bildirimi

Belirli bir giyim eşyasının özelliklerini tanımlamak için isim, renk, fiyat, brandid vb.Gibi sütunlara sahip bir giysi masam var .

İşte benim sorunum: Farklı giyim markaları farklı bilgiler gerektirir. Böyle bir problemle başa çıkmak için en iyi uygulama hangisidir?

Benim amacım için, bir giysi girişinden başlayan markaya özgü bilgileri bulmak gerektiğini unutmayın . Bunun nedeni , önce kullanıcıya bir giysi girişinden gelen bilgileri görüntülemem , daha sonra öğeyi satın almak için markaya özgü bilgileri kullanmam gerekir. Özetle, giysiler (dan) ve brand_x tabloları arasında yönlü bir ilişki olmalıdır .

Önerilen / mevcut çözüm

Bununla başa çıkmak için aşağıdaki tasarım şemasını düşündüm:

Giyim tablo olacaktır marka 1 x arasında değişen kimlik değerine sahip olabilir sütun burada bir marka özgü tablo özel bir kimlik denk gelmektedir. Örneğin, id değeri 1 tablo brand_1'e (bir url sütununa sahip olabilir ), id 2 brand_2'ye (bir tedarikçi sütununa sahip olabilir ) vb. Karşılık gelecektir .

Bu nedenle, belirli bir giysi girişini markaya özgü bilgilerle ilişkilendirmek için, uygulama düzeyindeki mantığın şöyle görüneceğini hayal ediyorum:

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.

Diğer yorum ve düşünceler

Tüm veritabanımı BCNF normalleştirmek için çalışıyorum ve bu ne kadar geldi, ancak ortaya çıkan uygulama kodu beni çok endişeli hissettiriyor. Uygulama seviyesi dışında ilişkileri güçlendirmenin bir yolu yoktur ve bu nedenle tasarım çok acayip hissediyor ve tahminimce hataya açık.

Araştırma

Gönderi yapmadan önce önceki girişlere baktığınızdan emin oldum. İşte bulmayı başardığım neredeyse aynı sorunu olan bir yazı.Ben zaten bu yazı yapılmış çünkü verilen tek cevap bir SQL veya tasarım tabanlı bir çözüm yok gibi görünüyor (yani OOP, kalıtım ve arayüzleri bahseder).

Ben de veritabanı tasarımı söz konusu olduğunda acemi biriyim ve bu yüzden herhangi bir anlayış takdir ediyorum.


Yığın Taşması konusunda daha yararlı yanıtlar olduğu anlaşılıyor:

Oradaki çözümlere değindim ve başkalarının da sorumu bulmasını öneriyorum.

Yukarıda verilen bağlantılara rağmen, ben hala burada yanıt arayışı içindeyim ve sağlanan çözümleri takdir ediyorum!

PostgreSQL kullanıyorum.

Yanıtlar:


7

Şahsen bu amaç için çok masalı bir şema kullanmak istemiyorum.

  • Bütünlüğü sağlamak zor.
  • Bakımı zor.
  • Sonuçları filtrelemek zor.

Bir dbfiddle örneği belirledim .

Önerilen tablo şemam:

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)
);

Biraz veri ekleyeyim:

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');

Ortak özellikleri getirmeniz gerekiyorsa:

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

Veya Markaya Göre Giysileri kolayca alabilirsiniz:

Bana Brand2'nin tüm kıyafetlerini ver

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

Ama benim için, bu şemanın en iyilerinden biri Attibutes'a göre filtreleyebilmenizdir:

Bana şu özelliğe sahip tüm Giysileri ver: Boyut

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

Önceki sorguların ne olursa olsun çok tablolu bir şema kullanmak, sınırsız sayıda tabloyla veya XML veya JSON alanlarıyla uğraşmayı gerektirir.

Bu şemadaki başka bir seçenek de şablon tanımlayabilmenizdir, örneğin BrandAttrTemplates için yeni bir tablo ekleyebilirsiniz. Her yeni kayıt eklediğinizde, bu Şube için önceden tanımlanmış bir dizi özellik oluşturmak için bir tetikleyici veya bir SP kullanabilirsiniz.

Üzgünüm, açıklamalarımı İngilizcemden daha net olduğunu düşünüyorum.

Güncelleme

Şu anki cevabım hangi RDBMS olursa olsun çalışmalıdır. Yorumlarınıza göre, özellik değerlerini filtrelemeniz gerekiyorsa küçük değişiklikler öneririm.

MS-Sql dizilere izin vermediği sürece, ben aynı tablo şemasını yöneten, ancak AttrValue bir ARRAY alan türüne değiştirerek yeni bir örnek kurduk.

Aslında, POSTGRES kullanarak, bir GIN dizini kullanarak bu dizinin avantajlarından yararlanabilirsiniz.

(@EvanCarrol'un Postgres hakkında iyi bir bilgiye sahip olduğunu söyleyeyim, kesinlikle benden daha iyi. Ama biraz ekleyeyim.)

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}');

Artık ek öznitelik değerlerini kullanarak aşağıdakileri sorgulayabilirsiniz:

Bana tüm pantolonların bir listesini ver Boyut: 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;

Sonuç budur:

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

Bu açıklamayı gerçekten çok beğendim, ancak görünüşe göre, bu çoklu CSV'leri tek bir sütunda bulundurmak için çok masalı bir şemadan işlem görüyoruz. Öte yandan, bu yaklaşımı daha iyi sevdiğimi hissediyorum, çünkü şemada herhangi bir değişiklik gerektirmiyor, ancak yine de sorunu başka bir yere itiyormuşuz gibi geliyor (değişken uzunluklu sütunlara sahip olarak). Bu bir sorun olabilir; DB 3 boyutu pantolon sorgulamak istesem ne olacak? Belki de bu tür bir soruna güzel ve temiz bir çözüm yoktur. Bu konseptin bir ismi var mı?
youngrrrr

Aslında ... verdiğim soruyu cevaplamak için, belki de cevap @ EvanCarroll'un çözümünden ödünç alınabilir: yani, CSV formatında TEXT / STRINGS yerine jsonb tiplerini kullanarak. Ama yine de - bu kavram için bir isim varsa, lütfen bana bildirin!
youngrrrr

1
Bir Varlık Özelliği Değer türü çözümdür. Performans ve iyi tasarım arasında kötü bir uzlaşma yok. Yine de bu bir ödünleşmedir. Sonsuz "Brand_X" tabloları ile çevrili olmayan daha temiz bir tasarım için biraz performans alıyorsunuz. Belirttiğiniz en yaygın yönden giden performans cezası asgari düzeyde olmalıdır. Diğer yöne gitmek daha acı verici olacak, ama bu uzlaşma. en.wikipedia.org/wiki/…
Jonathan Fite

4

Açıkladığınız şey, en azından kısmen, bir ürün kataloğudur. Tüm ürünler için ortak olan birkaç özelliğiniz vardır. Bunlar iyi normalleştirilmiş bir tabloya aittir.

Bunun ötesinde, markaya özgü bir dizi özelliğiniz var (ve ürüne özgü olabileceğini düşünüyorum). Sisteminizin bu belirli özelliklerle ne ilgisi var? Bu özelliklerin şemasına bağlı olan iş mantığınız var mı veya bunları sadece bir dizi "etiket": "değer" çiftinde mi listeliyorsunuz?

Diğer cevaplar, esasen bir CSV yaklaşımı olanı kullanmayı önerir (bu JSONya ARRAYda ya da başka türlü olsun) - Bu yaklaşımlar, şemayı meta verilerin dışına ve verinin içine taşıyarak düzenli ilişkisel şema işlemeden önce gelir.

Bunun için ilişkisel veritabanlarına çok iyi uyan taşınabilir bir tasarım deseni vardır. EAV (varlık-özellik-değer). Eminim ki birçok yerde "EAV Evil" dir (ve öyle). Bununla birlikte, EAV ile ilgili sorunların önemli olmadığı özel bir uygulama vardır ve bu da ürün özellik kataloglarıdır.

EAV'a karşı genel argümanların tümü bir ürün özellik kataloğu için geçerli değildir, çünkü ürün özellik değerleri genellikle sadece bir listeye ya da en kötü duruma bir karşılaştırma tablosuna dönüştürülür.

Bir JSONsütun türü kullanmak, veri kısıtlamalarını veritabanından zorlama yeteneğinizi alır ve bunu uygulama mantığınıza zorlar. Ayrıca, her marka için bir özellik tablosu kullanmak aşağıdaki dezavantajlara sahiptir:

  • Sonunda yüzlerce markanız (veya daha fazlasınız) varsa iyi ölçeklenmez.
  • Bir markanın izin verilen özelliklerini değiştirirseniz, bir marka alanı kontrol tablosuna yalnızca satır eklemek veya kaldırmak yerine bir tablo tanımını değiştirmeniz gerekir.
  • Markanın sadece küçük bir alt kümesi bilinen birçok potansiyel özelliği varsa, seyrek olarak doldurulmuş tablolarla sonuçlanabilirsiniz.

Markaya özgü özelliklere sahip bir ürün hakkında veri almak özellikle zor değildir. EAV modelini kullanarak dinamik bir SQL oluşturmak, kategori başına tablo modelinden daha kolaydır. Kategori başına tabloda JSON, özellik sütunu adlarının ne olduğunu öğrenmek için yansımaya (veya sizin ) ihtiyacınız vardır. Daha sonra bir where cümlesi için bir öğe listesi oluşturabilirsiniz. EAV modelinde, WHERE X AND Y AND Zolur INNER JOIN X INNER JOIN Y INNER JOIN Z, bu yüzden sorgu biraz daha karmaşıktır, ancak sorguyu oluşturmak için mantık hala tamamen tabloya dayalıdır ve uygun dizinleri oluşturduysanız yeterince ölçeklenebilir olacaktır.

EAV'ı genel bir yaklaşım olarak kullanmamak için birçok neden vardır. Bu nedenler bir ürün özellik kataloğu için geçerli değildir, bu nedenle bu özel uygulamada EAV'de yanlış bir şey yoktur.

Emin olmak gerekirse, bu karmaşık ve tartışmalı bir konu için kısa bir cevaptır. Daha önce benzer soruları cevapladım ve genel olarak EAV'dan kaçınma hakkında daha fazla ayrıntıya girdim. Örneğin:

Çoğunlukla iyi nedenlerden ötürü, EAV'ın son zamanlarda olduğundan daha az kullanıldığını söyleyebilirim. Ancak, bunun da iyi anlaşılmadığını düşünüyorum.


3

İşte benim sorunum: farklı giyim markaları farklı bilgiler gerektirir. Böyle bir problemle başa çıkmak için en iyi uygulama hangisidir?

JSON ve PostgreSQL kullanma

Sanırım bunu olması gerekenden daha zor hale getiriyorsun ve daha sonra ısırılırsın. Buna gerek yok Varlık-nitelik değeri modeli aslında EAV gerekmedikçe.

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
);

Bu şemada kesinlikle yanlış bir şey yok.

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 )
;

Şimdi basit bir birleştirme kullanarak sorgulayabilirsiniz

SELECT *
FROM brands
JOIN clothes
  USING (brand_id);

Ve herhangi bir JSON operatörü bir where yan tümcesinde çalışır.

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

Bir yan not olarak, URL'leri veritabanına koymayın. Zamanla değişirler. Onları alan bir işlev oluşturmanız yeterlidir.

generate_url_brand( brand_id );
generate_url_clothes( clothes_id );

ya da her neyse. PostgreSQL kullanıyorsanız hashids bile kullanabilirsiniz .

Ayrıca özel not, jsonb ikili (bu nedenle -'b ') olarak saklanır ve ayrıca endekslenebilir veya SARGable veya havalı çocuklar bu gün ne diyorsa:CREATE INDEX ON brands USING gin ( attributes );

Buradaki fark, sorgunun basitliğindedir.

Bana Brand2'nin tüm kıyafetlerini ver

SELECT * FROM clothes WHERE brand_id = 2;

Bana şu özelliğe sahip tüm Giysileri ver: Boyut

SELECT * FROM clothes WHERE attributes ? 'size';

Farklı bir tanesine ne dersin ..

Bana mevcut olan tüm kıyafetler için tüm kıyafetleri ve özellikleri ver.

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

Bu nedenle, doğru anlarsam, söylediklerinizin özü, markalar ve nitelikler arasında bir ilişki varsa (yani geçerli olup olmadığı) ise McNets'in çözümü tercih edilir (ancak sorgular daha pahalı / yavaş olur). Öte yandan, bu ilişki önemli / daha fazla "ad-hoc" değilse, çözümünüzü tercih edebilirsiniz. "Ben asla PostgreSQL ile asla kullanmak olmaz" dediğinde ne demek biraz daha açıklayabilir misiniz? Bu yoruma bir açıklama gelmedi. Tüm sorular için özür dilerim !! Şimdiye kadar
verdiğiniz

1
Açık bir ilişki var, tek soru onu yönetmek için ne kadar ihtiyacınız var. Özellikler , nitelikler veya benzerleri gibi belirsiz bir terim kullanıyorsam , genellikle ad hoc veya son derece yapılandırılmamış olduğunu söylemek istiyorum. Bunun için JSONB daha iyi çünkü daha basit. bu yazıyı
Evan Carroll

-1

Kolay bir çözüm, tüm olası özellikleri ana giysi tablosuna sütunlar olarak dahil etmek ve markaya özgü tüm sütunları geçersiz kılmaktır. Bu çözüm veritabanı normalleştirmesini bozar, ancak uygulanması çok kolaydır.


Sanırım .. Ne söylediğine dair bir fikrim var, ama daha fazla ayrıntı ve belki de bir örnek eklemek yararlı olabilir.
youngrrrr
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.