我是SQL的新手。尝试学习尽可能多的东西,因此将小型网上商店作为我的培训目标。 我正在努力应对数据库结构。 我想实现的是:
因此,系统方面的主要思想是创建一个类别,向其添加字段,然后使用常规+类别字段将一些产品插入该类别。
我该如何实现?我试图将所有这些与一对多关系联系起来,但这似乎并没有按我预期的那样工作
答案 0 :(得分:2)
这是一个已知的(反)模式,称为“实体属性值”(如果要了解更多信息,可以在Internet上搜索该名称)。
现在(尤其是对于Postgres),我会去一个JSONB
列,该列存储每个产品的类别特定属性,而不是附加的fields
表。
您甚至可以根据product
表中的元信息来验证category
表中的动态属性。
是这样的:
create table category
(
id integer primary key,
name varchar(50) not null,
allowed_attributes jsonb not null
);
create table product
(
id integer primary key,
name varchar(100) not null,
brand varchar(100) not null, -- that should probably be a foreign key
... other common columns ...
);
create table product_category
(
product_id integer not null references product,
category_id integer not null references category,
attributes jsonb not null, -- category specific attributes
primary key (product_id, category_id)
);
现在,有了类别表中的“允许的属性”列表,我们可以编写一个触发器来验证它们。
首先,我创建一个辅助函数,以确保一个JSON值中的所有键都存在于另一个中:
create function validate_attributes(p_allowed jsonb, p_to_check jsonb)
returns boolean
as
$$
select p_allowed ?& (select array_agg(k) from jsonb_object_keys(p_to_check) as t(k));
$$
language sql;
此函数随后在类别表的触发器中使用:
create function validate_category_trg()
returns trigger
as
$$
declare
l_allowed jsonb;
l_valid boolean;
begin
select allowed_attributes
into l_allowed
from category
where id = new.category_id;
l_valid := validate_attributes(l_allowed, new.attributes);
if l_valid = false then
raise 'some attributes are not allowed for that category';
end if;
return new;
end;
$$
language plpgsql;
现在让我们插入一些示例数据:
insert into category (id, name, allowed_attributes)
values
(1, 'TV Set', '{"display_size": "number", "color": "string"}'::jsonb),
(2, 'Laptop', '{"ram_gb": "number", "display_size": "number"}');
insert into product (id, name)
values
(1, 'Big TV'),
(2, 'Small TV'),
(3, 'High-End Laptop');
现在我们插入类别信息:
insert into product_category (product_id, category_id, attributes)
values
(1, 1, '{"display_size": 60}'), -- Big TV
(2, 1, '{"display_size": 32}'), -- Small TV
(3, 2, '{"ram_gb": 128}'); -- Laptop
这是有效的,因为所有属性都在类别中定义。如果我们尝试插入以下内容:
insert into product_category (product_id, category_id, attributes)
values
(3, 2, '{"usb_ports": 5}');
然后,触发器将引发异常,从而阻止用户插入行。
可以扩展为实际使用存储在allowed_attributes
中的数据类型信息。
要根据属性查找产品,我们可以使用Postgres提供的JSON functions,例如所有具有display_size的产品:
select p.*
from product p
where exists (select *
from product_category pc
where pc.product_id = p.id
and pc.attributes ? 'display_size');
查找包含多个属性的产品同样简单(而使用“传统” EAV模型则要复杂得多)。
以下查询仅查找具有属性display_size
和 ram_gb
select p.*
from product p
where exists (select *
from product_category pc
where pc.product_id = p.id
and pc.attributes ?& '{display_size, ram_gb}');
此索引可以非常有效地索引,以加快搜索速度。
我不确定您是否确实希望将属性存储在product_category
表中。也许应该将它们直接存储在product
表中-但这取决于您的要求和管理方式。
使用上述方法,您可以具有“计算机硬件”类别,该类别将存储诸如CPU数量,RAM和时钟速度之类的信息。可以使用该类别(及其属性),例如同时使用智能手机和笔记本电脑。
但是,如果要这样做,您将需要在product_category
中多行描述一个产品。
最常见的方法可能是将属性直接存储在产品上,并跳过所有动态JSONB验证。
是这样的:
create table category
(
id integer primary key,
name varchar(50) not null
);
create table product
(
id integer primary key,
name varchar(100) not null,
brand varchar(100) not null, -- that should probably be a foreign key
attributes jsonb not null,
... other common columns ...
);
create table product_category
(
product_id integer not null references product,
category_id integer not null references category,
primary key (product_id, category_id)
);
或者,如果您需要类别特定的动态属性和产品特定属性(无论类别如何),甚至可以将两者组合使用。
答案 1 :(得分:0)
您可以创建联结表和外键来表示表之间的关系。
类别表
id |名称
字段表
id |名称
类别字段表
id | category_id | field_id
品牌 id |名称
产品表
id | category_id | brand_id |名称
产品功能
id | product_id | field_id |值
对于产品表格,您可能需要考虑为品牌使用单独的表格,并在brand_id
表格中包含products
列而不是名称,以避免重复。
category_fields
表将存储类别的id
和相关字段的id
,表中的每一行代表该类别的不同字段。
然后,表product_features
将存储特征,这些特征取决于分配给产品类别的字段。
答案 2 :(得分:0)
使用基于Dataphor的伪代码,内联引用(外键),数据类型和无关详细信息:
create table Category {
CategoryId,
CategoryName,
key { CategoryId },
key { CategoryName } /* Don't want categories that differ only by surrogate id */
};
/* Allowed fields */
create table CategoryField {
CategoryId,
FieldName,
key { CategoryId, FieldName },
reference CategoryField_Category
{ CategoryId } references Category { CategoryId }
};
create table Product {
ProductId,
ProductName,
ProductBrand,
CategoryId,
key { ProductId }, /* Probably other attributes, keys and references as well */
reference Product_Category
{ CategoryId } references Category { CategoryId }
};
create table ProductFieldValue {
ProductId,
CategoryId, /* Violates BCNF, but is controlled by foreign superkey */
FieldName,
FieldValue,
key { ProductId, FieldName },
reference PFV_Product
{ ProductId, CategoryId } references Product { ProductId, CategoryId },
reference PFV_CategoryField
{ CategoryId, FieldName } references CategoryField { CategoryId, FieldName }
};
重叠的外键(我更喜欢术语“引用”,特别是因为其中之一实际上是一个适当的外键),请确保每个产品只能具有根据CategoryField
表中的行的字段值。
此模型中有一些冗余-ProductFieldValue违反了Boyce-Codd正常形式(也为2NF,但没关系)-因此,您必须自己决定简单完整性控制的好处是否胜过该缺点。但是请注意,冗余是受控制的。不会有任何不一致的地方。
此模型假定所有字段值都具有相同的数据类型,例如一个字符串。如果您还希望对此进行限制(例如,某些字段只能包含数字值;某些字段会被枚举等等),事情会变得更加复杂。