《七周七数据库》一一2.2 第1天:关系、CRUD和联接

2.2 第1天:关系、CRUD和联接

七周七数据库
我们虽然不会把你当作是一个关系数据库专家,但是确实会假设你曾用过一两个数据库。这些数据库很可能是关系型的。我们将开始创建自己的数据表,并填充数据。然后尝试查询一些行。最后探讨关系数据库中非常重要的表联接。

就像大多数数据库一样,Postgres提供一个后台服务进程(Backend),它完成所有数据处理工作,还提供一个命令行客户端程序,通过它连接到运行中的服务进程。服务进程默认监听5432端口,可以用psql这个命令行工具连接。

数学关系

关系数据库的名称源于它们包含关系(即表),它们是元组(即行)的集合,元组又将属性映射到原子值(例如,{name: 'Genghis Khan',p.died_at_age: 65})。

可用的属性通过头部的属性元组来定义,这些属性映射到某个域或限制的类型(即列;例如,{name: string,age: int})。这是关系结构的要点。

尽管听起来数学味很浓,但是实现比名字所暗示的更具有现实意义。那么,为什么要提到这些?我们正试图说明,关系数据库的关系是因为它的数学基础,不是因为表通过外键彼此“关联”。这样的限制是否存在并不是关键。

虽然许多数学关系你看不到,但模型的力量肯定是蕴藏在数学之中。这种魔法允许用户提出功能强大的查询,然后让系统基于预定义的模式进行优化。RDBMS基于集合理论的一个分支,名为关系代数,它包括选择(WHERE...)、投影(SELECT...)、笛卡尔积(JOIN...)等操作,如图2-1所示。

如果将关系想象为一张物理表(数组的数组,在数据库入门课中无数次重复过),可能在实践中造成痛苦,如编写遍历所有行的代码。关系查询的描述性远胜于此,它源于一个数学分支,名为元组关系演算,可以转换为关系代数。PostgreSQL和其他的RDBMS通过执行这个转换优化了查询,简化了代数运算。你可以看到,图2-2中的SQL,与图2-1中的SQL是一样的。

$ psql book

以管理员用户运行的话,PostgreSQL的提示符是数据库的名字后面跟一个‘#’,如果是普通用户,后面跟的是‘$’。这个命令行程序的内置文档是所有命令行程序中最好的。输入‘/h’,可以列出有关SQL命令的信息,\?列出以反斜杠开始的psql特有命令的帮助信息。可以使用下列方式找到每个SQL命令的使用详细信息:

