3

I have the following structure of my database, implementing a simple EAV model (see pic):

My DB model

My product has a type, which through the junction table restricts prop_names, available for this product. And here everything is clear. BUT: Then I've added a prop_values table to keep the properties values for each product. It has reference to products through prod_sku and to prop_names through prop_id. And here the problem comes: One can add to any product any properties - even those, which are not allowed for this product type. Also, there can be duplications - two or more same properties for a single product.

Is there any way to restrict this on the database level?

After the @BillKarvin's answer, I've tried the below CREATE code, but failed with the 'Foreign key constraint is incorrectly formed' error when creating the last table (property_values).

I have found my error - I forgot to add a KEY to the products table. Below is the corrected (working) version of my code:

CREATE TABLE product_types (
  id INT PRIMARY KEY,
  product_type varchar(50) NOT NULL,
  block_css_id varchar(50) NOT NULL,
  block_description varchar(50) NOT NULL
);

CREATE TABLE products (
  sku varchar(50) PRIMARY KEY,
  name varchar(50) NOT NULL,
  price decimal(20,2) unsigned NOT NULL,
  id_product_type INT NOT NULL,
  FOREIGN KEY (id_product_type) REFERENCES product_types (id),
  KEY (sku, id_product_type)
);

CREATE TABLE property_names (
  id INT PRIMARY KEY,
  property_name varchar(50) NOT NULL,
  property_css_id varchar(50) NOT NULL,
  property_input_name varchar(50) NOT NULL
);

CREATE TABLE junction_ptype_propname (
  id_productt_type INT NOT NULL,
  id_property_name INT NOT NULL,
  PRIMARY KEY (id_productt_type, id_property_name),
  FOREIGN KEY (id_productt_type) REFERENCES product_types (id),
  FOREIGN KEY (id_property_name) REFERENCES property_names (id)
);

CREATE TABLE property_values (
  id INT NOT NULL PRIMARY KEY,
  product_sku varchar(50) NOT NULL,
  property_id INT NOT NULL,
  property_value decimal(20,2) NOT NULL DEFAULT 0.00,
  id_prod_type INT NOT NULL,
  UNIQUE KEY (product_sku, property_id),
  FOREIGN KEY (product_sku, id_prod_type) REFERENCES products (sku, id_product_type),
  FOREIGN KEY (property_id, id_prod_type) REFERENCES junction_ptype_propname (id_property_name, id_productt_type)
);
  • You database design is slightly inconsistent. Basically you have 'products', 'properties' and 'types', but that's not what I see in your database. You do have a table with 'products', which has a field 'name' but then you have a table called 'prop_names' which holds the properties, with a field called 'prop_name'. That's weird. I would call the latter table 'propeties'. I like unique field names, so I would have used 'product_name' and 'property_name'. Do not abbreviate unnecessarily. Your 'prop_values' table could be called 'products_properties' because it combines those two tables. – KIKO Software Apr 15 '22 at 10:30
  • The relation which is set by junction table is a pattern whereas the relation between the product and a value of its property is an entity built on the base of this pattern. I.e. there is no relation between the pattern and the entity which was used for this pattern creation. – Akina Apr 15 '22 at 10:36
  • @KIKOSoftware - I agree, maybe the naming could be better, but this doesn't change the topic issue – Denys Galanenko Apr 15 '22 at 11:14
  • @Akina - Yes - I realize there is no such relationship and actually this is my question - how to create it... – Denys Galanenko Apr 15 '22 at 11:17

2 Answers2

2

I would design this in the following way:

enter image description here

There are few important differences from your model:

  • prop_values has a unique key on (prod_sku, prop_id) so you can only have one instance of a given property per product sku.

  • prop_values has a prod_type column, and this references products, using both columns (sku, prod_type).

  • prop_values has a compound foreign key to junction_ptype_propname instead of prop_name.

Now the prod_type in prop_values can have a single value per row, and it must reference the correct product type in both the products table and the junction_ptype_propname table. So it is constrained to be a valid property for the given product, and a valid property for the product type. You therefore cannot add a property to a product that isn't legitimate for that product's type.

Here's the DDL:

create table prod_types (
  id int primary key,
  type_name varchar(30) not null
);

create table products (
  sku varchar(30) primary key,
  name varchar(30) not null,
  type int not null,
  foreign key (type) references prod_types(id),
  key(sku, type)
);

create table prop_names (
  id int primary key,
  prop_name varchar(30) not null
);

create table junction_ptype_propname (
  id_prop_name int not null,
  id_prod_type int not null,
  primary key (id_prop_name, id_prod_type),
  foreign key (id_prod_type) references prod_types(id),
  foreign key (id_prop_name) references prop_names(id)
);

create table prop_values (
  id int primary key,
  prod_sku varchar(30) not null,
  prod_type int not null,
  prop_id int not null,
  prop_value decimal not null,
  unique key (prod_sku, prop_id),
  foreign key (prod_sku, prod_type) references products(sku, type),
  foreign key (prop_id, prod_type) references junction_ptype_propname(id_prop_name, id_prod_type)
);

This question is fun because it's a case of using Fifth Normal Form. Many articles on database design claim that normal forms past the Third Normal Form aren't used. But your model disproves that.

Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
  • Thank you very much for your answer. I'm trying to implement it for already a couple days (my DB already changed from the day I asked me initial question). But I fail. Please review my CREATE code - I've temporarily added it to a question body, since it is to long for a comment. It produces an error 'Foreign key constraint is incorrectly formed'. – Denys Galanenko Apr 25 '22 at 14:02
  • 1
    Sorry - I have already found my error - I forgot to create a KEY in the `products` table. – Denys Galanenko Apr 25 '22 at 15:59
  • For the future, you might like this checklist of foreign key errors, which I contributed to: https://stackoverflow.com/a/4673775/20860 – Bill Karwin Apr 25 '22 at 16:13
0

Also, there can be duplications - two or more same properties for a single product.

Use UNIQUE to prevent from duplications

w3schools.com - UNIQUE

Quiet Molly
  • 102
  • 1
  • 7
  • This will not work - there can be many products of one type In the `prop_values` table and they of course will have the same `prop_id` values, so UNIQUE is not a case. There should be some query-like restriction - that items with the same `prod_sku` can't have the same `prop_id`... – Denys Galanenko Apr 15 '22 at 11:12
  • @DenysGalanenko: I think this can work if you restrict the uniqueness to the `prod_sku` and `prop_id` columns. I would have upvoted this question if it had a better explanation. – KIKO Software Apr 15 '22 at 11:17
  • @KIKOSoftware - I feel like I totally misunderstand something here: the definition of `UNIQUE` is: "The UNIQUE constraint ensures that all values in a column are different." So how can I put `UNIQUE` to the `prod_sku` and `prop_id` if I'm going to store here: 1) products with more than one property - which needs repetition of the `prod_sku` 2) different products having the same property - which needs repetition of the `prop_id` What I need, is to ensure that A PAIR OF `prod_sku` and `prop_id` is to be unique. – Denys Galanenko Apr 15 '22 at 11:27
  • 1
    @DenysGalanenko: Follow the link and see the second example. You can put a unique constraint on multiple columns. In your case that would be: `CONSTRAINT unique_property_value UNIQUE (product_sku, property_id)`. This means that _the combination_ of `product_sku` and `property_id` has to be unique. – KIKO Software Apr 15 '22 at 11:58
  • @KIKOSoftware - Thank you very much - this really works and your description is better than at W3Schools example! What about my main question - about restriction of adding wrong attributes? – Denys Galanenko Apr 15 '22 at 19:18