[SQL必知必会] 读书笔记
第1课 数据库
这一课介绍SQL究竟是什么,它能做什么事情。
1.1 数据库基础
下面是一些数据库概念的简要介绍,如果你刚开始接触数据库,可以由此了解必需的基本知识。
1.1.1 数据库
数据库这个术语的用法很多,但就本书而言(从SQL的角度来看),数据库是一个以某种有组织的方式存储的数据集合。最简单的办法是将数据库想象为一个文件柜。这个文件柜是一个存放数据的物理位置,不管数据是什么,也不管数据是如何组织的。
数据库(database) 保存有组织的数据的容器(通常是一个文件或一组文件)。 |
注意:误用导致馄淆 人们通常用数据库这个术语来代表他们使用的数据库软件,这是不正确的,也因此产生了许多混淆。确切地说,数据库软件应称为数据库管理系统(即DBMS)。数据库是通过DBMS创建和操纵的容器,而具体它宄竟是什么,形式如何,各种数据库都不一样。 |
1.1.2 表
你往文件柜里放资料时,并不是随便将它们扔进某个抽屉就完事了,而是在文件柜中创建文件,然后将相关的资料放入特定的文件中。在数据库领域中,这种文件称为表。表是一种结构化的文件,可用来存储某种特定类型的数据。表可以保存顾客清单、产品目录,或者其他信息清单。
表(table) 某种特定类型数据的结构化清单。 |
1.1.3 列和数据类型
表由列组成。列存储表中某部分的信息。理解列的最好办法是将数据库表想象为一个网格,就像个电子表格那样。网格中每一列存储着某种特定的信息。例如,在顾客表中,一列存储顾客编号,另一列存储顾客姓名。
列 Ccolumn) 表中的一个字段。所有表都是由一个或多个列组成的。 |
数据库中每个列都有相应的数据类型。数据类型(datatype)定义了列可以存储哪些数据种类。例如,如果列中存储的是数字(或许是订单中的物品数),则相应的数据类型应该为数值类型。如果列中存储的是日期、文本、注释、金额等,则应该规定好恰当的数据类型。
数据类型 所允许的数据的类型。每个表列都有相应的数据类型,它限制(或允许)该列中存储的数据。 |
1.1.4 行
表中的数据是按行存储的,所保存的每个记录存储在自己的行内。如果将表想象为网格,网格中垂直的列为表列,水平行为表行。例如,顾客表可以每行存储一个顾客。表中的行编号为记录的编号。
行(row) 表中的一个记录。 |
1.1.5 主键
表中每一行都应该有一列(或几列)可以唯一标识自己,称为主键列,主键列不允许NULL值。例如,顾客表可以使用顾客编号,而订单表可以使用订单编号。
主键(primary key) 一列(或一组列),其值能够唯一标识表中每一行。 |
1.2 什么是SQL
SQL (发音为字母S-Q-L或sequel)是Structured Query Language(结构化查询语言)的缩写。SQL是一种专门用来与数据库沟通的语言。
与其他语言(如英语或Java、C、PHP这样的编程语言)不一样,SQL中只有很少的词,这是有意而为的。设计SQL的目的是很好地完成一项任务 一 提供一种从数据库中读写数据的简单有效的方法。
SQL有如下的优点。
- SQL不是某个特定数据库供应商专有的语言。几乎所有重要的DBMS都支持SQL,所以学习此语言使你几乎能与所有数据库打交道。
- SQL简单易学。它的语句全都是由有很强描述性的英语单词组成,而且这些单词的数目不多。
- SQL虽然看上去很简单,但实际上是一种强有力的语言,灵活使用其语言元素,可以进行非常复杂和高级的数据库操作。
1.3 动手实践
与其他任何语言一样,学习SQL的最好方法是自己动手实践。为此,需要一个数据库和用来测试SQL语句的应用系统。
附录A给出了具体的样例表,并介绍了获得(或创建)它们的详细步骤。附录B介绍在各种应用程序中执行SQL所需的步骤。这里给出下载样例表的地址:http://forta.com/books/0672336073/。
(笔者使用的是MySQL5.6,所以下面的语句若不做介绍,皆默认使用MySQL版本的)
第2课 检索数据
这一课介绍如何使用SELECT语句从表中检索一个或多个数据列。
2.1 SELECT 语句
正如第1课所述,SQL语句是由简单的英语单词构成的。这些单词称为关键字,每个SQL语句都是由一个或多个关键字构成的。最经常使用的SQL语句大概就是SELECT语句了。它的用途是从一个或多个表中检索信息。
关键字(keyword) 作为SQL组成部分的保留字。关键字不能用作表或列的名字。附录E列出了某些经常使用的保留字。 |
为了使用SELECT检索表数据,必须至少给出两条信息一想选择什么,以及从什么地方选择。
2.2 检索单个列
我们将从简单的SQL SELECT语句讲起,此语句如下所示:
输入▼
SELECT prod_name FROM Products;
分析▼
上述语句利用SELECT语句从Products表中检索一个名为prod_name的列。所需的列名写在SELECT关键字之后,FROM关键字指出从哪个表中检索数据。
提示:SQL语句和大小写 请注意,SQL语句不区分大小写,因此SELECT与select是相同的。同样,写成Select也没有关系。许多SQL开发人员喜欢对SQL关键字使用大写,而对列名和表名使用小写,这样做使代码更易于阅读和调试。不过,一定要认识到虽然SQL是不区分大小写的,但是表名、列名和值可能有所不同(这有赖于具体的DBMS及其如何配置)。 |
提示:使用空格 在处理SQL语句时,其中所有空格都被忽略。SQL语句可以写成长长的一行,也可以分写在多行。多数SQL开发人员认为,将SQL语句分成多行更容易阅读和调试。 |
2.3 检索多个行
要想从一个表中检索多个列,仍然使用相同的SELECT语句。唯一的不同是必须在SELECT关键字后给出多个列名,列名之间必须以逗号分隔。
输入▼
SELECT prod_id, prod_name, prod_price FROM Products;
分析▼
这条语句使用SELECT语句从表Products中选择多个数据。在这个例子中,指定了3个列名,列名之间用逗号分隔。
2.4 检索所有列
除了指定所需的列外(如上所述,一个或多个列),SELECT语句还可以检索所有的列而不必逐个列出它们。在实际列名的位置使用星号(*)通配符可以做到这点,如下所示。
输入▼
SELECT * FROM Products;
分析▼
如果给定一个通配符(*),则返回表中所有列。列的顺序一般是列在表定义中出现的物理顺序,但并不总是如此。
2.5 检索不同的值
如前所述,SELECT语句返回所有匹配的行。但是,如果你不希望每个值每次都出现,该怎么办呢?办法就是使用DISTINCT关键字,顾名思义,它指示数据库只返回不同的值。
输入▼
SELECT DISTINCT vend_id FROM Products;
分析▼
SELECT DISTINCT vend_id告诉DBMS只返回不同(具有唯一性)的vend_id行。如果使用DISTINCT关键字,它必须直接放在列名的前面。
警告:不能部分使用DISTINCT DISTINCT关键字作用于所有的列,不仅仅是跟在其后的那一列。例如,你指定SELECT DISTINCT vend_id,prod_price,除非指定的两列完全相同,否则所有的行都会被检索出来。 |
2.6 限制结果
SELECT语句返回指定表中所有匹配的行,很可能是每一行。如果你只想返回第一行或者一定数量的行,该怎么办呢?这是可行的,然而遗憾的是,各种数据库中的这一 SQL实现并不相同。
如果你使用MySQL、MariaDB、PostgreSQL或者SQLite,需要使用LIMIT子句,像这样:
输入▼
SELECT prod_name FROM Products LIMIT 5;
分析▼
上述代码使用SELECT语句来检索单独的一列数据。LIMIT 5指示MySQL等DBMS返回不超过5行的数据。
为了得到后面的5行数据,需要指定从哪儿开始以及检索的行数,像这样:
输入▼
SELECT prod_name FROM Products LIMIT 5 OFFSET 5;
分析▼
LIMIT 5 OFFSET 5指示MySQL等DBMS返回从第5行起的5行数据。第一个数字是指从哪儿开始,第二个数字是检索的行数。
警告:第0行 第一个被检索的行是第0行,而不是第1行。因此,LIMIT 1 OFFSET 1会检索第2行,而不是第1行。 |
提示:MySQL和MariaDB快捷键 MySQL和MariaDB支持简化版的LIMIT 4 OFFSET 3语句,即LIMIT 3,4。使用这个语法,之前的值对应LIMIT,之后的值对应OFFSET。 |
2.7 使用注释
我们先来看行内注释:
输入▼
SELECT prod_name FROM Products; --这是一条注释
分析▼
注释使用--(两个连字符)嵌在行内。--之后的文本就是注释。
你也可以进行多行注释,注释可以在脚本的任何位置停止和开始。
输入▼
/* SELECT prod_name, vend_id
FROM Products; */
SELECT prod_name FROM Products;
分析▼
注释从/*开始,到*/结束,/*和*/之间的任何内容都是注释。这种方式常用于给代码加注释,就如这个例子演示的。
第3课 排序检索数据
这一课讲授如何使用SELECT语句的ORDER BY子句,根据需要排序检索出的数据。
3.1 排序数据
为了明确地排序用SELECT语句检索出的数据,可使用ORDER BY子句。ORDER BY子句取一个或多个列的名字,据此对输出进行排序。请看下面的例子:
输入▼
SELECT prod_name FROM Products
ORDER BY prod_name;
分析▼
ORDER BY子句,指示DBMS软件对prod_name列以字母顺序排序数据。
注意:ORDER BY子句的位置 在指定一条ORDER BY子句时,应该保证它是SELECT语句中最后一条子句。如果它不是最后的子句,将会出现错误消息。 |
3.2 按多个列排序
下面的代码检索3个列,并按其中两个列对结果进行排序 一 首先按价格,然后按名称排序。
输入▼
SELECT prod_id, prod_price, prod_name FROM Products
ORDER BY prod_price, prod_name;
分析▼
对于上述例子中的输出,仅在多个行具有相同的prod_price值时才对产品按prod_name进行排序。如果prod_price列中所有的值都是唯一的,则不会按prod_name排序。(先对prod_price排序,再对prod_name排序)
3.3 按列位置排序
除了能用列名指出排序顺序外,ORDER BY还支持按相对列位置进行排序。为理解这一内容,我们来看个例子:
输入▼
SELECT prod_id, prod_price, prod_name FROM Products
ORDER BY 2, 3;
分析▼
SELECT清单中指定的是选择列的相对位置而不是列名。ORDER BY 2 表示按SELECT清单中的第二个列prod_name进行排序。ORDER BY 2, 3 表示先按prod_price,再按prod_name进行排序。
3.4 指定排序方向
数据排序不限于升序排序(从A到Z),这只是默认的排序顺序。还可以使用ORDER BY子句进行降序(从Z到A)排序。为了进行降序排序,必须 指定DESC关键字。例子如下:
输入▼
SELECT prod_id, prod_price, prod_name FROM Products
ORDER BY prod_price DESC, prod_name;
分析▼
DESC关键字只应用到直接位于其前面的列名。在上例中,只对prod_price列指定DESC,对prod_name列不指定。因此,prod_price列以降序排序,而prod_name列(在每个价格内)仍然按标准的升序排序。
警告:在多个列上降序排序 如果想在多个列上进行降序排序,必须对每一列指定DESC关键字。 |
第4课 过滤数据
这一课将讲授如何使用SELECT语句的WHERE子句指定搜索条件。
4.1 使用WHERE子句
在SELECT语句中,数据根据WHERE子句中指定的搜索条件进行过滤。WHERE子句在表名(FROM子句)之后给出,如下所示:
输入▼
SELECT prod_name, prod_price FROM Products
WHERE prod_price = 3.49;
分析▼
这条语句从products表中检索两个列,但不返回所有行,只返回prod_price值为3.49的行。
4.2 WHERE子句操作符
SQL支持下表列出的所有条件操作符。
操作符 | 说明 |
---|---|
= | 等于 |
!= | 不等于 |
< | 小于 |
<= | 小于等于 |
> | 大于 |
>= | 大于等于 |
BETWEEN | 在指定的两个值之间 |
IS NULL | 为NULL值 |
4.2.1 范围内检查
要检查某个范围的值,可以使用BETWEEN操作符。其语法与其他WHERE子句的操作符稍有不同,因为它需要两个值,即范围的开始值和结束值。下面的例子说明如何使用BETWEEN操作符,它检索价格在5美元和10美元之间的所有产品:
输入▼
SELECT prod_name, prod_price FROM Products
WHERE prod_price BETWEEN 5 AND 10;
分析▼
从这个例子可以看到,在使用BETWEEN时,必须指定两个值——所需范围的低端值和高端值。这两个值必须用AND关键字分隔。
4.2.2 空值检查
确定值是否为NULL,不能简单地检查是否=NULL。SELECT语句有一个特殊的WHERE子句,可用来检查具有NULL值的列。这个WHERE子句就是IS NULL子句。其语法如下:
输入▼
SELECT prod_name FROM Products
WHERE prod_price IS NULL;
分析▼
这条语句返回所有没有价格(空prod_price字段,不是价格为0)的产品。
第5课 高级数据过滤
这一课讲授如何组合WHERE子句以建立功能更强、更高级的搜索条件。我们还将学习如何使用NOT和IN操作符。
5.1 组合WHERE子句
第4课介绍的所有WHERE子句在过滤数据时使用的都是单一的条件。为了进行更强的过滤控制,SQL允许给出多个WHERE子句。这些子句有两种使用方式,即以AND子句或OR子句的方式使用。
5.1.1 AND操作符
要通过不止一个列进行过滤,可以使用AND操作符给WHERE子句附加条件。下面的代码给出了一个例子:
输入▼
SELECT prod_id, prod_price, prod_name FROM Products
WHERE vend_id = 'DLL01' AND prod_price <= 4;
分析▼
此SQL语句检索由供应商DLL01制造且价格小于等于4美元的所有产品的名称和价格。
5.1.2 OR操作符
OR操作符与AND操作符正好相反,它指示DBMS检索匹配任一条件的行。事实上,许多DBMS在OR WHERE子句的第一个条件得到满足的情况下, 就不再计算第二个条件了。例子如下:
输入▼
SELECT prod_name, prod_price FROM Products
WHERE vend_id = 'DLL01' OR vend_id = 'BRS01';
分析▼
此SQL语句检索由任一个指定供应商制造的所有产品的产品名和价格。
5.2 IN操作符
IN操作符用来指定条件范围,范围中的每个条件都可以进行匹配。IN取一组由逗号分隔、括在圆括号中的合法值。下面的例子说明了这个操作符:
输入▼
SELECT prod_name, prod_price FROM Products
WHERE vend_id IN ( 'DLL01', 'BRS01' );
分析▼
此SELECT语句检索由供应商DLL01和BRS01制造的所有产品。IN操作符后跟由逗号分隔的合法值,这些值必须括在圆括号中。 你可能会猜测IN操作符完成了与OR相同的功能,恭喜你猜对了!
5.3 NOT操作符
WHERE子句中的NOT操作符有且只有一个功能,那就是否定其后所跟的任何条件。NOT关键字可以用在要过滤的列前,而不仅是在其后。下面的例子说明了这个操作符:
输入▼
SELECT prod_name FROM Products
WHERE NOT vend_id = 'DLL01';
分析▼
这里的NOT否定跟在其后的条件,因此,DBMS不是匹配vend_id为DLL01,而是匹配非DLL01之外的所有东西。
第6课 用通配符进行过滤
这一课介绍什么是通配符、如何使用通配符以及怎样使用LIKE操作符进行通配搜索,以便对数据进行复杂过滤。
6.1 LIKE操作符
利用通配符,可以创建比较特定数据的搜索模式。例如,如果你想找出名称包含bean bag的所有产品,可以构造一个通配符搜索模式,找出在产品名的任何位置出现bean bag的产品。
6.1.1 百分号(%)通配符
最常使用的通配符是百分号(%)。在搜索串中,%表示任何字符出现任意次数。例如,为了找出所有以词Fish起头的产品,可发布以下SELECT语句:
输入▼
SELECT prod_id, prod_name FROM Products
WHERE prod_name LIKE 'Fish%';
分析▼
此例子使用了搜索模式'Fish%’。在执行这条子句时,将检索任意以Fish起头的词。%告诉DBMS接受Fish之后的任意字符,不管它有多少字符。
6.1.2 下划线(_)通配符
另一个有用的通配符是下划线(_)。下划线的用途与%—样,但它只匹配单个字符,而不是多个字符。例子如下:
输入▼
SELECT prod_id,prod_name FROM Products
WHERE prod_name LIKE '__ inch teddy bear';
分析▼
这个WHERE子句中的搜索模式给出了后面跟有文本的两个通配符。
6.1.3 方括号([])通配符
方括号([])通配符用来指定一个字符集,它必须匹配指定位置(通配符的位置)的一个字符。例如,找出所有名字以J或M起头的联系人,可进行如下查询:
输入▼
SELECT cust_contact FROM Customers
WHERE cust_contact LIKE '[JM]%';
分析▼
这一搜索模式使用了两个不同的通配符。[JM]匹配任何以方括号中字母开头的联系人名,它也只能匹配单个字符。[JM]之后的%通配符匹配第一个字符之后的任意数目的字符,返回所需结果。
第7课 创建计算字段
这一课介绍什么是计算字段,如何创建计算字段,以及如何从应用程序中使用别名引用它们。
7.1 计算字段
存储在数据库表中的数据一般不是应用程序所需要的格式,下面举几个例子。
- 需要显示公司名,同时还需要显示公司的地址,但这两个信息存储在不同的表列中。
- 物品订单表存储物品的价格和数量,不存储每个物品的总价格(用价格乘以数量即可)。但为打印发票,需要物品的总价格。
- 需要根据表数据进行诸如总数、平均数的计算。
在上述每个例子中,存储在表中的数据都不是应用程序所需要的。我们需要直接从数据库中检索出转换、计算或格式化过的数据,而不是检索出数据,然后再在客户端应用程序中重新格式化。
这就是计算字段可以派上用场的地方了。与前几课介绍的列不同,计算字段并不实际存在于数据库表中。计算字段是运行时在SELECT语句内创建的。
7.2 拼接字段
为了说明如何使用计算字段,我们来举一个简单例子,创建由两列组成的标题。在SQL中的SELECT语句中,可使用一个特殊的操作符来拼接两个列。根据你所使用的DBMS,此操作符可用加号(+)或两个竖杠(II)表示。在MySQL和MariaDB中,必须使用特殊的函数。
下面是MySQL和MariaDB中使用的语句:
输入▼
SELECT Concat(vend_name, ' ', vend_country) FROM Vendors;
分析▼
使用Concat函数拼接vend_name,一个空格' '和vend_country。
使用别名
SELECT语句可以很好地拼接地址字段,但这个新计算列的名字是什么呢?实际上它没有名字,它只是一个值。如 果仅在SQL查询工具中查看一下结果,这样没有什么不好。但是,一个未命名的列不能用于客户端应用中,因为客户端没有办法引用它。
为了解决这个问题,SQL支持列别名。别名(alias)是一个字段或值的替换名。别名用AS关键字赋予。请看下面的SELECT语句:
输入▼
SELECT Concat(vend_name, ' ', vend_country) AS vend_title FROM Vendors;
分析▼
这里的计算字段之后跟了文本AS vend_title,它指示SQL创建一个包含指定计算结果的名为vend_title的计算字段。现在列名为vend_title,任何客户端应用都可以按名称引用这个列,就像它是一个实际的表列一样。
7.3 执行算数计算
计算字段的另一常见用途是对检索出的数据进行算术计算。例如,可以汇总2008年这一年订单物品的价格(单价乘以订购数量):
输入▼
SELECT prod_id, quantity, item_price,quantity*item_price AS expanded_price
FROM OrderItems WHERE order_num = 20008;
分析▼
这里的计算字段之后跟了文本AS vend_title,它指示SQL创建一个包含指定计算结果的名为vend_title的计算字段。现在列名为vend_title,任何客户端应用都可以按名称引用这个列,就像它是一个实际的表列一样。
SQL支持基本的加减乘除这4种算术操作符。
第8课 使用数据处理函数
这一课介绍什么是函数,DBMS支持何种函数,以及如何使用这些函数;还将讲解为什么SQL函数的使用可能会带来问题。
8.1 函数
与大多数其他计算机语言一样,SQL也可以用函数来处理数据。函数一般是在数据上执行的,为数据的转换和处理提供了方便。
函数带来的问题
与几乎所有DBMS都等同地支持SQL语句(如SELECT)不同,每一个DBMS都有特定的函数。事实上,只有少数几个函数被所有主要的DBMS等同地支持。虽然所有类型的函数一般都可以在每个DBMS中使用,但各个函数的名称和语法可能极其不同。这就表示为特定SQL实现编写的代码在其他实现中可能不正常。
8.2 使用函数
大多数SQL实现支持以下类型的函数。
- 用于处理文本字符串(如删除或填充值,转换值为大写或小写)的文本函数。
- 用于在数值数据上进行算术操作(如返回绝对值,进行代数运算)的数值函数。
- 用于处理日期和时间值并从这些值中提取特定成分(如返回两个日期之差,检查日期有效性)的日期和时间函数。
- 返回DBMS正使用的特殊信息(如返回用户登录信息)的系统函数。
8.2.1 文本处理函数
下面例子使用的是UPPER()函数:
输入▼
SELECT vend_name, UPPER(vend_name) AS vend_name_upcase FROM Vendors;
分析▼
UPPER()将文本转换为大写。
下表列出了一些常用的文本处理函数。
函 数 | 说 明 |
---|---|
LEFT()(或使用子字符串函数) | 返回字符串左边的字符 |
LENGTH() (也使用 DATALENGTH()或LEN()) | 返回字符串的长度 |
LOWER() (Access使用 LCASE()) | 将字符串转换为小写 |
LTRIM() | 去掉字符串左边的空格 |
RIGHT() (或使用子字符串函数) | 返回字符串右边的字符 |
RTRIM() | 去掉字符串右边的空格 |
SOUNDEX() | 返回字符串的SOUNDEX值 |
UPPER() (Access使用UCASE()) | 将字符串转换为大写 |
8.2.2 日期和时间处理函数
MySQL和MariaDB用户可使用名为YEAR()的函数从日期中提取年份:
输入▼
SELECT order_num FROM Orders
WHERE YEAR(order_date) = 2012;
分析▼
DBMS提供的功能远不止简单的日期成分提取。大多数DBMS具有比较日期、执行基于日期的运算、选择日期格式等的函数。但是,可以看到,不同DBMS的日期-时间处理函数可能不同。关于具体DBMS支持的日期-时间处理函数,请参阅相应的文档。
8.2.3 数值处理函数
数值处理函数仅处理数值数据。这些函数一般主要用于代数、三角或几何运算,因此不像字符串或日期-时间处理函数使用那么频繁。具有讽刺意味的是,在主要DBMS的函数中,数值函数是最一致、最统一的函数。下表列出了一些常用的数值处理函数。
函 数 | 说 明 |
---|---|
ABS() | 返回一个数的绝对值 |
COS() | 返回一个角度的余弦 |
EXP() | 返回一个数的指数值 |
PI() | 返回圆周率 |
SIN() | 返回一个角度的正弦 |
SQRT() | 返回一个数的平方根 |
TAN() | 返回一个角度的正切 |
第9课 汇总数据
这一课介绍什么是SQL的聚集函数,如何利用它们汇总表的数据。
9.1 聚集函数
我们经常需要汇总数据而不用把它们实际检索出来,为此SQL提供了专门的函数。使用这些函数,SQL查询可用于检索数据,以便分析和报表生成。这种类型的检索例子有:
- 确定表中行数(或者满足某个条件或包含某个特定值的行数);
- 获得表中某些行的和;
- 找出表列(或所有行或某些特定的行)的最大值、最小值、平均值。
为方便这种类型的检索,SQL给出了5个聚集函数,见下表。这些函数能进行上述检索。与前一章介绍的数据处理函数不同,SQL的聚集函数在各种主要SQL实现中得到了相当一致的支持。
函 数 | 说 明 |
---|---|
AVG() | 返回某列的平均值 |
COUNT() | 返回某列的行数 |
MAX() | 返回某列的最大值 |
MIN() | 返回某列的最小值 |
SUM() | 返回某列值之和 |
9.1.1 AVG()函数
AVG()通过对表中行数计数并计算其列值之和,求得该列的平均值。下面的例子使用AVG()返回Products表中所有产品的平均价格:
输入▼
SELECT AVG(prod_price) AS avg_price FROM Products
WHERE vend_id = 'DLL01';
分析▼
此SELECT语句返回值avg_price,它包含Products表中所有产品的平均价格。
9.1.2 COUNT()函数
C0UNT()函数进行计数。可利用C0UNT()确定表中行的数目或符合特定条件的行的数目。C0UNT()函数有两种使用方式:
- 使用C0UNT()对表中行的数目进行计数,不管表列中包含的是空值(NULL)还是非空值。
- 使用COUNT(column)对特定列中具有值的行进行计数,忽略NULL值。
输入▼
SELECT COUNT(*) AS num_cust FROM Customers;
分析▼
在此例子中,利用C0UNT(*)对所有行计数,不管行中各列有什么值。计数值在num_cust中返回。
9.2 聚集不同值
以上5个聚集函数都可以如下使用:
- 对所有行执行计算,指定ALL参数或不指定参数(因为ALL是默认行为)。
- 只包含不同的值,指定DISTINCT参数。
下面的例子使用AVG()函数返回特定供应商提供的产品的平均价格。它与上面的SELECT语句相同,但使用了DISTINCT参数,因此平均值只考虑各个不同的价格:
输入▼
SELECT AVG(DISTINCT prod_price) AS avg_price FROM Products
WHERE vend_id = 'DLL01';
分析▼
使用了DISTINCT后,会排除相同的价格。
9.3 组合聚集函数
目前为止的所有聚集函数例子都只涉及单个函数。但实际上,SELECT语句可根据需要包含多个聚集函数。请看下面的例子:
输入▼
SELECT COUNT(*) AS num_items,MIN(prod_price) AS price_min,
MAX(prod_price) AS price_max, AVG(prod_price) AS price_avg FROM Products;
分析▼
这里用单条SELECT语句执行了4个聚集计算,返回4个值(Products表中物品的数目,产品价格的最高值、最低值以及平均值)。
第10课 分组数据
这一课介绍如何分组数据,以便汇总表内容的子集。这涉及两个新SELECT语句子句:GROUP BY子句和HAVING子句。
10.1 数据分组
如果要返回每个供应商提供的产品数目,该怎么办?或者返回只提供一项产品的供应商的产品,或者返回提供10个以上产品的供应商的产品, 怎么办?这就是分组大显身手的时候了。使用分组可以将数据分为多个逻辑组,对每个组进行聚集计算。
10.2 创建分组
分组是使用SELECT语句的GROUP BY子句建立的。理解分组的最好办法是看一个例子:
输入▼
SELECT vend_id,COUNT(*) AS num_prods FROM Products GROUP BY vend_id;
输出▼
+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
| BRS01 | 3 |
| DLL01 | 4 |
| FNG01 | 2 |
+---------+-----------+
分析▼
上面的SELECT语句指定了两个列:vend_id包含产品供应商的ID,num_prods为计算字段,GROUP BY子句指示DBMS 按vend_id排序并分组数据。这就会对每个vend_id而不是整个表计算num_prods—次。
10.3 过滤分组
除了能用GROUP BY分组数据外,SQL还允许过滤分组,规定包括哪些分组,排除哪些分组。例如,你可能想要列出至少有两个订单的所有顾客。
SQL为此提供了另一个子句,就是HAVING子句。HAVING非常类似于WHERE。事实上,目前为止所学过的所有类
型的WHERE子句都可以用HAVING来替代。唯一的差别是,WHERE过滤行,而HAVING过滤分组。
输入▼
SELECT cust_id,COUNT(*) AS orders FROM Orders
GROUP BY cust_id HAVING COUNT(*) >= 2;
分析▼
这条SELECT语句的前三行类似于上面的语句。最后一行增加了HAVING子句,它过滤COUNT(*) >= 2(两个以上订单)的那些分组。
第11课 使用子查询
这一课介绍什么是子查询,如何使用它们。
11.1 子查询
SQL还允许创建子查询(subquery),即嵌套在其他查询中的查询。为什么要这样做呢?理解这个概念的最好方法是考察几个例子。
11.2 利用子查询进行过滤
订单存储在两个表中。每个订单包含订单编号、客户 ID、订单日期,在Orders表中存储为一行。各订单的物品存储在相关的Orderltems表中。Orders表不存储顾客信息,只存储顾客ID。顾客的实际信息存储在Customers表中。
现在,假如需要列出订购物品RGAN01的所有顾客,应该怎样检索?下面列出具体的步骤。
- 检索包含物品RGAN01的所有订单的编号。
- 检索具有前一步骤列出的订单编号的所有顾客的ID。
- 检索前一步骤返回的所有顾客ID的顾客信息。
上述每个步骤都可以单独作为一个查询来执行。可以把一条SELECT语句返回的结果用于另一条SELECT语句的WHERE子句。也可以使用子查询来把3个查询组合成一条语句。使用子查询的语句如下所示:
输入▼
SELECT cust_name, cust_contact FROM Customers
WHERE cust_id IN (SELECT cust_id FROM Orders WHERE order_num IN
(SELECT order_num FROM OrderItems WHERE prod_id = 'RGAN01'));
分析▼
为了执行上述SELECT语句,DBMS实际上必须执行三条SELECT语句。最里边的子查询返回订单号列表,此列表用于其外面的子查询的WHERE子句。外面的子查询返回顾客ID列表,此顾客ID列表用于最外层查询的WHERE子句。最外层查询返回所需的数据。
第12课 联结表
这一课会介绍什么是联结,为什么使用联结,如何编写使用联结的SELECT语句。
12.1 联结
SQL最强大的功能之一就是能在数据查询的执行中联结(join)表。联结是利用SQL的SELECT能执行的最重要的操作,很好地理解联结及其语法是学习SQL的极为重要的部分。
12.1.1 关系表
有一个包含产品目录的数据库表,其中每类物品占一行。对于每一种物品,要存储的信息包括产品描述、价格,以及生产该产品的供应商。
现在有同一供应商生产的多种物品,那么在何处存储供应商名、地址、联系方法等供应商信息呢?将这些数据与产品信息分开存储的理由是:
- 同一供应商生产的每个产品,其供应商信息都是相同的,对每个产品重复此信息既浪费时间又浪费存储空间;
- 如果供应商信息发生变化,例如供应商迁址或电话号码变动,只需修改一次即可;
- 如果有重复数据(即每种产品都存储供应商信息),则很难保证每次输入该数据的方式都相同。不一致的数据在报表中就很难利用。
关系表的设计就是要把信息分解成多个表,一类数据一个表。各表通过某些共同的值互相关联(所以才叫关系数据库)。
在这个例子中可建立两个表:一个存储供应商信息,另一个存储产品信息。Vendors表包含所有供应商信息,每个供应商占一行,具有唯一的标识。
Products表只存储产品信息,除了存储供应商ID (Vendors表的主键)外,它不存储其他有关供应商的信息。Vendors表的主键将Vendors表 与Products表关联,利用供应商ID能从Vendors表中找出相应供应商的详细信息。
12.1.2 为什么使用联结
如前所述,将数据分解为多个表能更有效地存储,更方便地处理,并且可伸缩性更好。但这些好处是有代价的。如果数据存储在多个表中,怎样用一条SELECT语句就检索出数据呢?
答案是使用联结。简单说,联结是一种机制,用来在一条SELECT语句中关联表,因此称为联结。使用特殊的语法,可以联结多个表返回一组输出,联结在运行时关联表中正确的行。
12.2 创建联结
创建联结非常简单,指定要联结的所有表以及关联它们的方式即可。请看下面的例子:
输入▼
SELECT vend_name, prod_name, prod_price FROM Vendors, Products
WHERE Vendors.vend_id = Products.vend_id;
分析▼
SELECT语句与前面所有语句一样指定要检索的列。这里最大的差别是所指定的两列(prod_name和prod_price)在一个表中,而第三列(vend_name)在另一个表中。
这条语句的FROM子句列出了两个表:Vendors和Products。它们就是这条SELECT语句联结的两个表的名字。这两个表用WHERE子句正确地联结,WHERE子句指示DBMS将Vendors表中的vend_id与Products表中的vend_id匹配起来。
可以看到,要匹配的两列指定为Vendors.vend_id和Products.vend_id。这里需要这种完全限定列名,如果只给出vend_id,DBMS就不知道指的是哪一个(每个表中有一个)。
12.3 联结多个表
SQL不限制一条SELECT语句中可以联结的表的数目。创建联结的基本规则也相同。首先列出所有表,然后定义表之间的关系。例如:
输入▼
SELECT prod_name,vend_name, prod_price, quantity FROM OrderItems, Products, Vendors
WHERE Products.vend_id = Vendors.vend_id AND OrderItems.prod_id = Products.prod_id
AND order_num = 20007;
分析▼
这个例子显示订单20007中的物品。订单物品存储在OrderItems表中。每个产品按其产品ID存储,它引用Products表中的产品。这些产品通过供应商ID联结到Vendors表中相应的供应商,供应商ID存储在每个产品的记录中。这里的FROM子句列出三个表,WHERE子句定义这两个联结条件, 而第三个联结条件用来过滤出订单20007中的物品。
第13课 创建高级联结
本课讲解另外一些联结(包括它们的含义和使用方法),介绍如何使用表别名,如何对被联结的表使用聚集函数。
13.1 使用表别名
请看下面的SELECT语句。它与前一课例子中所用的语句基本相同,但改成了使用表别名:
输入▼
SELECT cust_name, cust_contact FROM Customers AS C, Orders AS O, OrderItems AS OI
WHERE C.cust_id = O.cust_id AND OI.order_num = O.order_num AND prod_id = 'RGAN01';
分析▼
可以看到,FROM子句中的三个表全都有别名。Customers AS C使用C作为Customers的别名,如此等等。这样,就可以使用省略的C而不用全名Customers。在这个例子中,表别名只用于WHERE子句。其实它不仅能用于WHERE子句,还可以用于SELECT的列表、ORDER BY子句以及其他语句部分。
13.2 使用不同类型的联结
迄今为止,我们使用的只是内联结或等值联结的简单联结。现在来看三种其他联结:自联结(self-join)、自然联结(natura join)和外联结(outer join)。由于篇幅原因,这里不再过多介绍。
第14课 组合查询
本课讲述如何利用UNION操作符将多条SELECT语句组合成一个结果集。
14.1 组合查询
多数SQL查询只包含从一个或多个表中返回数据的单条SELECT语句。但是,SQL也允许执行多个查询(多条SELECT语句),并将结果作为一个查询结果集返回。这些组合查询通常称为并(union)或复合查询(compound query)。
主要有两种情况需要使用组合查询:
- 在一个查询中从不同的表返回结构数据;
- 对一个表执行多个查询,按一个查询返回数据。
14.2 创建组合查询
可用UNION操作符来组合数条SQL查询。利用UNION,可给出多条SELECT语句,将它们的结果组合成一个结果集。如下所示:
输入▼
SELECT cust_name, cust_contact, cust_email FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI')
UNION
SELECT cust_name,cust_contact,cust_email FROM Customers
WHERE cust_name = 'Fun4All';
分析▼
这条语句由前面的两条SELECT语句组成,之间用UNION关键字分隔。UNION指示DBMS执行这两条SELECT语句,并把输出组合成一个查询结果集。
第15课 插入数据
这一课介绍如何利用SQL的INSERT语句将数据插入表中。
15.1 数据插入
把数据插入表中的最简单方法是使用基本的INSERT语法,它要求指定表名和插入到新行中的值。下面举一个例子:
输入▼
INSERT INTO Customers VALUES('1000000006', 'Toy Land', '123 Any Street', 'New York',
'NY', '11111', 'USA', NULL, NULL);
分析▼
这个例子将一个新顾客插入到Customers表中。存储到表中每一列的数据在VALUES子句中给出,必须给每一列提供一个值。如果某列没有值,如上面的cust_contact和cust_email列,则应该使用NULL值(假定表允许对该列指定空值)。各列必须以它们在表定义中出现的次序填充。
虽然这种语法很简单,但并不安全,应该尽量避免使用。上面的SQL语句高度依赖于表中列的定义次序,还依赖于其容易获得的次序信息。即使可以得到这种次序信息,也不能保证各列在下一次表结构变动后保持完全相同的次序。因此,编写依赖于特定列次序的SQL语句是很不安全的,这样做迟早会出问题。
编写INSERT语句的更安全(不过更烦琐)的方法如下:
输入▼
INSERT INTO Customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country,
cust_contact, cust_email)
VALUES('1000000006', 'Toy Land', '123 Any Street', 'New York', 'NY', '11111', 'USA', NULL, NULL);
分析▼
这个例子与前一个INSERT语句的工作完全相同,但在表名后的括号里明确给出了列名。在插入行时,DBMS将用VALUES列表中的相应值填入列表中的对应项。因为提供了列名,VALUES必须以其指定的次序匹配指定的列名,不一定按各列出现在表中的实际次序。其优点是,即使表的结构改变,这条INSERT语句仍然能正确工作。
15.2 从一个表复制到另一个表
要将一个表的内容复制到一个全新的表(运行中创建的表),可以使用SELECT INTO语句。MariaDB、MySQL、Oracle、PostgreSQL和SQLite使用的语法如下:
输入▼
CREATE TABLE CustCopy AS SELECT * FROM Customers;
分析▼
这条SELECT语句创建一个名为CustCopy的新表,并把Customers表的整个内容复制到新表中。因为这里使用的是SELECT *,所以将在CustCopy表中创建(并填充)与Customers表的每一列相同的列。要想只复制部分的列,可以明确给出列名,而不是使用*通配符。
第16课 更新和删除数据
这一课介绍如何利用UPDATE和DELETE语句进一步操作表数据。
16.1 更新数据
更新(修改)表中的数据,可以使用UPDATE语句。例子如下:
输入▼
UPDATE Customers SET cust_contact = 'Sam Roberts', cust_email = 'sam@toyland.cxx'
WHERE cust_id = '1000000006';
分析▼
UPDATE语句以WHERE子句结束,它告诉DBMS更新哪一行。没有WHERE子句,DBMS将会用这个电子邮件地址更新Customers表中的所有行,这不是我们希望的。
16.2 删除数据
从一个表中删除数据,使用DELETE语句。下面的语句从Customers表中删除一行:
输入▼
DELETE FROM Customers WHERE cust_id = '1000000006';
分析▼
这条语句很容易理解。DELETE FROM要求指定从中删除数据的表名,WHERE子句过滤要删除的行。在这个例子中,只删除顾客1000000006。如果省略WHERE子句,它将删除表中每个顾客。
第17课 创建和操纵表
这一课讲授创建、更改和删除表的基本知识。
17.1 创建表
用程序创建表,可以使用SQL的CREATE TABLE语句。下面的SQL语句创建本书中所用的Products表:
输入▼
CREATE TABLE Products(
prod id CHAR(10) NOT NULL,
vend id CHAR(10) NOT NULL,
prod name CHAR(254) NOT NULL,
prod price DECIMAL(8,2) NOT NULL DEFAULT 3,
proddesc VARCHAR(1000) NULL
);
分析▼
从上面的例子可以看到,表名紧跟CREATE TABLE关键字。实际的表定义(所有列)括在圆括号之中,各列之间用逗号分隔。有一列的描述增加了DEFAULT 3,指示DBMS,如果不给出价格则使用价格3。
17.2 更新表
更新表定义,可以使用ALTER TABLE语句。因为给已有表增加列可能是所有DBMS都支持的唯一操作,所以我们举个这样的例子:
输入▼
ALTER TABLE Vendors ADD vend_phone CHAR(20);
分析▼
这条语句给Vendors表增加一个名为vend_phone的列,其数据类型为CHAR。删除列,就使用DROP COLUMN col_name
即可。
17.3 删除表
删除表(删除整个表而不是其内容)非常简单,使用DROP TABLE语句即可:
输入▼
DROP TABLE CustCopy;
分析▼
这条语句删除CustCopy表。删除表没有确认,也不能撤销,执行这条语句将永久删除该表。
使用关系规则防止意外删除 许多DBMS允许强制实施有关规则,防止删除与其他表相关联的表。在实施这些规则时,如果对某个表发布一条DROP TABLE语句,且该表是某个关系的组成部分,则DBMS将阻止这条语句执行,直到该关系被删除为止。 |
[SQL必知必会] 读书笔记的更多相关文章
- 读书笔记汇总 - SQL必知必会(第4版)
本系列记录并分享学习SQL的过程,主要内容为SQL的基础概念及练习过程. 书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL i ...
- 读书笔记--SQL必知必会18--视图
读书笔记--SQL必知必会18--视图 18.1 视图 视图是虚拟的表,只包含使用时动态检索数据的查询. 也就是说作为视图,它不包含任何列和数据,包含的是一个查询. 18.1.1 为什么使用视图 重用 ...
- 读书笔记--SQL必知必会--建立练习环境
书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL in 10 Minutes - Fourth Edition> MyS ...
- 读书笔记--SQL必知必会12--联结表
12.1 联结 联结(join),利用SQL的SELECT在数据查询的执行中联结表. 12.1.1 关系表 关系数据库中,关系表的设计是把信息分解成多个表,一类数据一个表,各表通过某些共同的值互相关联 ...
- SQL语法语句总结(《SQL必知必会》读书笔记)
一.SQL语句语法 ALTER TABLE ALTER TABLE 用来更新已存在表的结构. ALTER TABLE tablename (ADD|DROP column datatype [NULL ...
- 《mysql必知必会》读书笔记--存储过程的使用
以前对mysql的认识与应用只是停留在增删改查的阶段,最近正好在学习mysql相关内容,看了一本书叫做<MySQL必知必会>,看了之后对MySQL的高级用法有了一定的了解.以下内容只当读书 ...
- MySQL必知必会1-20章读书笔记
MySQL备忘 目录 目录 使用MySQL 检索数据 排序检索数据 过滤数据 数据过滤 用通配符进行过滤 用正则表达式进行搜索 创建计算字段 使用数据处理函数 数值处理函数 汇总数据 分组数据 使用子 ...
- 《SQL必知必会》学习笔记二)
<SQL必知必会>学习笔记(二) 咱们接着上一篇的内容继续.这一篇主要回顾子查询,联合查询,复制表这三类内容. 上一部分基本上都是简单的Select查询,即从单个数据库表中检索数据的单条语 ...
- 【SQL必知必会笔记(3)】SELECT语句的WHERE子句数据过滤操作
上个笔记主要介绍了利用SELECT语句检索单个/多个/所有列,并利用DISTINCT关键字检索具有唯一性的值.利用LIMIT/OFFSET子句限制结果:以及利用ORDER BY子句排序检索出的数据,主 ...
- 【SQL必知必会笔记(2)】检索数据、排序检索数据
上个笔记中介绍了一些关于数据库.SQL的基础知识,并且创建我们后续练习所需的数据库.表以及表之间的关系,从本文开始进入我们的正题:SQL语句的练习. 文章目录 1.检索数据(SELECT语句) 1.1 ...
随机推荐
- 莎拉公主的困惑(bzoj 2186)
Description 大富翁国因为通货膨胀,以及假钞泛滥,政府决定推出一项新的政策:现有钞票编号范围为1到N的阶乘,但是,政府只发行编号与M!互质的钞票.房地产第一大户沙拉公主决定预测一下大富翁国现 ...
- android开发里跳过的坑——android studio升级完成后eclipse adt无法正常使用
最近有时间,把android studio做了一次升级,升级完成后,悲催的发现eclipse不能正常运行了,网上查了好多资料,试了很多方法都不行,最后把eclipse使用的sdk与AS使用的SDK区分 ...
- Java电商项目-6.实现门户首页数据展示_Redis数据缓存
目录 项目的Github地址 需求介绍 搭建Redis集群环境 下面先描述单机版redis的安装 下面将进行Redis3主3从集群环境搭建 基于SOA架构, 创建门户ashop-portal-web门 ...
- Windows 10+Ubuntu 16.04在MBR分区上安装双系统之后没有Windows 10的启动菜单解决方法
背景: 硬盘分区方式:MBR 硬盘容量256,Windows 100,Ubuntu 156,其中主分区安装的是Windows,Ubuntu安装在逻辑分区上,文件系统为Ext4,整个Ubuntu就挂载在 ...
- Java导出jar并运行
1. 创建manifest.mf文件 在工程下创建manifest.mf文件.文件内容为: Manifest-version: 1.0 Main-Class: SqlCheckUtil.java 注意 ...
- Eclipse 远程tomcat调试程序
Eclipse 远程tomcat调试程序 很多时候我们把代码部署到云服务器上,需要调试的时候可以选择远程调试,既节省时间,效率又高.下面详细介绍如何进行远程调试. 1.1. 创建startup-deb ...
- Oracle数据库导入导出简单备份
oracle数据库简单备份 方法一: 1.导出 exp c##xmq/pwda@orcl owner=c##xmq file=C:/expdb.dmp buffer=8000 2.导入 2.1.删除原 ...
- windows下编译jpeg
项目为了脱离OpenCV,直接采用libjpeg,记录在windows下jpeg安装. 项目地址http://www.ijg.org/ 下载解压缩jpeg-9a 看了看OpenCV中对libjpeg的 ...
- 条款一:仔细区别pointers 和 reference
1.一个reference必须总代表某个对象,没有所谓的null reference.如果你有一个变量,其目的是用来指向(代表)另一个对象,但是也有可能它不指向(代表)另一个对象,那么应该使用poin ...
- 【cocos2d-x 3.7 飞机大战】 决战南海I (三) 敌机实现
如今来实现敌机类 敌机和我方飞机相似,具有生命值.能够发射子弹.而且有自己的运动轨迹.事实上能够为它们设计一个共同的基类,这样能够更方便扩展. 不同的敌机,应设置不同的标识.属性 // 敌机生命值 c ...