RDBMS:存储数据的正确方法 - 逗号分隔的变量还是不同的字段或表?

Bun*_*nny 0 mysql database-design relational-theory csv codds-rules

我似乎无法找到答案的基本问题。我有一个数据库,用于存储收据中的行项目以及用户名和收据编号。

现在该lineItem列只是一长串由逗号分隔的数据(原始文件似乎只是一个 Excel 文件)。此信息在 PHP 脚本中解析,以便在前端查看。

该表如下所示:

|----------|----------|----------|
|lineItem  |receiptID |customerID|
|----------|----------|----------|
|CD, DVD,  |001       |User01    |
|----------|----------|----------|
|CD, CD,   |002       |User02    |
|DVD, usb, |          |          |
|----------|----------|----------|
Run Code Online (Sandbox Code Playgroud)

最终,这是不好的做法吗?这些lineItem值是否应该链接到另一个表中的相关值?

Vér*_*ace 5

为什么将数据存储为字符串是个问题:

将多个数据存储为(在这种情况下用逗号分隔)字符串是不好的做法,因为:

-第一个原因:

它违反Codd's second rule(称为"Guaranteed Access Rule"),它指出Each and every datum (atomic value) in a relational data base is guaranteed to be logically accessible by resorting to a combination of table name, primary key value and column name.

所以,如果你想引用user02的 USB lineItem,你必须做进一步的处理,而不仅仅是知道表名、 thePRIMARY KEY和列名。

来自hereAmong the conventional database models, the Relational Model of data has a simple, sound mathematical foundation based on the notions of set theory.Relational Model(RM)上的维基我们有:

关系模型是第一个用正式数学术语描述的数据库模型。分层数据库和网络数据库早于关系数据库就存在,但它们的规范相对非正式。在定义了关系模型之后,有很多尝试比较和对比不同的模型,这导致了对早期模型的更严格的描述;尽管分层数据库和网络数据库的数据操作界面的程序性质限制了形式化的范围。

因此,基本上,唯一具有良好数学基础的数据模型是关系模型。Most relational databases use the SQL data definition and query language; these systems implement what can be regarded as an engineering approximation to the relational model.[同上]。

Codd 推导出他的规则作为他的关系演算实际实现的指南——考虑到它是唯一具有良好数学基础的模型,违反其中任何一个似乎都是一个坏主意。

警告:现在,如果例如,你将永远不要想行项目闯入了他们的个别组件,然后将其保存为一个“单元”是可以接受的,但我可以看到你在哪里许多情况下,要拆呢到它的组成部分(见下面的第五个原因)。

可能希望以 .csv 格式存储数据的一个示例可能是存储某人的姓名和学术期刊的标题 - 它可能存储为:

Citizen, Seán B., Prof.
Run Code Online (Sandbox Code Playgroud)

这是您打印/处理/传输/存储此信息的唯一方式,然后它是一个数据,而不是逗号分隔的变量——datum或者data是一个非常上下文的概念。

-第二个原因:

