Pola Desain Daftar Atribut Produk

9

Saya sedang berupaya memperbarui basis data produk situs web kami. Ini dibangun di MySQL tetapi ini lebih merupakan pertanyaan pola desain database umum.

Saya berencana beralih ke pola Supertype / Subtype. Basis data kami saat ini / sebelumnya sebagian besar merupakan tabel tunggal yang memiliki data pada satu jenis produk. Kami sedang memperluas penawaran produk kami untuk memasukkan produk yang berbeda.

Draf desain baru ini seperti ini:

Product             product_[type]          product_attribute_[name]
----------------    ----------------        ----------------------------
part_number (PK)    part_number (FK)        attributeId (PK)
UPC                 specific_attr1 (FK)     attribute_name
price               specific_attr2 (FK)
...                 ...

Saya punya pertanyaan tentang tabel atribut produk. Idenya di sini adalah produk dapat memiliki daftar atribut yang diberikan seperti warna: merah, hijau, biru, atau bahan: plastik, kayu, krom, aluminium, dll.

Daftar ini akan disimpan dalam sebuah tabel dan kunci utama (PK) untuk item atribut itu akan digunakan dalam tabel produk tertentu sebagai kunci asing (FK).

(Buku Martin Fowler, Pola Arsitektur Aplikasi Perusahaan menyebutnya " Pemetaan Kunci Asing ")

Ini memungkinkan antarmuka situs web untuk menarik daftar atribut untuk tipe atribut yang diberikan dan meludahkannya dalam menu pilih tarik turun atau elemen UI lainnya. Daftar ini dapat dianggap sebagai daftar "nilai atribut".

Jumlah sambungan yang akhirnya terjadi ketika menarik produk tertentu tampak berlebihan bagi saya. Anda harus bergabung dengan setiap tabel atribut produk ke produk sehingga Anda bisa mendapatkan bidang atribut itu. Umumnya, bidang itu mungkin hanya berupa string (varchar) untuk namanya.

Pola desain ini akhirnya membuat sejumlah besar tabel dan Anda berakhir dengan tabel untuk setiap atribut. Satu ide untuk mengatasi hal ini adalah membuat tabel “grab bag” untuk semua atribut produk. Sesuatu seperti ini:

product_attribute
----------------
attributeId (PK) 
name
field_name

Dengan cara ini, meja Anda mungkin terlihat seperti ini:

1  red     color
2  blue    color
3  chrome  material
4  plastic material
5  yellow  color
6  x-large size

Ini bisa membantu mengurangi creep meja tetapi tidak mengurangi jumlah gabungan dan rasanya sedikit salah menggabungkan begitu banyak jenis yang berbeda menjadi satu tabel. Tetapi Anda akan bisa mendapatkan semua atribut "warna" yang tersedia dengan cukup mudah.

Namun, mungkin ada atribut yang memiliki bidang lebih dari sekadar "nama" seperti nilai RGB warna. Ini akan memerlukan atribut khusus untuk memiliki tabel lain atau memiliki bidang tunggal untuk pasangan nama: nilai (yang memiliki kelemahan sendiri).

Pola desain terakhir yang dapat saya pikirkan adalah menyimpan nilai atribut aktual dalam tabel produk tertentu dan tidak memiliki "tabel atribut" sama sekali. Sesuatu seperti ini:

Product             product_[type] 
----------------    ----------------
part_number (PK)    part_number (FK) 
UPC                 specific_attr1 
price               specific_attr2 
...                 ...

Alih-alih Kunci Asing ke tabel lain, itu akan berisi nilai aktual seperti:

part_number    color    material
-----------    -----    --------
1234           red      plastic

Ini akan menghilangkan gabungan dan mencegah merayap meja (mungkin?). Namun, ini mencegah memiliki "daftar resmi" atribut. Anda bisa mengembalikan semua nilai yang saat ini dimasukkan untuk bidang tertentu (yaitu: warna) tetapi ini juga menghilangkan gagasan memiliki "daftar nilai" yang diotorisasi untuk atribut yang diberikan.

Untuk memiliki daftar itu, Anda masih harus membuat tabel atribut "grab bag" atau memiliki beberapa tabel (table creep) untuk setiap atribut.

Ini menciptakan kelemahan yang lebih besar (dan mengapa saya tidak pernah menggunakan pendekatan ini) sekarang memiliki nama produk di beberapa lokasi.

