Desain terbaik untuk referensi beberapa tabel dari satu kolom?

18

Skema yang diajukan

Pertama dan terpenting, berikut adalah contoh skema yang saya usulkan untuk referensi di seluruh posting saya:

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

Pernyataan masalah

Saya memiliki meja pakaian yang memiliki kolom seperti nama, warna, harga, brandid dan sebagainya untuk menggambarkan atribut untuk item pakaian tertentu.

Inilah masalah saya: berbeda merek s pakaian memerlukan informasi yang berbeda. Apa praktik terbaik untuk menangani masalah seperti ini?

Perhatikan bahwa untuk tujuan saya, perlu mencari informasi khusus merek mulai dari entri pakaian . Ini karena saya pertama kali menampilkan informasi dari entri pakaian kepada pengguna, setelah itu saya harus menggunakan informasi khusus mereknya untuk membeli item. Singkatnya, harus ada hubungan terarah antara pakaian (dari) dan tabel brand_x .

Solusi yang diajukan / saat ini

Untuk mengatasinya, saya telah memikirkan skema desain berikut:

The pakaian meja akan memiliki merek kolom yang mungkin memiliki nilai id mulai dari 1 sampai x, di mana tertentu berkoresponden id ke meja merek tertentu. Misalnya, nilai id 1 akan sesuai dengan tabel brand_1 (yang mungkin memiliki kolom url ), id 2 akan sesuai dengan brand_2 (yang mungkin memiliki kolom pemasok ), dll.

Jadi untuk menghubungkan entri pakaian tertentu dengan informasi spesifik mereknya, saya membayangkan logika pada tingkat aplikasi akan terlihat seperti ini:

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.

Komentar & pemikiran lain

Saya mencoba untuk menormalkan seluruh database saya di BCNF, dan meskipun ini yang saya buat, kode aplikasi yang dihasilkan membuat saya merasa sangat cemas. Tidak ada cara untuk menegakkan hubungan kecuali pada tingkat aplikasi, dan dengan demikian desainnya terasa sangat berantakan dan, saya mengantisipasi, sangat rawan kesalahan.

Penelitian

Saya memastikan untuk melihat entri sebelumnya sebelum membuat posting. Berikut adalah pos dengan masalah yang hampir identik yang berhasil saya temukan. Saya tetap membuat posting ini karena sepertinya satu-satunya jawaban yang diberikan tidak memiliki solusi berbasis desain atau SQL (yaitu menyebutkan OOP, warisan, dan antarmuka).

Saya juga seorang pemula dalam hal desain database, jadi saya sangat menghargai wawasan apa pun.


Tampaknya ada respons yang lebih membantu pada Stack Overflow:

Saya telah merujuk solusi di sana dan menyarankan orang lain menemukan pertanyaan saya juga.

Terlepas dari tautan yang disediakan di atas, saya masih mencari tanggapan di sini dan akan menghargai setiap solusi yang disediakan!

Saya menggunakan PostgreSQL.

youngrrrr
sumber

Jawaban:

7

Saya pribadi tidak suka menggunakan skema multi-tabel untuk tujuan ini.

  • Sulit untuk memastikan integritas.
  • Sulit dipertahankan.
  • Sulit untuk menyaring hasil.

Saya telah menetapkan sampel dbfiddle .

Skema tabel yang saya usulkan:

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

Biarkan saya memasukkan beberapa data:

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

Jika Anda perlu mengambil atribut umum:

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

Atau Anda dapat dengan mudah mendapatkan Pakaian dengan Merek:

Beri aku semua pakaian 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

Tetapi bagi saya, salah satu yang terbaik dari skema ini adalah Anda dapat memfilter berdasarkan Attibutes:

Beri aku semua Pakaian yang memiliki atribut: Ukuran

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

Menggunakan skema multi-tabel apa pun dari permintaan sebelumnya akan perlu berurusan dengan jumlah tabel yang tidak terbatas, atau dengan bidang XML atau JSON.

Opsi lain dengan skema ini, adalah bahwa Anda dapat menentukan template, misalnya, Anda bisa menambahkan tabel baru BrandAttrTemplates. Setiap kali Anda menambahkan catatan baru Anda bisa menggunakan pemicu atau SP untuk menghasilkan sekumpulan atribut yang telah ditentukan untuk Cabang ini.

Maaf, saya ingin memperluas penjelasan saya dengan saya pikir itu lebih jelas daripada bahasa Inggris saya.

Memperbarui

Jawaban saya saat ini harus bekerja pada RDBMS apa pun. Menurut komentar Anda, jika Anda perlu memfilter nilai atribut saya sarankan perubahan kecil.

Sejauh MS-Sql tidak mengizinkan array, saya telah menyiapkan sampel baru mempertahankan skema tabel yang sama, tetapi mengubah AttrValue menjadi tipe bidang ARRAY.

Bahkan, menggunakan POSTGRES, Anda dapat memanfaatkan array ini menggunakan indeks GIN.

