第二章 SQL语言

2.1 概念

PostgreSQL是一种关系型数据库管理系统 (RDBMS)。这意味着它是一种用于管理那些以关系 形式存储数据的系统。关系实际上是的数学称呼。今天, 把数据存储在表里的概念已经快成固有的常识了,但是还有其它一些方法用于组织数据库。 在类 Unix 操作系统上的文件和目录就形成了一种层次数据库的例子。更现代的发展是面 向对象的数据库。

每个表都是一个命名的的集合。每一行由一组相同的命名 字段组成。而且每个字段都有一个特定的类型。虽然每个字 段在每一行里的位置是固定的,但一定要记住 SQL 并未对行在表中的顺序做任何保证 (但你可以对它们进行明确的排序显示)。

表组成数据库,一个由某个PostgreSQL服务器管理的数据库 集合组成一个数据库集群

2.2 创建表

你可以通过声明表的名字和所有字段的名字及其类型来创建表:

CREATE TABLE weather (
    city            varchar(80),
    temp_lo         int,           -- low temperature
    temp_hi         int,           -- high temperature
    prcp            real,          -- precipitation
    date            date
);

你可以在psql里连换行符一起键入这些东西。psql 可以识别该命令直到分号才结束。

你可以在 SQL 命令中自由使用空白(空格,tab,换行符)。这意味着你可以用 和上面不同的对齐方式(甚至在同一行中)键入命令。双划线("--") 引入注释,任何跟在它后面的东西直到该行的结尾都被忽略。SQL 是对关键字 和标识符大小写不敏感的语言,只有在标识符用双引号包围时才能保留它们的 大小写属性(上面没有这么干)。

varchar(80)声明一个可以存储最长 80 个字符的任意字符串的数据类型。 int是普通的整数类型。real是一种用于存储单精度浮点数 的类型。date类型应该可以自解释。没错,类型为date的字 段名字也是date。这么做可能比较方便,也可能容易让人 混淆,你自己看啦。

PostgreSQL支持标准的SQL类型: int, smallint, real, double precision, char(*N*), varchar(*N*), date, time, timestamp,和 interval,还支持其它的通用类型和丰富的几何类型。PostgreSQL 允许你自定义任意数量的数据类型。因而类型名并不是语法关键字,除了SQL 标准要求支持的特例外。

第二个例子将保存城市和它们相关的地理位置:

CREATE TABLE cities (
    name            varchar(80),
    location        point
);

point类型就是一个PostgreSQL特有的数据类型的例子。

最后,我们还要提到如果你不再需要某个表,或者你想创建一个不同的表,那么你可以用下面的命令删除它:

DROP TABLE tablename;

2.3 向表中添加行

INSERT语句用于向表中添加行:

INSERT INTO weather VALUES ('San Francisco', 46, 50, 0.25, '1994-11-27');