Jika Anda memiliki nilai warna "merah" di "tabel atribut utama" dan juga menyimpannya di tabel "product_ [type]", pembaruan ke tabel "master" akan menyebabkan masalah integritas data potensial jika aplikasi tidak perbarui semua catatan dengan nilai lama di tabel "product_type" juga.

Jadi, setelah penjelasan saya yang panjang lebar dan analisis skenario ini, kesadaran saya adalah bahwa ini bukan skenario yang tidak biasa dan bahkan mungkin ada nama untuk jenis situasi seperti ini.

Apakah ada solusi yang diterima secara umum untuk tantangan desain ini? Apakah jumlah gabungan yang berpotensi besar dapat diterima jika tabelnya relatif kecil? Apakah menyimpan nama atribut, alih-alih atribut PK dapat diterima dalam beberapa situasi? Apakah ada solusi lain yang tidak saya pikirkan?

Beberapa catatan tentang basis data / aplikasi produk ini:

  • Produk tidak sering diperbarui / ditambahkan / dihapus
  • Atribut tidak sering diperbarui / ditambahkan / dihapus
  • Tabel ini paling sering ditanyakan untuk membaca / mengembalikan informasi
  • Caching sisi server diaktifkan untuk men-cache hasil dari kueri / hasil yang diberikan
  • Saya berencana memulai dengan hanya satu jenis produk dan memperluas / menambah yang lain dari waktu ke waktu dan akan berpotensi 10+ jenis yang berbeda
jmbertucci
sumber
1
Berapa banyak jenis produk yang akan Anda miliki?
dezso
1
Pertanyaan bagus. Ini akan mulai dari kecil 3-4 tetapi secara
potensial
Apa yang Anda maksud dengan "Daftar atribut resmi"?
NoChance
Maaf, seharusnya "nilai atribut". Gagasan bahwa Anda memiliki tabel yang mencantumkan semua nilai yang diizinkan untuk atribut. Yaitu. di sini adalah daftar 10 warna yang dapat jenis produk ini. 10 ini adalah nilai "otorisasi" yang bisa dipilih seseorang.
jmbertucci
Saya bertanya-tanya apakah akan baik-baik saja jika semua nilai atribut ini digabungkan ke tabel jenis produk jika saya akhirnya membuat "tampilan" di atasnya?
jmbertucci

Jawaban:

17

Saya pribadi akan menggunakan model yang mirip dengan yang berikut:

Tabel produk akan sangat mendasar, detail produk utama Anda:

create table product
(
  part_number int, (PK)
  name varchar(10),
  price int
);
insert into product values
(1, 'product1', 50),
(2, 'product2', 95.99);

Kedua tabel atribut untuk menyimpan masing-masing atribut yang berbeda.

create table attribute
(
  attributeid int, (PK)
  attribute_name varchar(10),
  attribute_value varchar(50)
);
insert into attribute values
(1, 'color', 'red'),
(2, 'color', 'blue'),
(3, 'material', 'chrome'),
(4, 'material', 'plastic'),
(5, 'color', 'yellow'),
(6, 'size', 'x-large');

Terakhir, buat tabel product_attribute sebagai tabel JOIN antara setiap produk dan atributnya yang terkait dengannya.

create table product_attribute
(
  part_number int, (FK)
  attributeid int  (FK) 
);
insert into product_attribute values
(1,  1),
(1,  3),
(2,  6),
(2,  2),
(2,  6);

Bergantung pada bagaimana Anda ingin menggunakan data yang Anda lihat pada dua gabungan:

select *
from product p
left join product_attribute t
  on p.part_number = t.part_number
left join attribute a
  on t.attributeid = a.attributeid;

Lihat SQL Fiddle dengan Demo . Ini mengembalikan data dalam format:

PART_NUMBER | NAME       | PRICE | ATTRIBUTEID | ATTRIBUTE_NAME | ATTRIBUTE_VALUE
___________________________________________________________________________
1           | product1   | 50    | 1           | color          | red
1           | product1   | 50    | 3           | material       | chrome
2           | product2   | 96    | 6           | size           | x-large
2           | product2   | 96    | 2           | color          | blue
2           | product2   | 96    | 6           | size           | x-large

Tetapi jika Anda ingin mengembalikan data dalam PIVOTformat di mana Anda memiliki satu baris dengan semua atribut sebagai kolom, Anda bisa menggunakan CASEpernyataan dengan agregat:

