具有多种产品类别的网站的数据库设计

Jan*_*ski 2 sql postgresql database-design

我是 SQL 新手。尝试尽可能多地学习,因此将小型网上商店作为我的培训目标。我正在努力解决数据库结构问题。我想要实现的是:

  • 带有 ID 和名称的类别(例如电视、洗衣机)
  • 字段(每个类别都有仅限于该类别的字段(例如电视 - 分辨率、HDR 等,洗衣机 - 容量、洗涤周期)
  • 产品(每个产品应具有通用字段(名称、品牌等)以及每个类别中不同的类别字段。

因此,系统方面的主要思想是创建一个类别,向其添加字段,并使用常规+类别字段将一些产品插入该类别。

我该如何实现这一目标?我尝试将所有这些与一对多关系联系起来,但这似乎没有像我预期的那样工作

小智 5

这是一种称为“实体属性值”的已知(反)模式(如果您想了解更多信息,可以在互联网上搜索该名称)。

现在(尤其是使用 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)
);
Run Code Online (Sandbox Code Playgroud)

现在,通过类别表中的“允许的属性”列表,我们可以编写一个触发器来验证它们。

首先,我创建一个小辅助函数,确保一个 JSON 值中的所有键都存在于另一个 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;
Run Code Online (Sandbox Code Playgroud)

然后在类别表的触发器中使用该函数:

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;
Run Code Online (Sandbox Code Playgroud)

现在让我们插入一些示例数据:

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');
Run Code Online (Sandbox Code Playgroud)

现在让我们插入类别信息:

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
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为所有属性都在类别中定义。如果我们尝试插入以下内容:

insert into product_category (product_id, category_id, attributes)
values
(3, 2, '{"usb_ports": 5}');
Run Code Online (Sandbox Code Playgroud)

然后触发器将抛出异常,阻止用户插入行。

这可以扩展到实际使用存储在allowed_attributes.

要根据属性查找产品,我们可以使用Postgres提供的JSON函数,例如所有具有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');
Run Code Online (Sandbox Code Playgroud)

查找包含多个属性的产品同样容易(对于“传统”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}');
Run Code Online (Sandbox Code Playgroud)

这可以非常有效地建立索引以使搜索更快。


我不完全确定您确实想要将属性存储在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)
);
Run Code Online (Sandbox Code Playgroud)

如果您需要类别特定的动态属性和产品特定的属性(无论类别如何),甚至可以两者结合。