从单列引用多个表的最佳设计?

you*_*rrr 20 database-design subtypes

提议的模式

首先,这里是我建议的架构示例,供我在整个帖子中参考:

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

问题陈述

我有一个衣服表,其中包含名称、颜色、价格、品牌标识等列来描述特定服装项目的属性。

这是我的问题:不同品牌的服装需要不同的信息。处理此类问题的最佳做法是什么?

请注意,出于我的目的,有必要从服装条目开始查找特定于品牌的信息。这是因为我首先将衣服条目的信息显示给用户,然后我必须使用其特定于品牌的信息来购买该项目。总之,衣服(来自)和brand_x表之间必须存在方向关系。

提议的/当前的解决方案

为了应对这种情况,我想到了以下设计方案:

衣服表将具有品牌柱可具有的ID值范围为1至x,其中一个特定的ID对应于一个特定品牌的表。例如,id 值 1 将对应表brand_1(可能有一个url列),id 2 将对应于brand_2(可能有一个供应商列)等。

因此,要将特定的服装条目与其品牌特定信息相关联,我想应用程序级别的逻辑将如下所示:

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

其他评论和想法

我正在尝试在 BCNF 中规范化我的整个数据库,虽然这是我想出的,但产生的应用程序代码让我感到非常焦虑。除了在应用程序级别之外,没有办法强制执行关系,因此设计感觉非常hacky,并且我预计非常容易出错。

研究

在发帖之前,我一定要查看以前的条目。这是我设法找到的一个几乎相同问题的帖子。无论如何我发了这篇文章,因为似乎提供的唯一答案没有 SQL 或基于设计的解决方案(即它提到了 OOP、继承和接口)。

在数据库设计方面,我也是一个新手,所以我很感激任何见解。


Stack Overflow 上似乎有更多有用的回复:

我已经提到了那里的解决方案,并建议其他发现我的问题的人也这样做。

尽管提供了上述链接,但我仍在寻找此处的回复,并希望提供任何解决方案!

我正在使用 PostgreSQL。

McN*_*ets 9

我个人不喜欢为此使用多表模式。

  • 很难确保完整性。
  • 很难维护。
  • 过滤结果很困难。

我已经设置了一个 dbfiddle示例

我建议的表架构:

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

让我插入一些数据:

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

如果您需要获取公共属性:

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

或者您可以轻松获得品牌服装:

给我所有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
Run Code Online (Sandbox Code Playgroud)

但对我来说,这种模式的最好之处之一是您可以按属性进行过滤:

给我所有具有以下属性的衣服:尺寸

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

使用多表模式,​​无论前面的任何查询都需要处理无限数量的表,或者处理 XML 或 JSON 字段。

此架构的另一个选项是您可以定义模板,例如,您可以添加一个新表 BrandAttrTemplates。每次添加新记录时,您都可以使用触发器或 SP 为该分支生成一组预定义的属性。

对不起,我想扩展我的解释,我认为它比我的英语更清楚。

更新

无论哪个 RDBMS,我当前的答案都应该适用。根据您的评论,如果您需要过滤属性值,我建议进行一些小的更改。

就 MS-Sql 不允许数组而言,我已经设置了一个包含相同表架构的新示例,但将 AttrValue 更改为 ARRAY 字段类型。

事实上,使用 POSTGRES,您可以使用 GIN 索引来利用这个数组。

(让我说@EvanCarrol 对 Postgres 有很好的了解,肯定比我好。但让我补充一点。)

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

现在,您还可以使用单独的属性值进行查询,例如:

给我一份所有裤子的清单 尺码: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;
Run Code Online (Sandbox Code Playgroud)

这是结果:

clothes name | brand name | attribute | values 
------------- ------------ ----------  ---------------- 
Pants          Brand1       Size        32, 33, 34
Run Code Online (Sandbox Code Playgroud)