Har*_*ley 7 postgresql normalization database-design
如果之前有人问过并回答过这个问题,请原谅我。
我正在粗略地制定一个库存管理系统的架构,将在 PostgreSQL 中实现。我们所有的产品和服务都有一个 SKU。我们的大部分产品来自制造商或分销商,并带有单独的“项目编号”(无论是分销商的目录号、制造商的型号,等等)。然而,并非所有人都有这样的数字。我们有内部制造的小组件,通常没有项目编号。我们的服务没有项目编号。由于这些原因,以下 CREATE TABLE 对我来说很有意义。
场景A:
CREATE TABLE product (
sku text PRIMARY KEY,
name text UNIQUE NOT NULL, -- alternate key
price numeric NOT NULL CHECK (price > 0),
quantity numeric NOT NULL CHECK (quantity > 0),
item_number text -- hmmm...
);
Run Code Online (Sandbox Code Playgroud)
但是,我对此有两个问题。
有时(可能有 3% 到 5% 的时间),item_number 实际上等于SKU。也就是说,我的一个供应商特别在他们的产品上贴上了我怀疑不是全球唯一的 SKU,按照他们的项目编号设计。
无论是否等于 SKU,item_number(如果存在)在几乎所有情况下都足以唯一标识我的小商店域中的产品。
我担心将其标准化为 3NF。如果 item_number 有时为 null,则显然不能将其声明为备用键。但是,从语义上讲,它是一个唯一标识符,在我能想到的每种情况下都存在。那么我上面的表格,其中每个属性在功能上都依赖于非主要属性 item_number每当 item_number 存在时,是否标准化?我想不,但我当然不是专家。我想过做以下事情:
情景B
CREATE TABLE product (
sku text PRIMARY KEY REFERENCES product_item_number (sku),
name text UNIQUE NOT NULL, -- alternate key
price numeric NOT NULL CHECK (price > 0),
quantity numeric NOT NULL CHECK (quantity > 0),
);
CREATE TABLE product_item_number (
sku text PRIMARY KEY,
item_number text
);
Run Code Online (Sandbox Code Playgroud)
由于我确实不需要保留功能依赖项 item_number -> 价格、item_number -> 数量等,因此场景 B 对我来说似乎有点合理。我不会有决定任何其他非主要属性的非主要属性。
我的最终想法是在 item_number 不存在的所有情况下简单地使用 sku 作为项目编号,但我想知道这是否是一个好的做法。
情景C
CREATE TABLE product (
sku text PRIMARY KEY,
name text UNIQUE NOT NULL, -- alternate key
price numeric NOT NULL CHECK (price > 0),
quantity numeric NOT NULL CHECK (quantity > 0),
item_number text UNIQUE NOT NULL -- alternate key???
);
Run Code Online (Sandbox Code Playgroud)
我对场景 C 的担忧是,在某些情况下,供应商可能会回收具有不同 sku 的目录号(也许?),或者两个制造商都生产“d57-red”或类似的情况。在这种情况下,我想我必须以编程方式在违规 item_numbers 前面加上制造商名称或类似的前缀。
当然,也许我想多了。
谢谢阅读。根据 MDCCL 的评论,有几点说明:
我认为您已经通过发现从概念上讲ItemNumber是一个可选属性而找到了答案;也就是说,当你确定它不适用于每个并通过逻辑电平rows- -represented的发生的每一件产品的实体类型。因此,正如您正确指出的那样,该item_number
列不应在表中声明为 ALTERNATE KEY(为简洁起见为 AK)product
。
在这方面,您的场景 B 是非常合理的,正如以下概念级公式所示:
换句话说,Product和ItemNumber之间存在一比零或一(1:0/1) 的基数比。
然后,是的,您应该引入一个新表来处理可选列,我同意这product_item_number
是一个非常具有描述性的名称。该表应该sku
作为其 PRIMARY KEY (PK)进行约束,以确保不会sku
像您一样将具有相同值的行插入其中。
值得一提的是,它也product_item_number.sku
应该像引用product.sku
.
下面是一个示例 SQL-DDL 逻辑级设计,它说明了之前的建议:
-- You should determine which are the most fitting
-- data types and sizes for all your table columns
-- depending on your business context characteristics.
-- Also, you should make accurate tests to define
-- the most convenient INDEXing strategies.
CREATE TABLE product (
sku TEXT NOT NULL,
name TEXT NOT NULL,
price NUMERIC NOT NULL,
quantity NUMERIC NOT NULL,
--
CONSTRAINT product_PK PRIMARY KEY (sku),
CONSTRAINT product_AK UNIQUE (name), -- AK.
CONSTRAINT valid_price_CK CHECK (price > 0),
CONSTRAINT valid_quantity_CK CHECK (quantity > 0)
);
CREATE TABLE product_item_number (
sku TEXT NOT NULL, -- To be constrained as PK and FK to ensure the 1:0/1 correspondence ratio between the relevant rows.
item_number TEXT NOT NULL,
--
CONSTRAINT product_item_number_PK PRIMARY KEY (sku),
CONSTRAINT product_item_number_AK UNIQUE (item_number), -- In this context, ‘item_number’ is an AK.
CONSTRAINT product_item_number_TO_product_FK FOREIGN KEY (sku)
REFERENCES product (sku)
);
Run Code Online (Sandbox Code Playgroud)
在此 db<>fiddle 中在 PostgreSQL 11 上进行了测试。
此外,还有另一个概念公式可以指导塑造上述数据库设计:
因此,该item_number
列实际上应该声明为 AK 的位置就在product_item_number
表中,因为只有在提供相关值时,所述列才需要唯一性保护,因此必须相应地配置 UNIQUE 和 NOT NULL 约束。
缺失值和“封闭世界的解释”
前面描述的逻辑 SQL-DDL 排列是处理缺失值的关系方法的一个示例,尽管它不是最流行的——或通常的——。这种方法与“封闭世界解释”——或“假设”——有关。采取这一立场,(a) 数据库中记录的信息始终被视为真实,并且 (b) 未记录在其中的信息始终被视为虚假。通过这种方式,可以完全保留已知的事实。
在当前的业务场景中,当用户提供product
表中包含的所有数据点时,您必须插入相应的行,并且当且仅当用户提供item_number
数据可用时,您还必须插入product_item_number
对应的行。如果该item_number
值未知或根本不适用,则不要插入product_item_number
一行,仅此而已。
使用这种方法,您可以避免在基表中保留 NULL 标记/标记——以及我将在下一节详细介绍的逻辑级后果——但您应该意识到这是数据库管理领域中的一个“有争议”的话题。在这一点上,您可能会发现 Stack Overflow 问题的答案很有价值:
流行的做法
然而,我猜想,流行的(或常见的)程序是有一个product
包含item_number
列的表,而该列将被设置为 NULLable,同时使用 UNIQUE 约束进行定义。在我看来,这种方法会使您的数据库和适用的数据操作操作不那么优雅(例如,在这个出色的 Stack Overflow 答案中显示),但这是一种可能性。
请参阅举例说明此操作过程的连续 DDL 语句:
CREATE TABLE product (
sku TEXT NOT NULL,
name TEXT NOT NULL,
price NUMERIC NOT NULL,
quantity NUMERIC NOT NULL,
item_number TEXT NULL, -- Accepting NULL marks.
--
CONSTRAINT product_PK PRIMARY KEY (sku),
CONSTRAINT product_AK1 UNIQUE (name), -- AK.
CONSTRAINT product_AK2 UNIQUE (item_number), -- Being ‘NULLable’, this is not an AK.
CONSTRAINT valid_price_CK CHECK (price > 0),
CONSTRAINT valid_quantity_CK CHECK (quantity > 0)
);
Run Code Online (Sandbox Code Playgroud)
在此 db<>fiddle 中在 PostgreSQL 11 上进行了测试。
因此,已建立item_number
为可以包含 NULL 的列,从逻辑上讲,说它是 AK 是不正确的。此外,您将存储不明确的NULL 标记——它们不是值,无论 PostgreSQL 文档是否以这种方式标记它们——,因此可以说该表不是已适应数学关系的正确表示,并且规范化规则不能应用于它。
由于 NULL 表示列值是 (1) unknown或 (2) inapplicable,因此不能正确声明所述标记属于item_number
值的有效域。如您所知,这种标记说明了一个真实值的“状态”,但它本身并不是一个值,自然不会如此表现——顺便说一下,值得一提的是NULL 在不同的 SQL 数据库管理系统中的行为不同,即使在同一数据库管理系统的不同版本之间。
然后,如果 (i) 某个列的值域和 (ii)由于包含 NULL ,该列所承载的含义并不完全清楚:
如何评估和定义相关的功能依赖?
如何将其识别并声明为 PRIMARY 或 ALTERNATE KEY(如item_number
)?
尽管理论和实践(例如关于数据操作)都涉及在数据库中保留 NULL 标记的问题,但这是处理丢失数据的方法,您会在绝大多数 SQL 平台上构建的数据库中找到这种方法,因为它允许将可选值的列附加到重要的基表,从而避免创建 (a) 补充表和 (b) 相关任务。
我已经介绍了两种选择,以便您可以自行确定哪一种更适合实现您的目标。
你的问题中有一些点以特殊的方式引起了我的注意,所以我列出了它们:
有时(可能是 3% 到 5% 的时间),item_number 实际上等于 SKU。也就是说,我的一个供应商特别在他们的产品上贴上了我怀疑不是全球唯一的 SKU,按照他们的项目编号设计。
[…] 可能存在供应商回收具有不同 sku 的目录号(也许?)的情况,或者两个制造商都生产“d57-red”或类似的情况。在这种情况下,我想我必须以编程方式在违规 item_numbers 前面加上制造商名称或类似的前缀。
SKU 在我的域中将始终是唯一的(供应商提供的少量非全局唯一 SKU 不太可能发生冲突)。
这些观点可能会产生显着的影响,因为它们似乎表明:
该ItemNumber值可以最终成为复制和,这种情况发生的时候,你可能会评估结合两种不同的信息是承担同一列不同的含义。
毕竟,Sku值可能会重复(即使是少量重复的Sku实例)。
在这方面,值得注意的是数据建模练习的两个首要目标是 (1) 确定每个单独的重要数据和 (2) 防止在同一列中保留多个数据。例如,这些因素有助于描述稳定和通用的数据库结构,并有助于避免重复信息——这有助于通过各自的约束保持与业务规则一致的数据值。
处理Sku重复项的替代方法:将manufacturer
表引入场景
因此,如果可以在不同的制造商之间共享相同的Sku值,您可以使用表中的复合PK 约束,它将由 (i) 制造商 PK 列和 (ii) 组成。例如:product
sku
CREATE TABLE manufacturer (
manufacturer_number INTEGER NOT NULL, -- This could be something more meaningful, e.g., ‘manufacturer_code’.
name TEXT NOT NULL,
--
CONSTRAINT manufacturer_PK PRIMARY KEY (manufacturer_number),
CONSTRAINT manufacturer_AK UNIQUE (name) -- AK.
);
CREATE TABLE product (
manufacturer_number INTEGER NOT NULL,
sku TEXT NOT NULL,
name TEXT NOT NULL,
price NUMERIC NOT NULL,
quantity NUMERIC NOT NULL,
--
CONSTRAINT product_PK PRIMARY KEY (manufacturer_number, sku), -- Composite PK.
CONSTRAINT product_AK UNIQUE (name), -- AK.
CONSTRAINT product_TO_manufacturer_FK FOREIGN KEY (manufacturer_number)
REFERENCES manufacturer (manufacturer_number),
CONSTRAINT valid_price_CK CHECK (price > 0),
CONSTRAINT valid_quantity_CK CHECK (quantity > 0)
);
Run Code Online (Sandbox Code Playgroud)
并且,如果ItemNumber在适用时要求保持唯一性,则该product_item_number
表的结构可以如下:
CREATE TABLE product_item_number (
manufacturer_number INTEGER NOT NULL,
sku TEXT NOT NULL,
item_number TEXT NOT NULL,
--
CONSTRAINT product_item_number_PK PRIMARY KEY (manufacturer_number, sku), -- Composite PK.
CONSTRAINT product_item_number_AK UNIQUE (item_number), -- AK.
CONSTRAINT product_item_number_TO_product_FK FOREIGN KEY (manufacturer_number, sku)
REFERENCES product (manufacturer_number, sku)
);
Run Code Online (Sandbox Code Playgroud)
在此 db<>fiddle 中在 PostgreSQL 11 上进行了测试。
在情况下ItemNumber并没有要求防止重复,你只需删除UNIQUE约束申报这样的列,如在未来的DDL语句:
CREATE TABLE product_item_number (
manufacturer_number INTEGER NOT NULL,
sku TEXT NOT NULL,
item_number TEXT NOT NULL, -- In this case, ‘item_number’ does not require a UNIQUE constraint.
--
CONSTRAINT product_item_number_PK PRIMARY KEY (manufacturer_number, sku), -- Composite PK.
CONSTRAINT product_item_number_TO_product_FK FOREIGN KEY (manufacturer_number, sku)
REFERENCES product (manufacturer_number, sku)
);
Run Code Online (Sandbox Code Playgroud)
在另一方面,假设ItemNumber没有实际意味着避免重复值完全与问候到相关的制造商,你可以建立一个复合UNIQUE约束其中将包括manufacturer_number
和item_number
,如下面的代码行所示:
CREATE TABLE product_item_number (
manufacturer_number INTEGER NOT NULL,
sku TEXT NOT NULL,
item_number TEXT NOT NULL,
--
CONSTRAINT product_item_number_PK PRIMARY KEY (manufacturer_number, sku), -- Composite PK.
CONSTRAINT product_item_number_AK UNIQUE (manufacturer_number, item_number), -- Composite AK.
CONSTRAINT product_item_number_TO_product_FK FOREIGN KEY (manufacturer_number, sku) -- Composite FK.
REFERENCES product (manufacturer_number, sku)
);
Run Code Online (Sandbox Code Playgroud)
当Sku值始终唯一但特定的ItemNumber值可以在不同的制造商之间共享时
如果你能保证Product.Sku永远意味着重复,但一个ItemNumber可能由不同的使用厂家,你可以配置你的数据库这里暴露:
CREATE TABLE manufacturer (
manufacturer_number INTEGER NOT NULL,
name TEXT NOT NULL,
--
CONSTRAINT manufacturer_PK PRIMARY KEY (manufacturer_number),
CONSTRAINT manufacturer_AK UNIQUE (name) -- AK.
);
CREATE TABLE product (
sku TEXT NOT NULL,
name TEXT NOT NULL,
price NUMERIC NOT NULL,
quantity NUMERIC NOT NULL,
--
CONSTRAINT product_PK PRIMARY KEY (sku),
CONSTRAINT product_AK UNIQUE (name), -- AK.
CONSTRAINT valid_price_CK CHECK (price > 0),
CONSTRAINT valid_quantity_CK CHECK (quantity > 0)
);
CREATE TABLE product_item_number (
sku TEXT NOT NULL,
manufacturer_number INTEGER NOT NULL,
item_number TEXT NOT NULL,
--
CONSTRAINT product_item_number_PK PRIMARY KEY (sku, manufacturer_number),
CONSTRAINT product_item_number_AK UNIQUE (manufacturer_number, item_number), -- In this context, ‘manufacturer_number’ and ‘item_number’ compose an AK.
CONSTRAINT product_item_number_TO_product_FK FOREIGN KEY (sku)
REFERENCES product (sku),
CONSTRAINT product_item_number_TO_manufacturer_FK FOREIGN KEY (manufacturer_number)
REFERENCES manufacturer (manufacturer_number)
);
Run Code Online (Sandbox Code Playgroud)
在此 db<>fiddle 中在 PostgreSQL 11 上进行了测试。
我们还没有讨论列的确切类型和大小,product.sku
但是,如果它在字节方面“很大”,那么它最终可能会破坏系统的数据检索速度——由于物理抽象级别的各个方面,相关的例如,索引的大小和磁盘空间使用情况—。
通过这种方式,您可能想评估 INTEGER 列的合并,它可以提供比可能的“重”TEXT 更快的响应——但这一切都取决于比较列的精确特征——。product_number
正如预期的那样,它很可能代表一个代表记录集的序列中的数值products
。
包含这一新元素的说明性安排如下:
CREATE TABLE product (
product_number INTEGER NOT NULL,
sku TEXT NOT NULL,
name TEXT NOT NULL,
price NUMERIC NOT NULL,
quantity NUMERIC NOT NULL,
--
CONSTRAINT product_PK PRIMARY KEY (sku),
CONSTRAINT product_AK UNIQUE (name), -- AK.
CONSTRAINT valid_price_CK CHECK (price > 0),
CONSTRAINT valid_quantity_CK CHECK (quantity > 0)
);
CREATE TABLE product_item_number
(
product_number INTEGER NOT NULL,
item_number TEXT NOT NULL,
--
CONSTRAINT product_item_number_PK PRIMARY KEY (product_number),
CONSTRAINT product_item_number_AK UNIQUE (item_number), -- AK.
CONSTRAINT product_item_number_TO_product_FK FOREIGN KEY (product_number)
REFERENCES product (product_number)
);
Run Code Online (Sandbox Code Playgroud)
我强烈建议在大量数据负载下进行大量测试会话,以确定哪些键更方便——从物理上来说——,始终考虑整体数据库特性(所有表的列数、类型和大小)列、约束和基础索引等)。
您感兴趣的业务环境与这些帖子中处理的场景有一定的相似性,因此您可能会发现某些讨论的要点具有相关性。