SELECT p.part_number,
  p.name,
  p.price,
  MAX(IF(a.ATTRIBUTE_NAME = 'color', a.ATTRIBUTE_VALUE, null)) as color,
  MAX(IF(a.ATTRIBUTE_NAME = 'material', a.ATTRIBUTE_VALUE, null)) as material,
  MAX(IF(a.ATTRIBUTE_NAME = 'size', a.ATTRIBUTE_VALUE, null)) as size
from product p
left join product_attribute t
  on p.part_number = t.part_number
left join attribute a
  on t.attributeid = a.attributeid
group by p.part_number, p.name, p.price;

Lihat SQL Fiddle dengan Demo . Data dikembalikan dalam format:

PART_NUMBER | NAME       | PRICE | COLOR | MATERIAL | SIZE
_________________________________________________________________
1           | product1   | 50    | red   | chrome   | null
2           | product2   | 96    | blue  | null     | x-large

Ketika Anda melihat data mungkin dalam format yang lebih baik untuk Anda, tetapi jika Anda memiliki jumlah atribut yang tidak diketahui, itu akan dengan mudah menjadi tidak dapat dipertahankan karena nama atribut pengodean keras, jadi di MySQL Anda dapat menggunakan pernyataan yang disiapkan untuk membuat pivot dinamis . Kode Anda adalah sebagai berikut (Lihat SQL Fiddle With Demo ):

SET @sql = NULL;
SELECT
  GROUP_CONCAT(DISTINCT
    CONCAT(
      'MAX(IF(a.attribute_name = ''',
      attribute_name,
      ''', a.attribute_value, NULL)) AS ',
      attribute_name
    )
  ) INTO @sql
FROM attribute;

SET @sql = CONCAT('SELECT p.part_number
                    , p.name
                    , ', @sql, ' 
                   from product p
                   left join product_attribute t
                     on p.part_number = t.part_number
                   left join attribute a
                     on t.attributeid = a.attributeid
                   GROUP BY p.part_number
                    , p.name');

PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

Ini menghasilkan hasil yang sama seperti versi kedua tanpa perlu melakukan hard-code apa pun. Meskipun ada banyak cara untuk memodelkan ini saya pikir desain basis data ini adalah yang paling fleksibel.

Taryn
sumber
+1 - Jawaban yang ditulis dengan fantastis. Saya masih meluangkan beberapa saat untuk membaca kembali dan mencerna jawaban ini sebelum menerimanya. Itu memang terlihat seperti solusi yang baik untuk pertanyaan saya tentang gabungan dan atribut produk dan bahkan berjalan di atas dan di luar dengan contoh pivot dan pernyataan yang disiapkan. Jadi, saya akan mulai dengan +1 untuk itu. =)
jmbertucci
@ jmbertucci Anda sepertinya khawatir tentang kueri tabel sehingga saya pikir saya akan memberikan Anda beberapa sampel. :)
Taryn
Memang. Saya akan "doh" yang saya tidak melihat melakukan cross table produk untuk atribut. Mungkin kasus pemikiran berlebihan terutama setelah membenamkan pola desain dan teori. Juga, pengalaman DBA saya adalah dasar dan melakukan lebih banyak dengan pernyataan yang dipersiapkan adalah sesuatu yang saya butuhkan, jadi penyertaan Anda sangat membantu. Dan jawaban ini telah membantu memecahkan "blok penulis" yang saya miliki sehingga saya dapat melanjutkan proyek ini, yang membuat hari saya menyenangkan. =)
jmbertucci
baik, satu pertanyaan ... apakah lambat? Saya merasa seolah
olah
@ ZenithS Anda harus mengujinya untuk melihat dan mungkin menambahkan indeks pada kolom yang Anda query. Saya tidak punya instance MySQL untuk melakukan pengujian.
Taryn
0

Saya akan memperluas jawaban Taryn dan memodifikasi tabel atribut untuk memiliki fk_attribute_type_id kolom yang akan bukannya ATTRIBUTE_NAME kolom dan poin untuk attribute_type tabel baru.

Jadi, Anda memiliki tipe atribut terstruktur dalam satu tabel dan Anda dapat mengubahnya kapan saja di satu tempat.

Menurut pendapat saya itu lebih baik untuk bekerja dengan "dial" semacam itu (tabel dengan tipe yang mungkin) daripada dengan tipe enum (seperti itu di kolom atribut_name (dan di atas itu sebenarnya bukan nama, tipe atributnya)).

Ales
sumber