book=# \h CREATE INDEX
Command: CREATE INDEX
Description: define a new index
Syntax:
CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ name ] ON table [ USING method ]
  ( { column | ( expression ) } [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | ...
  [ WITH ( storage_parameter = value [, ... ] ) ]
  [ TABLESPACE tablespace ]
  [ WHERE predicate ]

在我们深入探讨PostgreSQL之前,最好先熟悉这个有用的工具。还可以熟悉一些常见命令值,如SELECT或CREATE TABLE。

2.2.1 从SQL开始

PostgreSQL遵循SQL惯例,称关系为表(TABLE),属性为列(COLUMN),元组为行(ROW)。虽然你可能会遇到一些数学术语,如关系、属性和元组,为了保持一致性,我们将使用这些术语,如关系、属性和元组。有关这些概念的更多信息,请参阅2.2节的“数学关系”。

关于CRUD

CRUD是一个助记符,帮助记忆数据管理基本操作:创建、读取、更新和删除(Create,Read,Update,Delete)。这些操作一般对应插入新记录(创建),修改现有记录(更新),删除不再需要的记录(删除)。你使用数据库时所有的其他操作(你可以梦想到的任何疯狂查询)都是读操作。如果能进行CRUD操作,你就能做任何事。

2.2.2 使用表

PostgreSQL是关系型的数据管理系统,所以需要事先进行设计。要先设计好数据库的表,然后插入符合数据库定义的数据。

创建表包括为它命名,定义所有列及其类型,以及定义(可选的)约束信息。每张表都应该指定唯一的标识符列,以标识特定的行。该标识符称为主键(PRIMARY KEY)。创建countries表的SQL语句如下所示:

CREATE TABLE countries (
  country_code char(2) PRIMARY KEY,
  country_name text UNIQUE
);

这张新表将存储一些行,其中每一行由两个字节的国家代码作为标识,国家名也是唯一的。这两列都有约束,主键约束country_code列不允许有重复的国家代码,所以只有一个us和一个gb可以存在表中。尽管country_name不是主键,但是明确地给予country_name类似的唯一性约束。可以用如下语句插入几行来填充这张counties表。

INSERT INTO countries (country_code, country_name)
VALUES ('us','United States'), ('mx','Mexico'), ('au','Australia'),
     ('gb','United Kingdom'), ('de','Germany'), ('ll','Loompaland');

让我们来测试一下唯一性约束。如果尝试添加包含重复的country_name的行,就会因为唯一性约束而不允许插入。约束是PostgreSQL这样的关系数据库用来确保数据完整的方法。

INSERT INTO countries
VALUES ('uk','United Kingdom');
ERROR: duplicate key value violates unique constraint "countries_country_name_key"
DETAIL: Key (country_name)=(United Kingdom) already exists.

通过SELECT...FROMtable语句进行查询,可以验证相关的行是否已经插入。

SELECT *
FROM countries;
country_code   | country_name
----------------+---------------
us       | United States
mx       | Mexico
au       | Australia
gb       | United Kingdom
de       | Germany
ll       | Loompaland
(6 rows)

根据正规的地图,可以知道Loompaland不是真实存在的地方,所以让我们从表中删除它。用WHERE子句指定要删除的行,country_code等于ll的行将被删除。

DELETE FROM countries
WHERE country_code = 'll';

只有实际存在的国家留在了countries表中,让我们再添加一个cities表。为保证所有插入的country_code都在countries表中,将添加关键字REFERENCES。因为country_code列引用了另一张表的键,所以它称为外键约束。

CREATE TABLE cities (
 name text NOT NULL,
 postal_code varchar(9) CHECK (postal_code <> ''),
 country_code char(2) REFERENCES countries,
 PRIMARY KEY (country_code, postal_code)
);

这一次,cities表中的name列的约束是不允许其值为NULL的。postal_code列的约束,是其值不能是空字符串(<>表示不等于)。

此外,因为主键唯一地标识一行,所以定义了一个复合键:country_code + postal_code。它们共同作为一行的唯一的标识符。

Postgres也有丰富的数据类型,刚才看到了三种不同的字符串表示:text(任意长度的字符串),varchar(9)(长度可达9个字节的字符串)和char(2)(正好两个字节的字符串)。

定义了数据表后,让我们插入Toronto,CA。

INSERT INTO cities
VALUES ('Toronto','M4C1B5','ca');
ERROR: insert or update on table "cities" violates foreign key constraint
  "cities_country_code_fkey"
DETAIL: Key (country_code)=(ca) is not present in table "countries".

这个操作失败并不是什么坏事!因为country_code需要参考countries,所以country_code必须存在于countries表中,这称为保持参照完整性,参见图2-3,它确保数据始终是正确的。值得指出的是,NULL对cities.country_code是有效的,因为NULL代表一个值空缺。如果你不想允许country_code引用为NULL,可以这样定义cities表的列:country_code char(2)REFERENCES countries NOT NULL。

现在我们再试试插入一个美国城市的数据。

INSERT INTO cities
VALUES ('Portland','87200','us');
INSERT 0 1

.

当然,这是一次成功的插入。但是我们输入了错误的邮政编码。波特兰(Portland)正确的邮政编码是97205,但我们不必删除并重新插入,可以直接更新这一行。

UPDATE cities
SET postal_code = '97205'
WHERE name = 'Portland';

现在已经可以创建、读取、更新、删除表中的行了。

2.2.3 使用联接的查询

在本书中学习的所有其他数据库,也都可以执行CRUD操作。但PostgreSQL这样的关系数据库有独特的能力,能够在读取表时对表进行联接。联接本质上是以某种方式联接两个独立的表,并返回一张结果表。这有点像拼字游戏,打散单词的字母卡片,重新拼接字母,从而得到新的词。

联接的基本形式是内联接(inner join)。最简单的形式就是,使用ON关键字指定匹配的两列(每张表一列)

SELECT cities.*, country_name
FROM cities INNER JOIN countries
 ON cities.country_code = countries.country_code;
country_code  |  name | postal_code | country_name
--------------+----------+-------------+----------------
us      | Portland | 97205    | United States

联接返回单张表,其中包含cities表的所有列的值,再加上匹配的countries表中country_name的值。

也可以联接诸如cities这样有复合主键的表。为了测试复合联接,我创建一张新表,来存储场地(venue)的列表。

某个国家和一个邮政编码组成一个场所。外键必须引用Cities表的两个主键列。(MATCH FULL是一个约束,确保两个值都存在,或两者均为NULL。)

CREATE TABLE venues (
  venue_id SERIAL PRIMARY KEY,
  name varchar(255),
  street_address text,
  type char(7) CHECK ( type in ('public','private') ) DEFAULT 'public',
  postal_code varchar(9),
  country_code char(2),
  FOREIGN KEY (country_code, postal_code)
   REFERENCES cities (country_code, postal_code) MATCH FULL
);

其中venue_id列是一种常见的主键设置:设置为自动递增整数(1,2,3,4,…)。可以使用SERIAL关键字来定义这个标识符(MySQL有一个类似的构造,称为AUTO_INCREMENT)。

INSERT INTO venues (name, postal_code, country_code)
VALUES ('Crystal Ballroom', '97205', 'us');

虽然没有设置venue_id的值,但创建行时会填充它。

回到复合联接。联接enues表和cities表需要用到两个外键列。为了减少输入量,可以在表名后面直接加别名,它们中间的AS是可选的(例如,venues vvenues AS v)。

SELECT v.venue_id, v.name, c.name
FROM venues v INNER JOIN cities c
  ON v.postal_code=c.postal_code AND v.country_code=c.country_code;
venue_id  |  name  | name
-----------+----------+---------
     1 | Crystal Ballroom | Portland

可以选择指定PostgreSQL在插入后返回一些列,方法是让请求以RETURNING语句结尾。

INSERT INTO venues (name, postal_code, country_code)
VALUES ('Voodoo Donuts', '97205', 'us') RETURNING venue_id;
 id
--------

 2

无须执行另一个查询,就可以得到新插入的venue_id值。

2.2.4 外联接

除了内联接,PostgreSQL也可以执行外联接(outer join)。外联接是合并两张表的一种方式,不论另一张表中是否存在匹配的列值,第一张表的结果总是必须返回。

最简单的方法是举一个例子,但是首先我们需要创建一张名为events的新表。events表应该有这些列:SERIAL整数event_idtitlestartsends(类型为时间戳),以及venue_id(引用venues的外键)。图2-4展示了一个数据库的定义图,它涵盖了到目前为止我们创建的所有表。

创建events表后,插入以下值(时间戳作为字符串插入,例如,2012-02-15 17:30),两个节日,以及我们不会详加讨论的一个俱乐部。

title   |    starts     |     ends    |  venue_id   | event_id
-------------------+---------------------+---------------------+-------------+--------
LARP Club      | 2012-02-15 17:30:00 | 2012-02-15 19:30:00 |   2   |     1
April Fools Day   | 2012-04-01 00:00:00 | 2012-04-01 23:59:00 |      |     2
Christmas Day    | 2012-12-25 00:00:00 | 2012-12-25 23:59:00 |      |     3

我们先来做一个查询,使用内联接返回一个事件的标题和场地名称(INNER JOIN中的INNER并不是必需的,所以这里省略它)。

SELECT e.title, v.name
FROM events e JOIN venues v
 ON e.venue_id = v.venue_id;
   title   |   name
----------------+--------------
LARP Club     | Voodoo Donuts

只有列值匹配,INNER JOIN才会返回一行。因为不能有空的venues.venue_id,所以两个空events.venue_id没有关联到任何事情。要查询所有的事件,不管它们是否有场地,我们需要一个左外连接(LEFT OUTER JOIN,简写为LEFT JOIN)。

SELECT e.title, v.name
FROM events e LEFT JOIN venues v
ON e.venue_id = v.venue_id;
    title    |    name
---------------------+---------------
 LARP Club      | Voodoo Donuts
 April Fools Day   |
 Christmas Day    |

如果你需要反过来,返回所有的场地和匹配的事件,就要用RIGHT JOIN。最后,还有FULL JOIN,这是LEFT和RIGHT的联合;保证能得到每张表中的所有值,列匹配时就会联接。

2.2.5 使用索引快速查找

PostgreSQL的速度(和任何其他RDBMS一样)源于其高效的数据块管理、尽可能少的磁盘块读取、查询优化等技术。如果从events表选择title为Christmas Day的行,则需要进行全表扫描,以返回相关的结果。如果没有索引,就必须从磁盘读取每一行,才能知道是否是匹配行。参见图2-5。

索引是一个特殊的数据结构,目的是避免执行查询时进行全表扫描。当运行CREATE TABLE命令时,你可能注意到这样一条消息:

CREATE TABLE / PRIMARY KEY will create implicit index "events_pkey" \
for table "events"

PostgreSQL自动在主键上创建索引,以主键的列值为索引的键,索引的值则指向磁盘上的一行,如图2-6所示。采用UNIQUE关键字,是强制在表中一列上创建索引的另一种方式。

可以使用CREATE INDEX命令明确地添加一个哈希索引,其中每个值必须是唯一的(就像一个哈希或映射)。

对于这样的操作,树是一个完美的数据结构。要对starts列创建B树索引,使用下面的命令:

CREATE INDEX events_title
 ON events USING hash (title);

对于操作符为小于/大于/等于这样的匹配查询,我们希望索引比简单的哈希更灵活,如B树索引(见图2-7)。考虑用一个查询来查找4月1日或之后发生的所有事件。

SELECT *
FROM events
WHERE starts >= '2012-04-01';

对于这样的操作,树是一个完美的数据结构。要对starts列创建B树索引,使用下面的命令:

CREATE INDEX events_starts
 ON events USING btree (starts);

这样对日期范围的查询将可以避免全表扫描。当扫描数百万或数十亿行时,上述查询的性能差异会很大。

可以用下面的命令,列出数据模式中的所有索引:

book=# \di
值得注意的是,当对列创建一个FOREIGN KEY约束时,PostgreSQL将自动在目标列创建索引。即使你不喜欢使用数据库约束,也会经常发现自己需要在进行联接的列上创建索引,以便加快基于外键的表联接。

2.2.6 第1天总结

我们今天快速介绍了许多内容,涉及很多方面。总结如下:

四十多年来,关系数据库已经成为事实上的数据管理策略,我们中的很多人在其发展的中途,开始了自己的职业生涯。因此,我们通过一些基本的SQL查询,初步探讨了关系模型的一些核心概念。明天我们将详细说明这些基本概念。

2.2.7 第1天作业

查找
1.将PostgreSQL官网的常见问题集(FAQ)和官方文档保存为书签。

2.熟悉PSQL的命令行\?h的输出信息。

3.在FOREIGN KEY的定义中文档中找到MATCH FULL是什么意思。

完成
1.从pg_class中查询我们创建的所有表(仅我们创建的)。

2.编写一个查询,找到LARP Club事件对应的国家名。

3.修改venues表,增加一个名为active的列,该列为布尔类型,默认值是TRUE

时间: 2024-12-25 06:27:23

《七周七数据库》一一2.2 第1天:关系、CRUD和联接的相关文章

《七周七数据库》一一1.2 风格

1.2 风格 七周七数据库 如同音乐一样,各种数据库有着其本身独特的风格.所有的歌曲都使用同样的音符,但是有些音符对某些网络的歌曲更合适.少有人驾驶着敞篷车,沿着405号公路超速行驶,同时播放巴赫的<B小调弥撒曲>.同样,在某些情况下,一些数据库比其他数据库更好些.你要问的不是"我能够用这个数据库来存储和完善数据吗?"而是"我应该用这个数据库吗?" 本节将要探讨5种主要的数据库类型,也会简单介绍每种类型中我们要关注的数据库. 重要的是要记住,你将面临的大

《七周七数据库》一一1.1 从一个问题开始

1.1 从一个问题开始 七周七数据库 本书的核心问题是:哪种数据库或数据库组合最好地解决了你的问题?读完本书,如果你知道如何根据特定需求和手头的资源做出这种选择,我们会很高兴. 但要回答这个问题,你需要了解你的选择.为此,我们将带你深入这7个数据库,揭示精华,并指出瑕疵.你将亲手尝试CRUD操作,发挥你使用的模式的力量,并找到下面这些问题的答案: ● 这是什么类型的数据库?数据库分为各种类型,例如,关系型.键-值型.多列型.面向文档型和图型.流行的数据库(包括本书中介绍的)一般可以划分为这几大类

《七周七数据库》一一2.5 总结

2.5 总结 七周七数据库 如果你没有接触过太多关系数据库的话,在决定放弃它而采用新类型的数据库之前,我们强烈建议更深入地学习PostgreSQL,或其他关系数据库.在过去的40多年里,关系数据库是大量学术研究和业界改进的重点,PostgreSQL是受益于这些进步的顶级开源关系数据库之一. 2.5.1 PostgreSQL的优点 和所有关系模型一样,PostgreSQL的优势很多:多年的研究,几乎每个计算领域的实践使用,灵活的查询能力,非常一致和持久的数据.在大多数编程语言中,都有经过实战考验的

《七周七数据库》一一第1章 概述

第1章 概述 七周七数据库当前是数据库世界的一个重要时刻.多年来,无论针对的问题是大还是小,关系模型一直是事实上的选择.我们不指望关系数据库会很快消失,但是人们正在从RDBMS的迷雾中走出来,寻找替代的方案,如无模式或可替代的数据结构,可简单复制,具有高可用性,可横向扩展,以及新的查询方法.这些选择统称为NoSQL,而NoSQL占据了本书的大部分内容. 本书将探讨七种数据库,涉及各种数据库风格.在阅读本书的过程中,你将了解每个数据库具有的各种功能和折中,如持久性与速度.绝对一致性与最终一致性等,

《七周七数据库》一一2.1 这就是Post-greS-Q-L

2.1 这就是Post-greS-Q-L 七周七数据库2.1 这就是Post-greS-Q-L在本书提到的数据库中,PostgreSQL是历史最悠久.实战经验最丰富的.它的扩展包括自然语言解析.多维索引.地理查询.自定义数据类型等.它具有高级的事务处理能力,支持十几种不同语言的存储过程,能在各种平台上运行.PostgreSQL内置支持Unicode.序列.表继承.子查询,而且是市场上遵循ANSI SQL标准最好的关系数据库之一.它快速可靠,可以处理TB量级的数据,并且已经在一些高知名度的生产系统

《七周七数据库》一一2.3 第2天:高级查询、代码和规则

2.3 第2天:高级查询.代码和规则 七周七数据库昨天,我们看到了如何定义数据库表,然后插入数据,更新和删除行,以及读数据.今天我们将更深入探讨PostgreSQL查询数据的各种方法. 我们将看到如何对相似的值归类,在服务器端执行代码,并使用视图(view)和规则(rule)创建自定义接口.在这一天的最后,我们将利用PostgreSQL的一个扩展包翻转表头. 2.3.1 聚合函数 聚合查询按照一些共同的标准将多行的结果分组.它可以简单到统计一张表的行数,或计算某些数值列的平均值.它们是强大的SQ

《七周七数据库》导读

前言 七周七数据库 前言 如果说数据是新的石油,那么数据库就是油田.炼油厂.钻井和油泵.数据存放在数据库中,如果你有兴趣利用它,那么掌握相应的现代化的工具就是好的开始. 数据库是工具,它们是到达终点的手段.每种数据库都有自己保存数据和看待世界的方式.你对它们的理解越多,就越能随心所欲,在日益增长的大数据上,就能更好地利用它们潜在的能力. 为什么是7种数据库 早在2010年3月,我们就想写本关于NoSQL的书.NoSQL这个术语已经聚集了人气,虽然许多人都在谈论,但关于它似乎也存在相当多的困惑.N

《七周七并发模型》第一章概述

并发编程虽不是新的概念,最近却逐渐热门起来.一些编程语言,如Erlang.Haskell.Go.Scala.Clojure,也因对并发编程提供了良好的支持,而受到广泛关注. 并发编程复兴的主要驱动力来自于所谓的"多核危机".正如摩尔定律①所预言的那样,芯片性能仍在不断提高,但相比加快CPU的速度,计算机正在向多核化方向②发展.正如Herb Sutter所说,"免费午餐的时代已然终结"③.为了让代码运行得更快,单纯依靠更快的硬件已无法满足要求,我们需要利用多核,也就是

《七周七并发模型》第五章Actor引言

作者:Paul Butcher   译者:黄炎  出品方:图灵  出版社:人民邮电出版社 (感谢图灵授权并发编程网发布此文) 使用actor就像租车--我们如果需要,可以快速便捷地租到一辆:如果车辆发生故障,也不需要自己修理,直接打电话给租车公司更换另外一辆即可. actor模型是一种适用性非常好的通用并发编程模型.它可以应用于共享内存架构和分布式内存架构,适合解决地理分布型的问题.同时它还能提供很好的容错性. 5.1 更加面向对象 函数式编程不使用可变状态,也就避免了共享可变状态带来的问题.相