正如评论中提到的,您的lineItem表格甚至不是第一范式(请参阅此处的图表- Atomic columns (cells have single value)。这显然与上述点有关。Database normalisation

按照一系列所谓的范式构造关系数据库的过程,以减少数据冗余并提高数据完整性。

这些“形式”源自 RM/关系演算和 Codd 的规则,作为确保数据保持一致的一种方式,这在任何数据库系统中显然都是最重要的 - 简单来说,它是我们如何确保给定原型的最终原型数据存储在一处且仅存储在一处。

-第三个原因:

您无法控制将哪些数据输入该字段 - 即您无法控制Declarative Referential Integrity(DRI)。这意味着,例如,没有什么可以阻止您提及不存在的产品(例如,DVDx)。

DRI 是使用 RM的最重要的好处之一——它意味着可以维护内部数据一致性,如果您曾经不幸使用过这样的系统,您将非常感激它的好处已经崩溃了。

在第二点中,我们说范式要ensure that the definitive archetype of a given datum is stored in one place and one place only- DRI 确保对该数据的所有其他引用都指向那个地方而不是其他地方。

-第四个原因:

SQL 不是为解析字符串而设计的 - 它可以完成,它只是混乱、耗时且容易出错。各种 RDBMS 提供者已经开发了许多专有扩展来尝试克服这个缺陷,但是处理正确规范化的表仍然容易得多(请参阅下面的 SQL)。

-第五个原因:

除了“理论”(更多或更少)的理由不这样做,是大量实用不能够分配您的架构下,单个数量和价格的项目的问题-假设我做我的圣诞购物,我想要的给我的三个 U2 狂热者朋友的新“U2 CD”?除了具有如下字段值外,无法告诉系统有 3 个 U2 CD:

'“U2 CD”、“U2 CD”、“U2 CD”、“UB40 CD”、“U2 DVD”、“金士顿 USB 32GB”'——注意“U2 CD”的重复。

假设您想知道已售出的 USB 数量?每个客户端的 USB 数量?每个客户区/地区/国家的数量 - 取决于您的运营规模(请参阅下面的 SQL)?假设我想知道上周在 USB 驱动器上花费了多少 -绝对无法获得任何这些信息!名单还在继续……

如何处理问题:

因此,处理完您问题的第一部分后,我们现在可以进入第二部分 - Should the lineItem values be linked to relational values in another table instead maybe?

-第一个解决方案(额外字段):

是与存储字符串相关的问题的另一个示例。在这种情况下,向给定记录添加字段是解决方案 - 即将字符串拆分为其组成部分并使每个组成部分成为一个字段!如果有(在这种情况下)邮政编码、街道名称等的参考表,则对执行 DRI 和控制数据正确性非常有帮助...

-第二种解决方案(额外记录 - 一对多关系):

您的问题的这种特殊情况下,我们这里有一个经典的1-many relationship- 也称为父子,其中receipt父和子在哪里line_item

你的表结构是这样的:

CREATE TABLE line_item
(
  lineItem VARCHAR(2000),  -- could have a many items - need a very long string - parsing a nightmare! 
  receiptID INTEGER,       -- "001" could be a string - MySQL has a zero-fill function
  customeID VARCHAR(20)     -- redundant - don't need to store it for every line_item - it corresponds to a receipt (1 customer/receipt), not a line_item!
);
Run Code Online (Sandbox Code Playgroud)

您应该拥有的是这样的东西(请参阅此处的小提琴- 所有数据和表格也在此答案的底部给出):

CREATE TABLE line_item
(
  receipt_id INTEGER NOT NULL,
  item_id INTEGER NOT NULL,
  item_qty INTEGER NOT NULL,
  CONSTRAINT line_item_pk PRIMARY KEY (receipt_id, item_id),
  CONSTRAINT li_item_fk FOREIGN KEY (item_id) REFERENCES item (item_id),
  CONSTRAINT li_receipt_fk FOREIGN KEY (receipt_id) REFERENCES receipt (receipt_id)
);
Run Code Online (Sandbox Code Playgroud)

您的数据将(相当神秘)如下所示:

INSERT INTO line_item VALUES
(1, 1, 1), (1, 4, 1), (2, 2, 1), (2, 3, 1), (2, 5, 1);
Run Code Online (Sandbox Code Playgroud)

receipt_id字段和item_id字段指向PRIMARY KEY各自的表秒-并没有任何多余的,在表中的无关信息-没有customer_id多次存储例如!这种建模方式允许编写以下形式的查询:

SELECT 
  c.customer_id, c.customer_name, c.customer_address_1,
  i.item_desc, i.item_price, 
  r.receipt_id, 
  li.item_id, li.item_qty
FROM 
  customer c
JOIN receipt r 
  ON c.customer_id = r.customer_id
JOIN line_item li 
  ON r.receipt_id = li.receipt_id
JOIN item i 
  ON li.item_id = i.item_id;
Run Code Online (Sandbox Code Playgroud)

结果:

customer_id  customer_name  customer_address_1  item_desc          item_price   receipt_id  item_id     item_qty
1            Bill Gates     Redmond             Michael Jackson CD      1.50              1     1          1
1            Bill Gates     Redmond             U2 DVD                   5.00             1     4          1
2            Larry Ellison  Redwood Shores      U2 CD                    2.00             2     2          1
2            Larry Ellison  Redwood Shores      UB40 CD                 4.00              2     3          1
2            Larry Ellison  Redwood Shores      Kingston USB 32GB       25.00             2     5          1
Run Code Online (Sandbox Code Playgroud)

有关所有 DDL 和 DML,请参阅小提琴(或下方)!我挑战您使用包含您的line_item产品的 .csv 字符串轻松完成此操作- 特别是在 MySQL 中!在 PostgreSQL 中使用类似array_to_table将字符串输入数组后的方法可能是可行的,但我将其作为练习留给您!

因此,对于 1-many 关系,您将项目添加到您的line_item表中 - .csv 字符串中的每个元素对应一个项目 - 1 个receipt父记录可以拥有1多个(可能非常多)line_item子记录。

现在,该item表也​​是它的父级line_item,在这种情况下,可以0有多个子级,例如,如果一个项目根本没有售出,则表中将没有对它的引用line_item

-第三个解决方案(额外的表 - 多对多关系):

在适当的情况下,“值应该链接到另一个表中的关系值”(正如您在问题中所暗示的那样),这是存在m-to-n关系的时候 - 否则称为many-to-many关系。

考虑以前最喜欢的 Databases-101 学生和课程示例以及许多学生参加的许多课程!请参阅此处的小提琴 - 这次我没有填充表格。我已经将 PostgreSQL 用于小提琴(我最喜欢的服务器),但稍微调整一下就可以让它在任何合理的 RDBMS 上工作。

创建表课程和学生:

CREATE TABLE course
(
  course_id SERIAL,  -- INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY in MySQL dialect
  course_year SMALLINT NOT NULL,
  course_name VARCHAR (100) NOT NULL,
  CONSTRAINT course_pk PRIMARY KEY (course_id)
);


CREATE TABLE student
(
  student_id SERIAL,
  student_name VARCHAR (50),
  CONSTRAINT student_pk PRIMARY KEY (student_id)
);
Run Code Online (Sandbox Code Playgroud)

这是一个JOINing 表(又linking名表(more formally known as an [关联实体`]13 - 顺便说一句,该页面上这种类型的表有 17 个不同的名称)出现。

  • 一个给定的学生可以选修很多课程
  • 一门课程可以有很多学生

因此,您可以通过创建Associative Entity- 您的JOINing 表来处理此问题:

CREATE TABLE registration 
(
  reg_course_id INTEGER NOT NULL,
  reg_student_id INTEGER NOT NULL,

  CONSTRAINT reg_course_fk FOREIGN KEY (reg_course_id) REFERENCES course (course_id),
  CONSTRAINT reg_student_fk FOREIGN KEY (reg_student_id) REFERENCES student (student_id)
);
Run Code Online (Sandbox Code Playgroud)

然后我添加一个PRIMARY KEY- 我将它保留在表定义之外以说明这一点,但它可以(并且通常会)成为表创建 DDL 的一部分。

ALTER TABLE registration
ADD CONSTRAINT registration_pk 
PRIMARY KEY (reg_course_id, reg_student_id);
Run Code Online (Sandbox Code Playgroud)

所以现在,

  • 给定的学生只能注册一次给定的课程并且

  • 同一门课程只能让同一个学生注册一次

在许多其他情况下,此构造很有用 - 基本上,这是对许多现实生活情况进行有意义建模的唯一方法。

我自己职业生涯中的一个例子:

想象flight一张包含flight_id字段、出发和到达机场列表以及相关时间的crew表格,然后还有一张包含机组人员和crew_id字段的表格(显然还有其他详细信息)。

具有flight_idcrew_id领域在JOIN荷兰国际集团的表被证明是非常有用的系统-在这两个冲突不断-它确实与调度和排班这与其他系统一团糟帮助。识别哪种模式设计适合哪种场景需要时间和经验,但是 1-many(现有表中的额外记录)和 many-many(extra JOINing table)是一个很好的经验法则!

ps 欢迎来到论坛!

_____________ 完整的 DDL 和 DML _____________

Customer table:

CREATE TABLE customer  -- storing the customer_id on every line item is redundant - check out 3rd normal form
(
  customer_id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
  customer_name VARCHAR (100) NOT NULL,
  customer_address_1 VARCHAR (100) NOT NULL -- can have address_1..n
  --
  -- other fields of particular interest to you
  --
);
Run Code Online (Sandbox Code Playgroud)

Customer data:

INSERT INTO customer (customer_name, customer_address_1) VALUES 
('Bill Gates', 'Redmond'), ('Larry Ellison', 'Redwood Shores');
Run Code Online (Sandbox Code Playgroud)

item table:

CREATE TABLE item ( item_id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, item_code VARCHAR (25) NOT NULL UNIQUE, item_desc VARCHAR (200) NOT NULL, item_price DECIMAL(10, 2), item_supplier INTEGER NOT NULL -- 指供应商表 - 未显示! -- -- 您感兴趣的其他领域 -- );

item data:

INSERT INTO item (item_code, item_desc, item_price, item_supplier) VALUES
('code_1', 'Michael Jackson CD', 1.5, 56), ('code_2', 'U2 CD', 2, 78), ('code_3', 'UB40 CD', 4, 67),
('code_4', 'U2 DVD', 5, 78), ('code_5', 'Kingston USB 32GB', 25, 23);
Run Code Online (Sandbox Code Playgroud)

receipt table:

CREATE TABLE 收据——通常被称为“订单”,但收据是可以的(收据_id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,customer_id INTEGER NOT NULL,——参考客户表——见下文

receipt_total DECIMAL(10, 2), -- 由触发器(未显示)保持更新 -- 可以即时计算或 -- 可能生成的字段receipt_dt TIMESTAMP NOT NULL, -- 销售日期和时间receipt_asst INTEGER, - - 参考销售助理表 - 未显示 CONSTRAINT rec_cust_fk FOREIGN KEY (customer_id) REFERENCES customer (customer_id) );

receipt data:

INSERT INTO receipt (customer_id, receipt_total, receipt_dt, receipt_asst)
VALUES
(1, 6.5, '2020-06-03 15:23:45.123', 34),
(2, 31 , '2020-06-05 10:54:23.123', 17);
Run Code Online (Sandbox Code Playgroud)

line_item table:

CREATE TABLE line_item
(
  receipt_id INTEGER NOT NULL,
  item_id INTEGER NOT NULL,
  item_qty INTEGER NOT NULL,
  CONSTRAINT line_item_pk PRIMARY KEY (receipt_id, item_id),
  CONSTRAINT li_item_fk FOREIGN KEY (item_id) REFERENCES item (item_id),
  CONSTRAINT li_receipt_fk FOREIGN KEY (receipt_id) REFERENCES receipt (receipt_id)
);
Run Code Online (Sandbox Code Playgroud)

line_item data:

INSERT INTO line_item VALUES
(1, 1, 1), (1, 4, 1), (2, 2, 1), (2, 3, 1), (2, 5, 1);
Run Code Online (Sandbox Code Playgroud)

询问:

SELECT 
  c.customer_id, c.customer_name, c.customer_address_1,
  i.item_desc, i.item_price, 
  r.receipt_id, 
  li.item_id, li.item_qty
FROM 
  customer c
JOIN receipt r 
  ON c.customer_id = r.customer_id
JOIN line_item li 
  ON r.receipt_id = li.receipt_id
JOIN item i 
  ON li.item_id = i.item_id;
Run Code Online (Sandbox Code Playgroud)

结果:

customer_id  customer_name  customer_address_1  item_desc          item_price   receipt_id  item_id     item_qty
1            Bill Gates     Redmond             Michael Jackson CD      1.50              1     1          1
1            Bill Gates     Redmond             U2 DVD                   5.00             1     4          1
2            Larry Ellison  Redwood Shores      U2 CD                    2.00             2     2          1
2            Larry Ellison  Redwood Shores      UB40 CD                 4.00              2     3          1
2            Larry Ellison  Redwood Shores      Kingston USB 32GB       25.00             2     5          1
Run Code Online (Sandbox Code Playgroud)