(Izinkan saya mengatakan bahwa @EvanCarrol memiliki pengetahuan yang baik tentang Postgres, tentunya lebih baik dari saya. Tetapi izinkan saya menambahkan bagian saya.)

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

Sekarang, Anda juga dapat melakukan kueri menggunakan nilai atribut individual seperti:

Beri saya daftar semua celana Ukuran: 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;

Ini hasilnya:

clothes name | brand name | attribute | values 
------------- ------------ ----------  ---------------- 
Pants          Brand1       Size        32, 33, 34
McNets
sumber
Saya sangat menyukai penjelasan ini, tetapi sepertinya kami hanya memperdagangkan skema multi-tabel untuk memiliki beberapa CSV dalam satu kolom - jika itu masuk akal. Di sisi lain, saya merasa saya menyukai pendekatan ini lebih baik karena tidak memerlukan perubahan pada skema, tapi sekali lagi rasanya seperti kami mendorong masalah di tempat lain (yaitu dengan memiliki kolom panjang variabel). Ini bisa menjadi masalah; bagaimana jika saya ingin meminta celana ukuran 3 di DB? Mungkin tidak ada solusi yang bagus dan bersih untuk masalah seperti ini. Apakah ada nama untuk konsep ini sehingga saya bisa melihat lebih dalam?
youngrrrr
Sebenarnya ... untuk menjawab masalah yang saya ajukan, mungkin jawabannya dapat dipinjam dari solusi @ EvanCarroll: yaitu, dengan menggunakan tipe jsonb alih-alih hanya TEKS / STRING dalam format CSV. Tetapi sekali lagi - jika ada nama untuk konsep ini, tolong beri tahu saya!
youngrrrr
1
Ini adalah jenis solusi Nilai Atribut Entitas. Ini bukan kompromi yang buruk antara kinerja dan desain yang baik. Namun, ini merupakan tradeoff. Anda menukar beberapa kinerja untuk desain yang lebih bersih, tidak dikotori dengan tabel "Brand_X" yang tak ada habisnya. Penalti kinerja, pergi dari arah yang paling umum Anda nyatakan harus minimal. Melangkah ke arah lain akan lebih menyakitkan, tetapi itulah komprominya. en.wikipedia.org/wiki/…
Jonathan Fite
4

Apa yang Anda gambarkan adalah, setidaknya sebagian, katalog produk. Anda memiliki beberapa atribut yang umum untuk semua produk. Ini termasuk dalam tabel yang dinormalisasi dengan baik.

Di luar itu, Anda memiliki serangkaian atribut yang spesifik merek (dan saya harapkan bisa spesifik produk). Apa yang perlu dilakukan sistem Anda dengan atribut spesifik ini? Apakah Anda memiliki logika bisnis yang bergantung pada skema atribut ini atau Anda hanya mencantumkannya dalam serangkaian pasangan "label": "nilai"?

Jawaban lain menyarankan menggunakan apa yang pada dasarnya pendekatan CSV (apakah ini JSONatau ARRAYatau sebaliknya) - ini pendekatan mengorbankan skema relasional biasa menangani dengan memindahkan skema dari metadata dan ke dalam data itu sendiri.

Ada pola desain portabel untuk ini yang sangat cocok dengan basis data relasional. Itu adalah EAV (entitas-atribut-nilai). Saya yakin Anda telah membaca di banyak tempat "EAV is Evil" (dan memang demikian). Namun, ada satu aplikasi khusus di mana masalah dengan EAV tidak penting, dan itu adalah katalog atribut produk.

Semua argumen biasa terhadap EAV tidak berlaku untuk katalog fitur produk, karena nilai-nilai fitur produk umumnya hanya dimuntahkan ke dalam daftar atau kasus terburuk ke dalam tabel perbandingan.

Menggunakan JSONtipe kolom membutuhkan kemampuan Anda untuk menegakkan batasan data apa pun dari database dan memaksanya ke dalam logika aplikasi Anda. Juga, menggunakan satu tabel atribut untuk setiap merek memiliki kelemahan berikut:

  • Ini tidak dapat diukur dengan baik jika Anda akhirnya memiliki ratusan merek (atau lebih).
  • Jika Anda mengubah atribut yang dibolehkan pada merek Anda harus mengubah definisi tabel, bukan hanya menambahkan atau menghapus baris dalam tabel kontrol bidang merek.
  • Anda mungkin masih berakhir dengan tabel yang jarang penduduknya jika merek memiliki banyak fitur potensial, hanya sebagian kecil yang diketahui.