请注意所有数据类型都使用了相当明了的输入格式。那些不是简单数字值的常量必 需用单引号(')包围,就像在例子里一样。date类型实际上 对可接收的格式相当灵活,不过在本教程里,我们应该坚持使用这里显示的格式。

point类型要求一个坐标对作为输入,如下:

INSERT INTO cities VALUES ('San Francisco', '(-194.0, 53.0)');

到目前为止使用的语法要求你记住字段的顺序。一个可选的语法允许你明确地列出字段:

INSERT INTO weather (city, temp_lo, temp_hi, prcp, date)
    VALUES ('San Francisco', 43, 57, 0.0, '1994-11-29');

如果需要,你可以用另外一个顺序列出字段或者是忽略某些字段,比如说,我们不知道降水量:

INSERT INTO weather (date, city, temp_hi, temp_lo)
    VALUES ('1994-11-29', 'Hayward', 54, 37);

许多开发人员认为明确列出字段要比依赖隐含的顺序是更好的风格。

你还可以使用COPY从文本文件中装载大量数据。这么干通常更快, 因为COPY命令就是为这类应用优化的,只是比INSERT 少一些灵活性。比如:

COPY weather FROM '/home/user/weather.txt';

2.3 查询一个表

要从一个表中检索数据就是查询这个表。SQL 的SELECT语句就是做这个用途的。该语句分为选择列表(列出要返回的字 段)、表列表(列出从中检索数据的表)、以及可选的条件(声明任意限制)。比如,要检索表 weather的所有行,键入:

SELECT * FROM weather;

这里的*是"所有字段"的缩写。 [1] 因此同样的结果可以用下面的语句获得:

SELECT city, temp_lo, temp_hi, prcp, date FROM weather;

而输出应该是:

     city      | temp_lo | temp_hi | prcp |    date
---------------+---------+---------+------+------------
 San Francisco |      46 |      50 | 0.25 | 1994-11-27
 San Francisco |      43 |      57 |    0 | 1994-11-29
 Hayward       |      37 |      54 |      | 1994-11-29
(3 rows)

你可以在选择列表中写任意表达式,而不仅仅是字段列表。比如,你可以:

SELECT city, (temp_hi+temp_lo)/2 AS temp_avg, date FROM weather;

这样应该得到:

     city      | temp_avg |    date
---------------+----------+------------
 San Francisco |       48 | 1994-11-27
 San Francisco |       50 | 1994-11-29
 Hayward       |       45 | 1994-11-29
(3 rows)

请注意这里的AS子句是如何给输出字段重新命名的。AS 子句是可选的。

一个查询可以使用WHERE子句进行"修饰",声明需要哪些行。

WHERE子句包含一个布尔表达式(值为真),只有那些布尔表达式为 真的行才会被返回。允许你在条件中使用常用的布尔操作符(AND, OR,NOT)。比如,下面的查询检索旧金山的下 雨天的天气:

SELECT * FROM weather
    WHERE city = 'San Francisco' AND prcp > 0.0;

结果:

     city      | temp_lo | temp_hi | prcp |    date
---------------+---------+---------+------+------------
 San Francisco |      46 |      50 | 0.25 | 1994-11-27
(1 row)

你可以要求返回的查询是排好序的:

SELECT * FROM weather
    ORDER BY city;
     city      | temp_lo | temp_hi | prcp |    date
---------------+---------+---------+------+------------
 Hayward       |      37 |      54 |      | 1994-11-29
 San Francisco |      43 |      57 |    0 | 1994-11-29
 San Francisco |      46 |      50 | 0.25 | 1994-11-27

在这个例子里,排序的顺序并非绝对清晰的,因此你可能看到 San Francisco 行 随机的排序。但是如果你使用下面的语句,那么就总是会得到上面的结果:

SELECT * FROM weather
    ORDER BY city, temp_lo;

你可以要求查询的结果消除重复行的输出:

SELECT DISTINCT city FROM weather;
     city
---------------
 Hayward
 San Francisco
(2 rows)

再次声明,结果行的顺序可能是随机的。你可以组合使用DISTINCTORDER BY来获取一致的结果: [2]

SELECT DISTINCT city
    FROM weather
    ORDER BY city;

备注

[1] 虽然SELECT *对于即兴的查询是有用的,但我们普遍认为在生产代码中 这是很糟糕的风格,因为给表增加一个字段就改变了结果。
[2] 在一些数据库系统里,包括老版本的PostgreSQL, DISTINCT自动对行进行排序,因此ORDER BY 是多余的。但是这一点并不是 SQL 标准的要求,并且目前的PostgreSQL 并不保证DISTINCT导致数据行被排序。

2.3 在表间连接

查询可以一次访问多个表, 或者用某种方式访问一个表,而同时处理该表的多个行。一个同时访问同一个或 者不同表的多个行的查询叫连接查询。举例来说,比如你 想列出所有天气记录以及这些记录相关的城市。要实现这个目标,我们需要拿 weather表每行的city字段和cities 表所有行的name字段进行比较,并选取那些这些数值相匹配的行。

这个任务可以用下面的查询来实现:

SELECT *
    FROM weather, cities
    WHERE city = name;

     city      | temp_lo | temp_hi | prcp |    date    |     name      | location
---------------+---------+---------+------+------------+---------------+-----------
 San Francisco |      46 |      50 | 0.25 | 1994-11-27 | San Francisco | (-194,53)
 San Francisco |      43 |      57 |    0 | 1994-11-29 | San Francisco | (-194,53)
(2 rows)

观察结果集的两个方面:

  • 没有城市 Hayward 的结果行。这是因为在cities表里面没有与 Hayward 匹配的行,所以连接忽略了weather表里的不匹配行。我们稍后将 看到如何修补这个问题。

  • 有两个字段包含城市名。这是正确的,因为weathercities表的字段是接在一起的。不过,实际上我们不想要这些, 因此你将可能希望明确列出输出字段而不是使用*

    SELECT city, temp_lo, temp_hi, prcp, date, location
        FROM weather, cities
        WHERE city = name;
    

因为这些字段的名字都不一样,所以分析器自动找出它们属于哪个表,但是如果两个 表中有重复的字段名,你就必须使用字段全称限定你想要的字段:

SELECT weather.city, weather.temp_lo, weather.temp_hi,
       weather.prcp, weather.date, cities.location
    FROM weather, cities
    WHERE cities.name = weather.city;

一般认为在连接查询里使用字段全称是很好的风格,这样,即使在将来向其中一个 表里添加了同名字段也不会引起混淆。

到目前为止,这种类型的连接查询也可以用下面这样的形式写出来:

SELECT *
    FROM weather INNER JOIN cities ON (weather.city = cities.name);

这个语法并非像上面那个那么常用,我们在这里写出来是为了让你更容易了解后面 的主题。

现在我们将看看如何能把 Hayward 记录找回来。我们想让查询干的事是扫描 weather表,并且对每一行都找出匹配的cities 表里面的行。如果没有找到匹配的行,那么需要一些"空值"代替 cities表的字段。这种类型的查询叫 外连接 (我们在此之前看到的连接都是内连接)。这样的命令看起来像这样:

SELECT *
    FROM weather LEFT OUTER JOIN cities ON (weather.city = cities.name);

     city      | temp_lo | temp_hi | prcp |    date    |     name      | location
---------------+---------+---------+------+------------+---------------+-----------
 Hayward       |      37 |      54 |      | 1994-11-29 |               |
 San Francisco |      46 |      50 | 0.25 | 1994-11-27 | San Francisco | (-194,53)
 San Francisco |      43 |      57 |    0 | 1994-11-29 | San Francisco | (-194,53)
(3 rows)

这个查询是一个左外连接,因为连接操作符(LEFT OUTER JOIN) 左边的表中的行在输出中至少出现一次,而右边的表只输出那些与左边的表中的某些行匹 配的行。如果输出的左表中的行没有右表中的行与其对应,那么右表中的字段将填充为 NULL 。

我们也可以把一个表和它自己连接起来。这叫自连接。 比如,假设我们想找出那些在其它天气记录的温度范围之外的天气记录。 这样我们就需要拿weather表里每行的temp_lotemp_hi 字段与weather表里其它行的temp_lotemp_hi字段进行比较。我们可以用下面的查询实现这个目标:

SELECT W1.city, W1.temp_lo AS low, W1.temp_hi AS high,
    W2.city, W2.temp_lo AS low, W2.temp_hi AS high
    FROM weather W1, weather W2
    WHERE W1.temp_lo < W2.temp_lo
    AND W1.temp_hi > W2.temp_hi;

     city      | low | high |     city      | low | high
---------------+-----+------+---------------+-----+------
 San Francisco |  43 |   57 | San Francisco |  46 |   50
 Hayward       |  37 |   54 | San Francisco |  46 |   50
(2 rows)

在这里我们把 weather 表重新标记为W1W2以区分连接的左边和右边。 你还可以用这样的别名在其它查询里节约一些敲键,比如:

SELECT *
    FROM weather w, cities c
    WHERE w.city = c.name;

以后会经常碰到这样的缩写。

2.4 聚合函数

和大多数其它关系数据库产品一样,PostgreSQL支持聚合函数。 一个聚合函数从多个输入行中计算出一个结果。比如,我们有在一个行集合上计算count(数目), sum(总和),avg(均值),max(最大值), min(最小值)的函数。

比如,我们可以用下面的语句找出所有低温中的最高温度:

SELECT max(temp_lo) FROM weather;

 max
-----
  46
(1 row)

如果我们想知道该读数发生在哪个城市,可能会用:

SELECT city FROM weather WHERE temp_lo = max(temp_lo);     错误

不过这个方法不能运转,因为聚合函数max不能用于WHERE 子句中。存在这个限制是因为WHERE子句决定哪些行可以进入聚合阶段; 因此它必需在聚合函数之前计算。不过,我们可以用其它方法实现这个目的; 这里我们使用子查询

SELECT city FROM weather
    WHERE temp_lo = (SELECT max(temp_lo) FROM weather);
     city
---------------
 San Francisco
(1 row)

这样做是可以的,因为子查询是一次独立的计算,它独立于外层查询计算自己的聚合。

聚合同样也常用于 GROUP BY子句。比如,我们可以获取每个城市低温的最高值:

SELECT city, max(temp_lo)
    FROM weather
    GROUP BY city;

     city      | max
---------------+-----
 Hayward       |  37
 San Francisco |  46
(2 rows)

这样每个城市一个输出。每个聚合结果都是在匹配该城市的行上面计算的。 我们可以用HAVING过滤这些分组:

SELECT city, max(temp_lo)
    FROM weather
    GROUP BY city
    HAVING max(temp_lo) < 40;
  city   | max
---------+-----
 Hayward |  37
(1 row)

这样就只给出那些temp_lo值曾经有低于 40 度的城市。最后, 如果我们只关心那些名字以"S"开头的城市,我们可以用:

SELECT city, max(temp_lo)
    FROM weather
    WHERE city LIKE 'S%'(1)
    GROUP BY city
    HAVING max(temp_lo) < 40;

理解聚合和SQL的WHEREHAVING 子句之间的关系非常重要。WHEREHAVING的基本区别如下: WHERE在分组和聚合计算之前选取输入行(它控制哪些行进入聚合计算), 而HAVING在分组和聚合之后选取输出行。因此,WHERE 子句不能包含聚合函数;因为试图用聚合函数判断那些行将要输入给聚合运算是没有意义的。 相反,HAVING子句总是包含聚合函数。当然,你可以写不使用聚合的HAVING 子句,但这样做没什么好处,因为同样的条件用在WHERE阶段会更有效。

在前面的例子里,我们可以在WHERE里应用城市名称限制,因为它不需要聚合。 这样比在HAVING里增加限制更加高效,因为我们避免了为那些未通过 WHERE检查的行进行分组和聚合计算。

2.5 更新

你可以用UPDATE命令更新现有的行。假设你发现所有 11 月 28 日的温度计数都低了两度,那么你就可以用下面的方式更新数据:

UPDATE weather
    SET temp_hi = temp_hi - 2,  temp_lo = temp_lo - 2
    WHERE date > '1994-11-28';

看看数据的新状态:

SELECT * FROM weather;

     city      | temp_lo | temp_hi | prcp |    date
---------------+---------+---------+------+------------
 San Francisco |      46 |      50 | 0.25 | 1994-11-27
 San Francisco |      41 |      55 |    0 | 1994-11-29
 Hayward       |      35 |      52 |      | 1994-11-29
(3 rows)

2.6 删除

数据行可以用DELETE命令从表中删除。假设你对 Hayward 的天气不再感兴趣, 那么你可以用下面的命令把那些行从表中删除:

DELETE FROM weather WHERE city = 'Hayward';

所有属于 Hayward 的天气记录都将被删除。

SELECT * FROM weather;

     city      | temp_lo | temp_hi | prcp |    date
---------------+---------+---------+------+------------
 San Francisco |      46 |      50 | 0.25 | 1994-11-27
 San Francisco |      41 |      55 |    0 | 1994-11-29
(2 rows)

使用下面形式的语句时一定要小心:

DELETE FROM tablename;

如果没有指定条件,DELETE将从指定表中删除所有行。 做这些之前系统不会请求你确认!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容