且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

《PHP精粹:编写高效PHP代码》——2.7节设计数据库

更新时间:2022-09-20 22:03:04

本节书摘来自华章社区《PHP精粹:编写高效PHP代码》一书中的第2章,第2.7节设计数据库,作者:(美)  Davey Shafik,更多章节内容可以访问云栖社区“华章社区”公众号查看

2.7 设计数据库
到目前为止,我们已经创建了两个非常基本的表并且看到如何使用PDO操作简单的数据。现在我们要扩展这些例子,添加一些另外的表,探讨我们如何在真实的应用程序中使用这些数据。让我们从图2.2开始,看看目前有什么。
图2.2显示了两张表以一对多的关系链接。这表明categories表中的每一个记录在食谱表中会有很多与之相关的记录;也就是说,一个类别会有很多食谱,但一个食谱只能属于一个类别。


《PHP精粹:编写高效PHP代码》——2.7节设计数据库

2.7.1 主键与索引
我们已经为两张表添加了主键,主键使我们提供的列在每个表中必定是唯一的,我们可以轻松访问一个特定的记录。此外还有一个好处,MySQL可以在该列放置一个索引(index)。添加一个索引至数据库的列和要求数据库保持对其中内容的跟踪一样。例如,如果你在recipes.name列上添加了一个索引,数据库使用该列会很容易找到项目,因为索引会保持对这些记录在什么地方进行跟踪。

2.7.2 MySQL解析
我们应该学习的最后一个数据库方法是MySQL EXPLAIN命令。EXPLAIN详细描述了MySQL将如何运行查询。在SELECT查询之前,我们通过迅速放置EXPLAIN术语来使用它:


《PHP精粹:编写高效PHP代码》——2.7节设计数据库

这表明我们的查询不得不搜索所有的5个行,以便找到正在寻找的一行。五行并不是很多,但在这种情况下它们就是表中的每一行,并且总是带来坏消息!如果我们经常用食谱名查询食谱表中的行,我们可以添加一个索引来提高性能。
如果要添加一个索引,需使用ALTER TABLE语句。因此,要在recipes.name列添加一个索引,我们将输入:


《PHP精粹:编写高效PHP代码》——2.7节设计数据库

这表明如果我们试图在recipes表中插入一个id为4的记录,我们将看到一个错误消息。
外键支持
请注意并非所有的数据库都支持外键。MySQL支持外键,但只支持InnoDB表类型。使用MyISAM表类型,你可以创建一个外键,但是它会被忽略!在phpMyAdmin中,当你创建一个表,你会发现一个标题为Storage Engine的下拉菜单,你可以通过其中的选项来选择一个InnoDB表类型。


《PHP精粹:编写高效PHP代码》——2.7节设计数据库


《PHP精粹:编写高效PHP代码》——2.7节设计数据库

这是一个内部连接(inner join)的例子,这表明在这个查询中我们只能看到所有表中有匹配行的数据。我们在recipes表中还有其他的条目,但是因为我们仍需将一些配料和它们连接,所以这些条目不会在这个查询结果中出现。为了看到所有的食谱,无论它们有没有和配料连接,我们都将使用一个外部连接。
加入=内部连接
有时我们会看到只对自己使用JOIN关键字的查询,这些都是隐含的内部连接。这个例子使用了INNER关键字,让我们更清楚地看到发生了什么。我们很快将看到其他连接类型。

2.7.4 外部连接
现在你知道什么是内部连接了,你大概也猜到了什么是外部连接(outer join)。外部连接使我们能从一个表中检索所有行,还有其他表中与之匹配的行。如果没有匹配的数据,MySQL将对这些列返回NULL值。
由于外部连接包含的行来自于一个表及另一个随机的表,因此我们需要指定哪个表连接哪个表。我们使用RIGHT JOIN和LEFT JOIN表达式做到以上这些。因为我们是从左到右读,所以左边的表就是我们在SQL语句中遇到的第一个表。外部连接通常有助于勾勒出此时数据库的布局,或者你可以参考前面的架构图。
让我们来看看外部连接的一个例子。我们要显示所有的食谱,而不仅仅是带有配料的食谱。由于recipes表首先出现,因此我们将使用LEFT JOIN来表明需要显示左表中的所有行。


《PHP精粹:编写高效PHP代码》——2.7节设计数据库

要是愿意,我们可以从包含在查询中的任一表格提取很多列或几列。若在多个表中有相同名称的列,我们必须为这些列加上它们所属表名的前缀,否则MySQL会告诉我们它不知道所指的是哪一列。这是一个修饰所有列名的好办法,可以帮助明确数据来源。当你想要添加另一个表到你的查询中时,这可让你免于返回去重新修饰这些列名。

2.7.5 聚合函数和Group By
聚合函数(aggregate function)向我们提供了与查询相匹配的简要数据信息。我们用这种技术能达到各种理想的结果。这种精确的功能不同于平台到平台模式,这里有一些常见的例子以及它们的MySQL函数名:
对记录计数(COUNT)。
得到最大或最小的一个特定列的值(MAX或MIN)。
计算某一列的总和(SUM)。
计算某一列的平均值(AVG)。
例如,若想知道在查询中有多少条记录,可以使用MySQL中的COUNT()函数,就像这样:


《PHP精粹:编写高效PHP代码》——2.7节设计数据库

使用连接和聚合这两个函数的确非常棘手,但如果我们走一步看一步,对这些技术还是会慢慢理清头绪的。比起写一个庞大的SQL语句然后调试它,分阶段建立这些东西显然更加容易。
第一步是从一个表中得到数据并按照需要进行过滤。一次加入一个到表中,每次运行查询的时候,检查结果是否如你预期。一旦你看到MySQL根据请求算出的所有数据行,你就可以添加额外的格式化列、计算总数,以及其他任何需要为你应用程序生成的正确数据。使用聚合函数远比在PHP中为汇总数据集或计算平均数使用循环效率更高;数据库平台的确擅长处理数据,因此***是把这些任务交给专家完成。

2.7.6 规格化数据
数据规格化的主题通常本身就可以构成一整章,但是,简而言之,使用这个方法的目标是:
按实体来拆分它们,并将拆分后的部分各自组成自己的表。
避免在一个列中有多个值。
在一个地方记录数据,并将其与其他数据连接。
按照现在的情况我们可以改善数据库设计,将chef列的数据移到一个单独的表中。每个厨师都有一个唯一的标识符,并且记录在recipes表中。由于厨师是一个实体,他应该有自己的表,在这里我们可以集中记录厨师的信息并且维护这些信息,而不是在每个recipe行中重复记录。
显而易见,如果允许用户输入名字会导致食谱表中出现很多个“John”,也许只有几个“John”,其中一些名字可能就是同一个人!为了避免发生这种情况,我们需要移动厨师到他们自己的表中,就像下面这样:


《PHP精粹:编写高效PHP代码》——2.7节设计数据库

将数据分离进入表之后,我们给“厨师”这个实体建立了他们自己的表,这样可以避免在recipes表中重复记录值。这使我们更加接近***的标准化形式,并且巧妙地储存数据,允许我们使用本章前面读过的JOIN技术检索它们。