Tidak terlalu sulit untuk mengambil data tentang suatu produk dengan fitur-fitur khusus merek. Mungkin lebih mudah untuk membuat SQL dinamis menggunakan model EAV daripada menggunakan model tabel-per-kategori. Dalam tabel-per-kategori, Anda perlu refleksi (atau Anda JSON) untuk mencari tahu apa nama kolom fitur. Kemudian Anda bisa membuat daftar item untuk klausa tempat. Dalam model EAV, WHERE X AND Y AND Zmenjadi INNER JOIN X INNER JOIN Y INNER JOIN Z, jadi kueri sedikit lebih rumit, tetapi logika untuk membangun kueri masih sepenuhnya didorong oleh tabel dan itu akan lebih dari cukup terukur jika Anda memiliki indeks yang tepat dibangun.

Ada banyak alasan untuk tidak menggunakan EAV sebagai pendekatan umum. Alasan-alasan itu tidak berlaku untuk katalog fitur produk sehingga tidak ada yang salah dengan EAV dalam aplikasi spesifik ini.

Yang pasti, ini adalah jawaban singkat untuk topik yang kompleks dan kontroversial. Saya telah menjawab pertanyaan serupa sebelumnya dan membahas lebih detail tentang penolakan umum terhadap EAV. Sebagai contoh:

Saya akan mengatakan EAV digunakan lebih jarang akhir-akhir ini daripada dulu, karena sebagian besar alasan bagus. Namun, saya pikir itu juga tidak dipahami dengan baik.

Joel Brown
sumber
3

Inilah masalah saya: merek pakaian yang berbeda membutuhkan informasi yang berbeda. Apa praktik terbaik untuk menangani masalah seperti ini?

Menggunakan JSON dan PostgreSQL

Saya pikir Anda membuat ini lebih sulit dari yang seharusnya dan Anda akan digigitnya nanti. Anda tidak perlu model nilai Entitas – atribut-nilai kecuali Anda benar-benar membutuhkan 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
);

Sama sekali tidak ada yang salah dengan skema ini.

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

Sekarang Anda dapat menanyakannya menggunakan gabungan sederhana

SELECT *
FROM brands
JOIN clothes
  USING (brand_id);

Dan salah satu operator JSON bekerja di mana klausa.

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

Sebagai catatan tambahan, jangan taruh url di database. Mereka berubah seiring waktu. Cukup buat fungsi yang membawanya.

generate_url_brand( brand_id );
generate_url_clothes( clothes_id );

atau terserah. Jika Anda menggunakan PostgreSQL Anda bahkan dapat menggunakan hashids .

Juga dari catatan khusus, jsonbdisimpan sebagai biner (dengan demikian -'b ') dan juga dapat diindeks, atau SARGable atau apa pun yang disebut anak-anak keren hari ini:CREATE INDEX ON brands USING gin ( attributes );

Perbedaannya di sini adalah kesederhanaan kueri ..

Beri aku semua pakaian Brand2

SELECT * FROM clothes WHERE brand_id = 2;

Beri aku semua Pakaian yang memiliki atribut: Ukuran

SELECT * FROM clothes WHERE attributes ? 'size';

Bagaimana dengan yang berbeda ..

Berikan saya semua pakaian dan atribut untuk semua pakaian yang tersedia dalam ukuran besar.

SELECT * FROM clothes WHERE attributes->>'size' = 'large';
Evan Carroll
sumber
Jadi, jika saya mengerti dengan benar, inti dari apa yang Anda katakan adalah jika ada hubungan antara merek dan atribut (yaitu apakah itu valid atau tidak) maka solusi McNets akan lebih disukai (tetapi pertanyaannya akan lebih mahal / lebih lambat). Di sisi lain, jika hubungan ini tidak penting / lebih "ad-hoc", maka orang mungkin lebih suka solusi Anda. Bisakah Anda menjelaskan sedikit lebih banyak dengan apa yang Anda maksud ketika Anda mengatakan "saya tidak akan pernah menggunakannya dengan PostgreSQL?" Tampaknya tidak ada penjelasan untuk komentar itu. Maaf untuk semua pertanyaan !! Saya sangat menghargai balasan Anda sejauh ini :)
youngrrrr
1
Jelas ada hubungan, satu-satunya pertanyaan adalah berapa banyak yang Anda butuhkan untuk mengelolanya. Jika saya menggunakan istilah yang tidak jelas seperti properti , atribut atau sejenisnya, saya biasanya bermaksud mengatakan bahwa itu cukup banyak ad-hoc atau sangat tidak terstruktur. Untuk itu, JSONB lebih baik karena lebih sederhana. Anda dapat menemukan posting ini informatif coussej.github.io/2016/01/14/...
Evan Carroll
-1

Salah satu solusi mudah adalah dengan memasukkan semua atribut yang mungkin sebagai kolom pada tabel pakaian utama, dan membuat semua kolom khusus merek dapat dibatalkan. Solusi ini memecah normalisasi database, tetapi sangat mudah diimplementasikan.

Matthew Sontum
sumber
Saya pikir .. Saya punya ide tentang apa yang Anda katakan, tetapi mungkin bermanfaat untuk memasukkan lebih detail dan mungkin contoh juga.
youngrrrr