第一课 了解SQL

1.1 数据库基础

1.1.1 数据库

1. 从SQL的角度看,数据库是一个以某种有组织的方式存储的数据集合。最简单的办法是将数据库想象为一个文件柜。这个文件柜是一个存放数据的物理位置,不管数据是什么,也不管数据是如何组织的。
数据库(database):保存有组织的数据的容器(通常是一个文件或一组文件)。
注意: 误用导致混淆:人们通常用数据库这个术语来代表他们使用的数据库软件,这不是正确的,也因此产生了许多混淆。确切的说,数据库软件应称为数据库管理系统(DBMS)。数据库是通过DBMS创建和操纵的容器,而具体它究竟是什么,形式如何,各种数据库都不一样。

1.1.2 表

表(table):某种特定类型数据的结构化清单。
1. 这里关键一点在于,存储在表中的数据是同一种类型的数据或清单。绝不应该将顾客的清单与订单的清单存储在同一个数据库中,否则以后的检索和访问会很困难。应该创建两个表,每个清单一个表。
表明:使表名成为唯一的,实际上是数据库名和表名等的组合。有的数据库还使用拥有者的名字作为唯一名的一部分。也就是说,虽然在相同数据库中不能两次使用相同的表名,但在不同的数据库中完全可以使用相同的表名。
1. 表具有一些特性,这些特性定义了数据在表中如何存储,包含存储什么样的数据,数据如何分解,各部分信息如何命名等信息。描述表的这组信息就是所谓的(schema),模式可以用来描述数据库中特定的表,也可以用来描述整个数据库(和其中表的关系)。(我理解为模式,是在创建表的过程中发生的)。
模式:关于数据库和表的布局及特性的信息。

1.1.3 列和数据类型

1. 表由列组成。列存储表中某部分的信息。
列(column):表中的一个字段。所有表都是由一个或多个列组成的。
分解数据:正确地将数据分解为多个列极为重要。通过分解这些数据,才有可能利用特定的列对数据进行分类和过滤。你可以根据自己的具体需求来决定把数据分解到何种程度。
1. 数据库中每个列都有相应的数据类型。数据类型(datatype)定义了列可以存储哪些数据种类。
数据类型(datatype):所允许的数据的类型。每个表列都有相应的数据类型,他限制(或允许)该列中存储的数据。
1. 数据类型限定了可存储在列中的数据种类。数据类型还帮助正确的分类数据,并在优化磁盘使用方面起重要作用。因此,在创建表时必须特别关注所用的数据类型。
数据类型兼容:数据类型及其名称是SQL不兼容的一个主要原因。虽然大多数基本数据类型得到了一致的支持,但许多高级的数据类型却没有。更糟的是,偶然会有相同的数据类型在不同的DBMS中具有不同的名称。对此用户毫无办法,重要的是在创建表结构时要记住这些差异(最后一句不理解)。

1.1.4 行

1. 表中的数据是按行存储的,所保存的每个记录存储在自己的行内。
行(row):表中的一个记录
是记录还是行?你可能听到用户在提到行时称其为数据库记录(record)。这两个术语多半是可以交替使用的,但从技术上说,行才是正确的术语。

1.1.5 主键

1. 表中每一行都应该有一列(或几列)可以唯一标识自己。
主键(primary key):一列(或一组列),其值能够唯一标识表中每一行。
1. 唯一标识表中每行的这个列(或这几列)称为主键。主键用来表示一个特定的行。没有主键,更新或删除表中特定行就极为困难,因为你不能保证操作只涉及相关的行。
应该总是定义主键:虽然并不总是需要主键,但多数数据库设计者都会保证他们创建的每个表具有一个主键,以便于以后的数据库操作和管理。
1. 表中的任何列都可以作为主键,只要它满足一下条件:
	1. 任意两行都不具有相同的主键值。
	2. 每一行都必须具有一个主键值(主键列不允许NULL值)
	3. 主键列中的值不允许修改或更新
	4. 主键值不能重用(如果某行从表中删除,它的主键不能赋值给以后的新行)
2. 主键通常定义在表的一列上,但并不是必须这么做,也可以一起使用多个列作为主键。在使用多列作为主键时,上述条件必须应用到所有列,所有列值得组合必须是唯一的(但单个列的值可以不唯一)

1.2 什么是SQL

1. SQL是Structured Query Language(结构化查询语言)的缩写。SQL是一种专门用来与数据库沟通的语言。
2. SQL中只有很少的词,这是有意而为的,设计SQL的目的是很好的完成一项任务-提供一种从数据库中读写数据的简单有效的方法。
3. SQL有如下的优点:
	1. SQL不是某个特定数据库供应商专有的语言。几乎所有重要的DBMS都支持SQL,所以学习此语言你几乎能与所有数据库打交道。
	2. SQL简单易学。它的语句全都是由有很强描述性的英语单词组成,而且这些单词的数目不多。
	3. SQL虽然看上去很简单,但实际上是一种强有力的语言,灵活使用其语言元素,可以进行非常复杂和高级的数据库操作。
SQL的扩展:许多DBMS厂商通过增加语句或指令,对SQL进行扩展。这种扩展的目的是提供执行特定操作的额外功能或简化方法。虽然这种扩展很有用,但一般都是针对个别DBMS的,很少有两个以上的供应商支持这种扩张。

第二课 检索数据

1. 这一课介绍如何使用SELECT语句从表中检索一个或多个数据列

2.1 SELECT语句

1. SQL语句是由简单的英语单词构成的。这些单词称为关键字,每个SQL语句都是由一个或多个关键字构成的。最经常使用的SQL语句大概就是SELECT语句了。他的用途是从一个或多个表中检索信息。
关键字(keyword):作为SQL的组成部分的保留字。关键字不能作为表或列的名字。
1. 为了使用SELECT检索表数据,必须至少给出两条信息-想选择什么,以及从什么地方选择。

2.2 检索单个列

1. 从简单的SQL SELECT语句讲起,此语句如下所示:
	1. SELECT prod_name FROM Products;
	2. 分析:上述语句利用SELECT语句从Products表中检索一个名为prod_name的列。所需的列名写在SELECT关键字后,FROM关键字指出从那个表中检索数据。
结束SQL语句:多条SQL语句必须以分号(;)分隔。果树DBMS不需要在单条SQL语句后加分号。事实上,即使不一定需要,加上分号也肯定没有坏处。
SQL语句和大小写:SQL语句不区分大小写。不过,一定要认识到虽然SQL是不区分大小写的,但是表名、列名和值可能有所不同。

2.3 检索多个列

1. 想要从一个表中检索多个列,仍然使用相同的SELECT语句。唯一不同是必须在SELECT关键字后给出多个列名,列名间必须以逗号分割。
担心逗号:在选择多个列时,一定要在列名之间加上逗号,但最后一个列名不加。如果在最后一个列名后加了逗号,将出现错误。
1. 下面的SELECT语句从Products表中选择3列
	1. SELECT prod_id,prod_name,prod_price FROM Products;
数据表示:SQL语句一般返回原始的、无格式的数据。数据的格式化是表示问题,而不是检索问题。因此,表示一般在显示该数据的应用程序中规定。通常很少直接使用实际检索出的数据。

2.4 检索所有列

1. 除了指定所需的列外,SELECT语句还可以检索所有的列而不必逐个列出它们。在实际列名的位置使用星号(*)通配符就可以做到这点,此语句如下所示。
	1. SELECT * FROM Products;
		1. 分析:如果给定一个通配符(*),则返回表中所有列。列的顺序一般是列在表中出现的物理顺序,但并不总是如此。不过,SQL数据很少这样(通常,数据返回给应用程序,根据需要进行格式化)。因此,这不应该造成什么问题。
使用通配符:一般而言,除非你确实需要表中的每一列,否则最好别使用*通配符。虽然使用通配符能让你自己省事,不用明确列出所需列,但检索不需要的列通常会减低检索和应用程序的性能。
检索未知列:使用通配符有一个大优点。由于不明确指定列明(因为星号检索每一列),所以能检索出名字未知的列。

2.5 检索不同的值

1. 如果你不希望每个值每次都出现,该怎么办呢?办法就是使用DISTINCT关键字,顾名思义,它指示数据库只返回不同的值。
	1. 语句:SELECT DISTINCT vend_id FROM Products;
		1. 分析:SELECT DISTINCT vend_id告诉DBMS只返回不同(具有唯一性)的vend_id行。如果使用DISTINCT关键字,它必须直接放在列名的前面。
不能部分使用DISTINCT:DISTINCT关键字作用于所有的列,不仅仅是跟在其后的那一列。

2.6 限制结果

1. SELECT语句返回指定表中所有匹配的行,很可能是每一行。如果你只想返回第一行或者一定数量的行,该怎么办呢?这是可行的,如果你使用MySQL,需要使用LIMIT子句,像这样:
	1. SELECT prod_name FROM Products LIMIT 5;
		1. 分析:上述代码使用SELECT语句来检索单独的一列数据。LIMIT 5 指示MySQL返回不超过5行的数据。
	2. 为了得到后面的5行数据,需要指定从哪儿开始以及检索的行数,像这样:
		1. SELECT prod_name FROM Products LIMIT 5 OFFSET 5;
			1. 分析:LIMIT 5 OFFSET 5指示MySQL返回从第5行起的5行数据。第一个数字是指从哪儿开始,第二个数字是检索的行数。
2. 单独一个LIMIT指定返回的行数。带OFFSET的LIMIT指定从哪儿开始。
第0行:第一个被检索的行是第0行,而不是第1行。因此,LIMIT 1 OFFSET 1会检索第2行,而不是第1行。
MySQL快捷键:MySQL支持简化版的LIMIT 4 OFFSET 3语句,即LIMIT 3,4。使用这个语法,逗号之前的值对应LIMIT,逗号之后的值对应OFFSET。

2.7 使用注释

1. 可以看到,SQL语句是由DBMS处理的指令。如果你希望包括不进行处理和执行的文本,该怎么办呢?为什么你想要这么做呢?原因有以下几点:
	1. 随着SQL语句变长,复杂性增加,你就会添加一些描述性的注释,这便于你自己今后参考,或者供项目后续参与人员参考。这些注释需要嵌入在SQL脚本中,但显然不能进行实际的DBMS处理。
	2. 这同样适用于SQL文件开始处的内容,他可能包含程序员的联系方式、程序描述以及一些说明。
	3. 注释的另一个重要应用是暂时停止要执行的SQL代码。如果你碰到一个长SQL语句,而只想测试它的一部分,那么应该注释掉一些代码,以便DBMS将其视为注释而加以忽略。
2. 很多DBMS都支持各种形式的注释语法。先看行内注释
	1. 注释使用--(两个连字符)嵌在行内。--之后的文本就是注释,下面是另一种形式的行内注释(虽然这种形式很少得到支持)。
	2. 在一行的开始处使用#,这一整行都将作为注释。
	3. 你也可以进行多行注释,注释可以在脚本的任何位置停止和开始。注释从/*开始,到*/结束。这种方式常用于给代码加注释。

第三课 排序检索数据

1. 这一课讲授如何使用SELECT语句的ORDER BY 字句,根据需要排序检索出的数据。

3.1 排序数据

1. 如果SELECT查询语句中不加排序子句,则没有特定的顺序。
2. 其实,检索出的数据并不是随机显示的。如果不排序,数据一般将以它在底层中出现的顺序显示,这有可能是数据最初添加到表中的顺序。但是,如果数据随后进行过更新或删除,那么这个顺序将会受到DBMS重用回收存储空间的方式的影响。因此,如果不明确控制的话,则最终的结果不能(也不应该)依赖改排序顺序。关系数据库设计理论认为,如果不明确规定排序顺序,则不应该假定检索出的数据的顺序有任何意义。
子句(clause):SQL语句由子句构成,有些子句是必须的,有些则是可选的。一个子句通常由一个关键字加上所提供的数据组成。子句的例子有SELECT语句的FROM子句。
3. 为了明确的排序用SELECT语句检索出的数据,可使用ORDER BY子句。ORDER BY子句取一个或多个列的名字,据此对输出进行排序。请看下面的列子:
	1. SELECT prod_name FORM Products ORDER BY prod_name;
		1. 分析:除了指示DBMS软件对prod_name列以字母顺序排序数据的ORDER BY子句外,这条语句与前面的语句相同。
ORDER BY子句的位置:在指定一条ORDER BY子句时,应该保证它是SELECT语句中最后一条子句。如果它不是最后的子句,将会出现错误消息。
通过非选择列进行排序:通常,ORDER BY子句中使用的列将是为显示而选择的列。但是,实际上并不一定要这样,用非检索的列排序数据是完全合法的。

3.2 按多个列排序

1. 通常需要按不止一个列进行数据排序。例如,如果要显示雇员名单,可能希望按姓和名排序(首先按姓排序,然后在每个姓中按名排序)。如果多个雇员有相同的姓,这样做很有用。
2. 要按多个列排序,简单指定列名,列名间用逗号分开即可(就像选择多个列时那样)
3. 下面的代码检索3个列,并按其中两个列队结果进行排序-首先按价格,然后按名称排序。
	1. SELECT prod_id,prod_price,prod_name FROM Products ORDER BY prod_price,prod_name;
4. 重要的是理解在按多个列排序时,排序的顺序完全按规定进行。换句话说,对于上述语句,仅在多行具有相同的prod_price值时才对产品按prod_name进行排序。如果prod_price列中所有的值都是唯一个,则不会按prod_name排序。

3.3 按列位置排序

1. 除了能用列名指出排序顺序外,ORDER BY还支持按相对列位置进行排序。例子如下
	1. SELECT prod_id,prod_price,prod_name FROM Products ORDER BY 2, 3;
		1. 分析:这个与上面的语句区别在于ORDER BY子句。SELECT清单中指定的是选择列的相对位置而不是列名。ORDER BY 2表示按SELECT清单中的第二个列prod_name进行排序。ORDER BY 2,3((从 1 开始计数))表示先按prod_price,再按prod_name进行排序。
2. 这一技术的主要好处在于不用重新输入列名。但它也有缺点。首先,不明确给出列名有可能造成错用列名排序。其次,在对SELECT清单进行更改时容易错误的对数据进行排序(忘记对ORDER BY子句做相应的改动)。最后,如果进行排序的列不在SELECT清单中,显然不能使用这项技术。
按非选择列排序:显然,当根据不出现在SELECT清单中的列进行排序时,不能采用这项技术。但是,如果有必要,可以混合使用实际列名和相对列位置。

3.4 指定排序方向

1. 数据排序不限于升序排列(从A到Z),这只是默认的排序顺序。还可以使用ORDER BY子句进行降序(从Z到A)排序。为了进行降序排序,必须指定DESC关键字。下面的例子以价格降序来排序产品(最贵的排在最前面):
	1. SELECT prod_id,prod_price,prod_name FROM Products ORDER BY prod_price DESC;
	2. 如果打算用多个列排序,该怎么办?下面的例子以降序排序产品(最贵的在前面),再加上产品名:
		1. SELECT prod_id, prod_price, prod_name FROM Products ORDER BY prod_price DESC, prod_name;
		2. DESC关键字只应用到直接位于其前面的列名。在上例中,只对prod_price列指定DESC,对prod_name列不指定。因此,prod_price列以降序排序,而prod_name列(在每个价格内)仍然按标准的升序排序。
在多个列上降序排序:如果想在多个列上进行降序排序,必须对每一列指定DESC关键字。
1. 请注意,DESC是DESCENDING的缩写,这两个关键字都可以使用。与DESC相对的是ASC(或ASCENDING),在升序排序时可以指定他。但实际上,ASC没有多大用处,因为升序是默认的(如果既不指定ASC也不指定DESC,则假定为ASC)。
区分大小写和排序顺序:在对文本性数据进行排序时,A与a相同吗?a位于B之前,还是Z之后?这些问题不是理论问题,其答案取决于数据库的设置方式。在字典(dictionary)排序顺序中,A被视为与a相同,这是大多数数据库管理系统的默认行为。但是,许多DBMS允许数据库管理员在需要时改变这种行为(如果你的数据库包含大量外语字符,可能必须这样做)(这句话不理解)。这里的关键问题是,如果确实需要改变这种排序顺序,用简单的ORDER BY子句可能做不到。你必须请求数据库管理员的帮助。

第四课 过滤数据

1. 这一课将讲授如何使用SELECT语句的WHERE子句指定搜索条件。

4.1 使用WHERE子句

1. 数据库表一般包含大量的数据,很少需要检索表中的所有行。通常只会根据特定操作或报告的需要提取表数据的子集。只检索所需数据需要指定搜索条件(search criteria),搜索条件也称为过滤条件(filter condition)。
2. 在SELECT语句中,数据根据WHERE子句中指定的搜索条件进行过滤。WHERE子句在表名(FROM子句)之后给出,如下所示:
	1. SELECT prod_name,prod_price FROM Products WHERE prod_price = 3.49;
		1. 分析:这条语句从products表中检索两个列,但不返回所有行,只返回prod_price值为3.49的行。
SQL过滤与应用过滤:数据也可以在应用层过滤。为此,SQL的SELECT语句为客户端应用检索出超过实际所需的数据,然后客户端代码对返回数据进行循环,提取出需要的行。通常这种的做法极为不妥。优化数据库后可以更快速有效的对数据进行过滤。而让客户端应用(或开发语言)处理数据库的工作将会极大地影响应用的性能,并且使所创建的应用完全不具备可伸缩性。此外,如果在客户端过滤数据,服务器不得不通过网络发送多余的数据,这将导致网络带宽的浪费。

4.2 HWERE子句操作符

4.2.1 检查单个值

何时使用引号:单引号用来限定字符串。如果将值与字符串类型的列进行比较,就需要限定引号。用来与数值列进行比较的值不用引号。

4.2.3 范围值检查

1. 要检查某个范围的值,可以使用BETWEEN操作符。其语法与其他WHERE子句的操作符稍有不同,因为他需要两个值,即范围的开始值和结束值。
2. 下面的例子说明如何使用BETWEEN操作符,他检索价格在5美元和10美元之间的所有产品。
		1. SELECT prod_name,prod_price FROM Products WHERE prod_price BETWEEN 5 AND 10;
			1. 分析:从这个例子可以看到,在使用BETWEEN时,必须指定两个值-所需范围的低端值和高端值。这两个值必须用AND关键字分隔。BETWWEN匹配范围中的所有值,包括指定的开始值和结束值。

4.2.4 空值检查

1. 在创建表时,表设计人员可以指定其中的列是否不包含值。在一个列不包含值时,称其包含空值NULL。
NULL:无值(no value),它与字段包含0、空字符串或仅仅包含空格不同。
1. 确定值是否为NULL,不能简单的检查是否=NULL。SELECT语句有一个特殊的WHERE子句,可用来检查具有NULL值得列。这个WHERE子句就是IS NULL 子句。其语法如下:
	1. SELECT prod_name FROM Products WHERE prod_price IS NULL;
NULL和非匹配:通过过滤选择不包含指定值的所有行时,你可能会希望返回含NULL值得行。但是这做不到。因为未知(unknown)有特殊的含义,数据库不知道它们是否匹配,所以在进行匹配过滤或非匹配过滤时,不会返回这些结果。过滤数据时,一定要验证被过滤列中含NULL的行确实出现在返回的数据中。

第五课 高级数据过滤

1. 这一课讲授如何组合WHERE子句以建立功能更强、更高级的搜索条件。我们还将学习如何使用NOT和IN操作符。

5.1 组合WHERE子句

1. 上一课介绍的所有WHERE子句在过滤数据时使用的都是单一的条件。为了进行更强的过滤控制,SQL允许给出多个WHERE子句。这些子句有两种使用方式,即以AND子句或OR子句的方式使用。
操作符(operator)
1. 用来联结或改变WHERE子句中的子句的关键字,也称为逻辑操作符(logical operator)。

5.1.1 AND操作符

1. 要通过不止一个列进行过滤,可以使用AND操作符给WHERE子句附加条件。下面的代码给出了一个例子:
	1. 输入:	
		1. SELECT prod_id,prod_price,prod_name FROM Products WHERE vend_id = 'DLL01' AND prod_price <= 4;
	2. 分析:
		1. 此SQL语句检索由供应商DLL01制造且价格小于等于4美元的所有产品的名称和价格。这条SELECT语句中的WHERE子句包含两个条件,用AND关键字联结在一起。AND指示DBMS只返回满足所有给定条件的行。如果某个产品由供应商DLL01制造,但价格高于4美元,则不检索它。类似的,如果产品价格小于4美元,但不是由指定供应商制造的也不被检索。
AND
1. 用在WHERE子句中的关键字,用来指示满足所有给定条件的行
说明:没有ORDER BY子句
1. 为了节省空间,也为了减少你的输入。因此,在很多例子里省略了ORDER BY子句。

5.1.2 OR操作符

1. OR操作符与ADN操作符正好相反,他指示DBMS搜索匹配任一条件的行。事实上,许多DBMS在OR WHERE子句的第一个条件得到满足的情况下,就不再计算第二个条件了(在第一个条件满足时,不管第二个条件是否满足,相应的行都将被检索出来)。
	1. 输入:
		1. SELECT prod_name,prod_price FROM Products WHERE vend_id = 'DLL01' OR vend_id = 'BRS01';
	2. 分析:
		1. 此SQL语句检索由任一个指定供应商制造的所有产品名和价格。OR操作符告诉DBMS匹配任一条件而不是同时匹配两个条件。如果在这里使用的是AND操作符,则没有数据返回(因为会创建没有匹配行的WHERE子句)。
OR
1. WHERE子句中使用的关键字,用来表示检索匹配任一给定条件的 行。

5.1.3 求值顺序

1. WHERE子句可以包含任意数目的AND和OR操作符。允许两者结合以进行复杂、高级的过滤。
2. 但是,组合AND和OR会带来一个有趣的问题。为了说明这个问题,来看一个例子。假如需要列出价格为10美元及以上,且由DLL01或BRS01制造的所有商品。
	1. 输入:
		1. SELECT prod_name,prod_price FROM Products WHERE vend_id = 'DLL01' OR vend_id = 'BRS01' AND prod_price >= 10;
	2. 上面语句的输出肯定与我们的设想不一致,其原因在于求值的顺序。SQL(想大多数语言一样)在处理OR操作符之前,优先处理AND操作符。当SQL看到上述WHERE子句时,它理解为:由供应商BRS01制造的价格为10美元以上的所有产品,以及由供应商DLL01制造的所有产品,而不管其价格如何。**换句话说,由于AND在求值过程中优先级更高,操作符被错误地组合了。此问题的解决方法是使用圆括号对操作符进行明确分组。**
		1. 输入:
			1. SELECT prod_name,prod_price FROM Products WHERE (vend_id = 'DLL01' OR vend_id = 'BRS01') AND prod_price >= 10;
		2. 分析:
			1. 这条SELECT语句与前一条的唯一差别是,将前两个条件用圆括号括了起来。因为圆括号具有比AND或OR操作符更高的求值顺序,所以DBMS首先过滤圆括号内的OR条件。这时,SQL语句变成了选择由供应商DLL01或BRS01制造的且价格在10美元及以上的所有产品,这正是我们希望的结果。
提示:在WHERE子句中使用圆括号
1. 任何时候使用具有AND和OR操作符的WHERE子句,都应该使用圆括号明确地分组操作符。不要过分依赖求值顺序,即使它确实如你希望的那样。使用圆括号没有什么坏处,它能消除歧义。

5.2 IN操作符

1. IN操作符用来指定条件范围,范围内的每个条件都可以进行匹配。IN取一组由逗号分隔、括在圆括号中合法值。
	1. 输入:
		1. SELECT prod_name,prod_price FROM Products WHERE vend_id IN ('DLL01','BRS01') ORDER BY prod_name;
	2. 分析:
		1. 此SELECT语句检索由供应商DLL01和BRS01制造的所有产品。IN操作符后跟由逗号分隔的合法值,这些值必须括在圆括号中
		2. 你可能会猜测IN操作符完成了与OR相同的功能,恭喜你猜对了!下面的SQL语句完成与上面的例子相同的工作。
			1. SELECT prod_name,prod_price FROM Products WHERE vend_id = 'DLL01' OR vend_id = 'BRS01' ORDER BY prod_name;
2. 由上述的两组语句可以看出OR能实现IN相同的功能,那为什么要使用IN操作符呢?其优点如下:
	1. 在有很多合法选项时,IN操作符的语法更清楚,更直观。
	2. 在与其它AND和OR操作符组合使用IN时,求值顺序更容易管理。
	3. IN操作符一般比一组OR操作符执行得更快(在上面这个合法选项很少的例子中,你看不出性能差异)。
	4. **IN的最大优点是可以包含其它SELECT语句**,能够动态的建立WHERE子句。
IN
1. WHERE子句中用来指定要匹配值的清单的关键字,功能与OR相等。

5.3 NOT操作符

1. WHERE子句中的NOT操作符有且只有一个功能,那就是否定其后所跟的任何条件。因为NOT从不单独使用(他总是与其它操作符一起使用),所以它的语法与其它操作符有所不同。NOT关键字可以用在要过滤的列前,而不仅是在其后。
NOT
1. WHERE子句中用来否定其后条件的关键字。
例子
1. 下面的例子说明NOT的用法。为了列出除DLL01之外的所有供应商制造的产品,可编写如下的代码:
	1. 输入:
		1. SELECT prod_name FROM Products WHERE NOT vend_id = 'DLL01' ORDER BY prod_name;
	2. 分析:
		1. 这里的NOT否定跟在其后的条件,因此,DBMS不是匹配vend_id为DLL01,而是匹配非DLL01之外的所有东西。
		2. 这个例子也可以用<>操作符来完成,如下所示:
			1. 输入:
				1. SELECT prod_name FROM Products WHERE vend_id <> 'DLL01' ORDER BY prod_name;
			2. 分析:
				1. 为什么使用NOT?对于这里的这种简单的WHERE子句,使用NOT确实没什么优势。但在更复杂的子句中,NOT是非常有用的。例如,在与IN操作符联合使用时,NOT可以非常容易地找出与条件列表不匹配地行。
说明:MariaDB中地NOT
1. MariaDB支持使用NOT否定IN、BETWEEN和EXISTS子句。大多数DBMS允许使用NOT否定任何条件。

第6课 用通配符进行过滤

1. 这一课介绍什么是通配符、如何使用通配符以及怎样使用LIKE操作符进行通配搜索,以便对数据进行复杂过滤。

6.1 LIKE操作符

1. 前面介绍的所有操作符都是针对已知值进行过滤的。不管是匹配一个值还是多个值,检验大于还是小于已知值,或者检查某个范围的值,其共同点是过滤中使用的值都是已知的。但是,这种过滤方式并不是任何时候都好用。例如,怎样搜索产品名中包含文本bean bag的所有产品?用简单的操作符肯定不行,必须使用通配符。利用通配符,可以创建比较特定数据的搜索模式。
通配符(wildcard):用来匹配值的一部分的特殊字符。
搜索模式:由字面值、通配符或两者组合构成的搜索条件。
2. 通配符本身实际上是SQL的WHERE子句中有特殊含义的字符,SQL支持几种通配符。**为在搜索子句中使用通配符,必须使用LIKE操作符**。LIKE指示DBMS,后跟的搜索模式利用通配符匹配而不是简单的相等匹配进行比较。
谓词(predicate):操作符何时不是操作符?答案是,它作为谓词时。从技术上说,LIKE是谓词而不是操作符。虽然最终结果是相同的,但应该对此术语有所了解,以免在SQL文献中或手册中遇到此术语不知所云。
3. **通配符搜索只能用于文本字段(字符串),非文本数据类型不能使用通配符搜索。**

6.1.1 百分号(%)通配符

1. 最常使用的通配符是百分号(%)。在搜索串中,**%表示任何字符出现任意次数**。例如,为了找出所有以词Fish起头的产品,可以发布以下SELECT语句:
	1. 输入:
		```
			SELECT prod_id,prod_name
			FROM Products
			WHERE prod_name LIKE 'Fish%';
		```
	2. 分析:
		1. 此例子使用了搜索模式'%Fish'。在执行这条子句时,将检索任意以Fish起头的词。%告诉DBMS接受Fish之后的任意字符,不管它有多少字符。
2. 通配符可以在搜索模式中的任意位置使用,并且可以使用多个通配符。下面的例子使用两个通配符,它们位于模式的两端:
	1. 输入:
		```
			SELECT prod_id,prod_name
			FROM Products
			WHERE prod_name LIKE '%bean bag%';
		```
	2. 分析
		1. 搜索模式'%bean bag%'表示匹配任何位置上包含文本bean bag的值,不论它之前或之后出现什么字符。
3. 通配符也可以出现在搜索模式的中间,虽然这样做不太有用。下面的例子找出以F起头,以y结尾的所有产品:
	1. 输入:
		```
			SELECT prod_name
			FROM Products
			WHERE prod_name LIKE 'F%y';
        ```
提示:根据部分信息搜索电子邮件地址:有一种情况下把通配符放在搜索模式中间是很有用的,就是根据邮件地址的一部分来查找电子邮件,例如:WHERE email LIKE ‘b%@forta.com’。
说明:请注意后面所跟的空格,意思是如果输入值时不小心将空格输入在最后,那么空格也会被当作这个值的一部分。(切记不要手贱在值得前后加上空格)。
注意:请注意NULL:通配符开起来像是可以匹配任何东西,但有个例外,这就是NULL。子句WHERE prod_name LIKE '%'不能匹配值为NULL的列值。

6.1.2 下划线(_)通配符

1. 另一个有用的通配符是下划线(_)。下划线的用途与%一样,但它**只匹配单个字符,而不是多个字符**。
说明DB2通配符:DB2不支持通配符_。
说明:Access通配符:如果使用的是Microsoft Access,需要使用?而不是_。
2. 例子:
	1. 输入 
			1. SELECT prod_id,prod_name FROM Products WHERE prod_name LIKE '__ inch teddy bear';
说明:请注意后面所跟的空格:与上一例一样,可能需要给这个模式添加一个通配符。
	2. 分析:
		1. 这个WHERE子句中的搜索模式给出了后面跟有文本的两个通配符。**与%能匹配0个字符不同,_总是刚好匹配一个字符,不能多也不能少。**

6.1.3 方括号([])通配符

1. 方括号([])通配符用来指定一个字符集,它必须匹配指定位置(通配符的位置)的一个字符。
并不总是支持集合:与前面描述的通配符不一样,并不是所有DBMS都支持用来创建集合的[]。只有微软的Access和SQL Server支持集合。为确定你使用的DBMS是否支持集合,请参阅相应的文档。

6.2 使用通配符的技巧

1. 正如所见,SQL的通配符很有用。但这种功能是有代价的,**即通配符搜索一般比前面讨论的其它搜索要耗费更长处理时间。**这里给出一些使用**通配符时要记住的技巧**。
	1. 不要过度使用通配符。如果其它操作符能达到相同的目的,应该使用其它操作符。
	2. 在确实需要使用通配符时,也尽量不要把它们用在搜索模式的开始处。把通配符置于开始处,搜索起来是最慢的。
	3. 仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。

第7课 创建计算字段

7.1 计算字段

1. 由于存储在数据库中的数据一般不是应用程序所需要的格式。这就是计算字段可以派上用场的地方了。**计算字段并不是实际存在于数据库表中**。计算字段是运行时在SELECT语句内创建的。
字段(field):基本上与列(column)的意思相同,经常互换使用,不过数据库列一般称为列,而术语字段通常与计算字段一起使用。
2. 需要特别注意,只有数据库知道SELECT语句中那些列是实际的表列,那些列是计算字段。从客户端来看(如应用程序)来看,计算字段的数据与其它列的数据的返回方式相同。
客户端与服务器的格式:在SQL语句内可完成的许多转换和格式化工作都可以直接在客户端应用程序内完成。但一般来说,在数据库服务器上完成这些操作比在客户端中完成要快很多。

7.2 拼接字段

1. Vendors表包含供应商名和地址信息。假如要生成一个供应商报表,需要在格式化的名称(位置)中列出供应商的位置。
2. 此报表需要一个值,而表中数据存储在两个列vend_name和vend_country中。此外需要用括号将vead_country括起来,这些东西都没有存储在数据库表中。这个返回供应商名称和地址的SELECT语句很简单,但我们是如何创建这个组合值呢?
拼接:将值联结到一起(将一个值附加到另一个值)构成单个值。
3. 解决办法是把两个列拼接起来。在SQL中的SELECT语句中,可使用一个特殊的操作符来拼接两个列。根据你所使用的DBMS,此操作符可使用加号(+)或两个竖杆(||)表示。在MySQL和MariaDB中,必须使用特殊函数。
4. 例子:
	1. 输入:
		1. SELECT vend_name + '(' + vend_country + ')' FROM Vendors ORDER BY vead_name;
	2. MySQL的输入:
		1. SELECT Concat(vend_name,'(',vend_country,')') FROM Vendors ORDER BY vend_name 
	3. 分析:
		1. 上面这个SQL拼接以下元素:
			1. 存储在vend_name列中的名字
			2. 包含一个空格和一个左圆括号的字符串
			3. 存储在vend_country列中的国家
			4. 包含一个右圆括号的字符串。
		2. 输出为包含上述四个元素的一个列(计算字段)
	4. 如果你想要去掉这些空格。这就可以使用SQL的RTRIM()函数来完成,如下所示:
TRIM函数:RTRIM()(去掉字符串右边的空格)、LTRIM()(去掉字符串左边的空格)以及TRIM()(去掉字符串左右两边的空格)。

使用别名

1. 从前面的输出可以看到,SELECT语句可以很好地拼接地址字段。但是,这个新计算列地名字是什么呢?实际上它没有名字,它只是一个值。如果仅在SQL查询工具中查看一下结果,这样没什么不好。但是一个未命名地列不能用于客户端应用中,因为客户端没办法引用它。
2. 为了解决这个问题,SQL支持列别名。别名(alias)是一个字段或值得替换名。别名用AS关键字赋予。请看下面地SELECT语句:
	1. 输入:
		1. SELECT Concat(vend_name,'(',vend_country,')') AS vend_title FROM Vendors ORDER BY vend_name;
	2. 分析
		1. SELECT语句本身与之前使用地相同,只不过这里的计算字段之后跟了文本 AS vend_title。它指示SQL创建一个包含指定计算结果地名为vend_title地计算字段。从输出可以看到,结果与之前地相同,但现在列名为vend_title,任何客户端应用都可以按名称引用这个列,就像它是一个实际地列表一样。
别名地其他用途:别名还有其他用途。常见地用途包括在实际地表列名包含不合法地字符(如空格)时重新命名它,在原来的名字含混或容易误解时扩充它。
别名:别名地名字既可以是一个单词,也可以是一个字符串。如果是后者,字符串应该括在引号中。虽然这种做法是合法地,但不建议这么去做。多单词地名字可读性高,不过会给客户端应用带来各种问题。因此,别名常见地使用是将多个单词地列名重命名为一个单词地名字。
导出列:别名有时也称为导出列(derived column),不管怎么叫,它们所代表地是相同的东西。

7.3 执行算数计算

1. 计算字段的另一个常见用途是对检索出的数据进行算术计算。举个例子,Orders表包含收到的所有订单,OrderItems表包含每个订单中的各项物品。下面的SQL语句检索订单号20008中的所有物品:
	1. 输入:
		1. SELECT prod_id,quantity,item_price FROM OrderItems WHERE order_num = 20008
		2. item_price列包含订单中每项物品的单价。如下汇总物品的价格(单价乘以订购数量):
			1. 输入:
				1. SELECT prod_id,quantity,item_price,quantity*item_price AS expanded_price FROM OrderItems WHERE order_num = 20008;
			2. 分析:
				1. 输出中显示的expanded_price列是一个计算字段,此计算为quantity*item_price。客户端应用现在可以使用这个新计算列,就像使用其他列一样。
2. SQL支持基本算数操作符(+,-,*,/)。此外,圆括号可用来区分优先顺序。
如何测试计算:SELECT语句为测试、检验函数和计算提供了很好的方法。虽然SELECT通常用于从表中检索数据,但是省略了FROM字句后就是简单的访问和处理表达式,例如SELECT 3*2;将返回6,SELECT Trim(’ abc ');将返回abc,SELECT Now();使用Now()函数返回当前日期和时间。

第8课 使用函数处理数据

8.1 函数

1. 与大多数其他计算机语言一样,SQL也可以用函数来处理数据。函数一般是在数据上执行的,为数据的转换和处理提供了方便。前面使用的RTRIM()就是一个函数。

函数带来的问题

1. 虽然所有类型的函数一般都可以在每个DBMS中使用,但各个函数的名称和语法可能极其不同。

8.2 使用函数

1. 大多数SQL实现支持以下类型的函数:
	1. 用于处理文本字符串(如删除或填充值,转换值为大写或小写)的文本函数。
	2. 用于在数值数据上进行算数操作(如返回绝对值,进行代数运算)的数值函数。
	3. 用于处理日期和时间值并从这些值中提取特定成分(如返回两个日期之差,检查日期有效性)的日期和时间函数。
	4. 返回DBMS正使用的特殊信息(如返回用户登录信息)的系统函数。
2. 我们在上一课看到函数用于SELECT后面的列名,但函数的作用不仅于此。它还可以作为SELECT语句的其他成分,如在WHERE子句中使用,在其他SQL语句中使用等。

8.2.1 文本处理函数

1. 我们以及看过一个文本处理函数的例子,其中使用RTRIM()函数来去除列值右边的空格 。下面是另一个例子,这次使用的是UPPER()函数:
	1. 输入:
		1. SELECT vend_name,UPPER(vend_name) AS vend_name_upcase FROM Vendors ORDER BY vend_name
2. 常用的文本处理函数
	1. LEFT()(或使用子字符串函数):返回字符串左边的字符
	2. LENGTH()(也使用DATALENGTH()或LEN()):返回字符串的长度
	3. LOWER():将字符串转换为小写
	4. LTRIM():去掉字符串左边的空格
	5. RIGHT()(或使用子字符串函数):返回字符串右边的字符
	6. RTRIM():去掉字符串右边的空格
	7. SOUNDEX():返回字符串的SOUNDEX值,这个可以不要关注
	8. UPPER():将字符串转换为大写

8.2.2 日期和时间处理函数

1. 日期和时间采用相应的数据类型存储在表中,每种DBMS都有自己的特殊形式。日期和时间值以特殊的格式存储,以便能快速和有效地排序或过滤,并且节省物理存储空间。
2. 应用程序一般不使用日期和时间的储存格式,因此日期和时间函数总是用来读取、统计和处理这些值。由于这个原因,日期和时间函数在SQL中具有重要的作用。**遗憾的是,它们很不一致,可移植性最差。**

8.2.3 数值处理函数

1. 数值处理函数仅处理数值数据。这些函数一般用于代数、三角或几何运算,因此不像字符串或日期-时间处理函数使用那么频繁。
2. 具有讽刺意味的是,在主要DBMS的函数中,数值函数是最一致、最统一的函数。
3. 列出了一些常用的数值处理函数
	1. ABS():返回一个数的绝对值
	2. COS():返回一个角度的余弦
	3. EXP():返回一个属的指数值
	4. PI():返回圆周率
	5. SIN():返回一个角度的正弦
	6. SQRT():返回一个数的平方根
	7. TAN():返回一个角度的正切

第9课 汇总数据

1. 这一课介绍什么是SQL的汇聚函数,如何利用它们汇总表的数据。

9.1 汇聚函数

1. 我们经常需要汇总数据而不用把它们实际检索出来,为此SQL提供了专门的函数。使用这些函数,SQL查询可用于检索数据,以便分析和报表生成。
2. 为方便这种类型的检索,SQL给出了5个聚集函数。这些函数能进行上述检索。与前一章介绍的数据处理不同,SQL的聚集函数在各种主要SQL实现中得到了相当一致的支持。
聚集函数(aggregate function):对某些行运行的函数,计算并返回一个值。
3. SQL聚集函数:
	1. AVG():返回某列的平均值
	2. COUNT():返回某列的行数
	3. MAX():返回某列的最大值
	4. MIN():返回某列的最小值
	5. SUM():返回某列值之和

9.1.1 AVG()函数

1. AVG()通过对表中行数计数并计算其列值之和,求得该列的平均值。AVG()可用来返回所有列的平均值,也可以用来返回特定列或行的平均值。
只用于单个列:AVG()只能用来确定特定数值列的平均值,并且列名必须作为函数参数给出。为了获得多个列的平均值,必须使用多个AVG()函数。
NULL值:AVG()函数忽略列值为NULL的行。

9.1.2 COUNT()函数

1. COUNT()函数进行计数。可利用COUNT()确定表中行的数目或符合特定条件的行的数目。
2. COUNT()函数有两种使用方式:
	1. 使用COUNT(*)对表中行的数目进行计数,不管表列中包含的是空值(NULL)还是非空值。
	2. 使用COUNT(column)对特定列中具有值的行进行计数,忽略NULL值。

NULL值:如果指定列名,则COUNT()函数会忽略指定列的值为空的行,但如果COUNT()函数中用的是’*’,则不忽略。

9.1.3 MAX()函数

1. MAX()返回指定列中的最大值。MAX()要求指定列名。

对非数值数据使用MAX():虽然MAX()一般用来找出最大的数值或日期值,但许多(并非所有)DBMS允许将它用来返回任意列中的最大值,包括返回文本列中的最大值。在用于文本数据时,MAX()返回按该列排序后的最后一行。

NULL值:MAX()函数忽略列值为NULL的行。

9.1.4 MIN()函数

1. MIN()的功能正好与MAX()功能相反,它返回指定列的最小值。与MAX()一样,MIN()要求指定列名。

对非数值数据使用MAX():虽然MAX()一般用来找出最小的数值或日期值,但许多(并非所有)DBMS允许将它用来返回任意列中的最小值,包括返回文本列中的最小值。在用于文本数据时,MAX()返回按该列排序后的最前面的行。

NULL值:MAX()函数忽略列值为NULL的行。

9.1.5 SUN()函数

1. SUN()用来返回指定列值的和(总计)。
2. SUN()也可以用来合计计算值。
在多个列上进行计算:利用标准的算术操作符,所有聚合函数都可用来执行多个列上的计算
NULL值:SUN()函数忽略列值为NULL的行

9.2 聚集不同值

1. 以上5个聚集函数都可以如下使用:
	1. 对所有行执行计算,指定ALL参数或不指定参数(因为ALL是默认行为)
	2. 只包含不同的值,指定DISTINCT参数
ALL为默认:ALL参数不需要指定,因为它是默认行为。如果不指定DISTINCT,则假定为ALL
DISTINCT不能用于COUNT():如果指定列名,则DISTINCT只能用于COUNT(),DSITINCT不能用于COUNT()。类似的,DISTINCT必须使用列名,不能用于计算或表达式
将DISTINCT用于MIN()和MAX():虽然DISTINCT从技术上可用于MIN()和MAX(),但这样做实际上没有价值。一个列中的最小值和最大值不管是否只考虑不同值,结果都是相同的。

9.3 组合聚集函数

1. 聚集函数可以多个一起使用。
取别名:在指定别名以包含某个聚集函数的结果时,不应该使用表中实际的列名。虽然这样做也算合法,但许多SQL实现不支持,可能会产生模糊的错误消息

第10课 分组数据

1. 这一课介绍如何分组数据,以便汇总表内容的子集。这涉及两个新SELECT语句子句:GROUP BY子句和HAVING子句。

10.1 数据分组

1. 使用分组可以将数据分为多个逻辑组,对每个组进行聚集计算。

10.2 创建分组

1. 因为使用了GROUP BY,就不必指定要计算和估算的每个组了。系统会自动完成。GROUP BY子句指示DBMS分组数据,然后对每个组而不是整个结果集进行聚集。
2. 在使用GROUP BY子句前,必须知道一些重要的规定。
	1. GROUP BY 子句可以包含任意数目的列,因而可以对分组进行嵌套,更细致地进行数据分组。
	2. 如果在GROUP BY子句中嵌套了分组,数据将在最后指定地分组上进行汇总。换句话说,在建立分组时,指定的所有列一起计算(所以不能从个别地列取回数据)。
	3. GROUP BY 子句中列出的每一列都必须是检索列或有效的表达式(但不能是聚集函数)。如果在SELECT中使用表达式,则必须在GROUP BY子句中指定相同地表达式。不能使用别名。
	4. 大多数SQL实现不允许GROUP BY列带有长度可变地数据类型(如文本或备注型字段)。
	5. 除聚集计算语句外,SELECT语句中的每一列都必须在GROUP BY子句中给出。
	6. 如果分组列中包含具有NULL值的行,则NULL将作为一个分组返回。如果列中有多行NULL值,它们将分为一组。
	7. GROUP BY子句必须出现在WHERE子句之后,ORDER BY子句之前。
注意:通过相对位置指定列
```
	有的SQL实现允许根据SELECT列表中的位置指定GROUP BY的列。虽然这种速记语法很方便,但并非所有SQL实现都支持,并且使用它容易在编辑SQL语句时出错。
```

10.3 过滤分组

1. 除了能用GROUP BY 分组数据外,SQL还允许过滤分组,规定包括那些分组,排除那些分组。为此,必须基于完整的分组而不是个别的行进行过滤。
2. 我们已经看到了WHERE子句的作用。但是WHERE不能完成任务,因为WHERE过滤指定的是行而不是分组。事实上,WHERE没有分组的概念。
3. 那么,不使用WHERE使用什么呢?SQL为此提供了另一个子句,就是HAVING子句。HAVING非常类似于WHERE。事实上,目前为止所学过的所有类型的WHERE子句都可以用HAVING来替代。**唯一的差别是,WHERE过滤行,而HAVING过滤分组。**
提示:HAVING支持所有的操作符
```
	我们学习了WHERE子句的条件(包括通配符条件和带多个操作符的子句)。学过的这些有关于WHERE的所有技术和选项都适用于HAVING。它们的句法是相同的,指示关键字有差别。
```
说明:HAVING和WHERE的差别
```
	这里有另一种理解方法,WHERE在数据分组前进行过滤,HAVING在数据分组后进行过滤。这是一个重要的区别,WHERE排除的行不包括在分组中。这可能会改变计算值,从而影响HAVING子句中基于这些值过滤掉的分组
```
4. WHERE和HAVING可以使用在同一条SQL中,即先对行进行过滤,再对组进行分类。
说明:使用HAVING和WHERE
```
	HAVING与WHERE非常类似,如果不指定GROUP BY,则大多数DBMS会同等对待它们。不过,你自己要能区分这一点。使用HAVING时应该结合GROUP BY 子句,而WHERE子句用于标准的行级过滤。
```

10.4 分组和排序

1. GROUP BY 和 ORDER BY 经常完成相同的工作,但它们非常不同,理解这一点很重要。
提示:不要忘记ORDER BY
```
	一般在使用GROUP BY 子句时,应该也要给出ORDER BY 子句。这是保证数据正确排序的唯一方法。千万不要仅依赖GROUP BY 排序数据。
```

10.5 SELECT 子句顺序

1. 下面回顾一下SELECT 语句中子句的顺序。
	1.  子句		|			说明		|        是否必须使用
	2.  SELECT  |     要返回的列或表达式   | 是
	3.  FROM     |   从中检索数据的表    |   仅在从表选择数据时使用
	4.  WHERE    | 行级过滤      |   否
	5.  GROUP BY | 分组说明       | 仅在按组计算聚集时使用
	6.  HAVING	 | 组级过滤        |   否
	7.  ORDER BY  |  输出排序顺序   |  否

第11课 使用子查询

11.1 子查询

1. SELECT 语句是SQL的查询。我们迄今为止所看到的SELECT语句都是简单查询,即从单个数据库表中检索数据的单条语句。
2. SQL还允许创建子查询(subquery),即嵌套在其他查询中的查询。

11.2 利用子查询进行过滤

1. 在SELECT语句中,子查询总是从内向外处理。
2. 在WHERE子句中使用子查询能够编写出功能很强且很灵活的SQL语句。对于能嵌套的子查询的数目没有限制,不过在实际使用时由于性能的限制,不能嵌套太多的子查询。
注意:只能是单列
```
	作为子查询的SELECT语句只能查询单个列。企图检索多个列将返回错误。
```
注意: 子查询的性能
```
	这里给出的代码有效,并且获得了所需的结果。但是,使用子查询并不总是执行这类数据检索的最有效的方法。
```

11.3 作为计算字段使用子查询

1. 使用子查询的另一种方法是创建计算字段。

第12课 联结表

12.1 联结

12.1.1 关系表

1. 关系表的设计就是要把信息分解成多个表,一类数据一个表。各表通过某些公共的值互相关联(所以才叫关系数据库)
可伸缩性(scale)
```
	能够适应不断增加的工作量而不失败。设计良好的数据库或应用程序称为可伸缩性好(scale well)
```

12.1.2 为什么使用联结

1. 如前所述,将数据分解为多个表能更有效的存储,更方便的处理,并且可伸缩性更好。但这些好处是有代价的。
2. 如果数据存储在多个表中,怎样用一条SELECT语句就检索出数据呢?
3. 答案就是使用联结。简单说,联结是一种机制,用来在一条SELECT语句中关联表,因此称为联结。使用特殊的语法,可以联结多个表返回一组输出,联结在运行时关联表中正确的行。
说明:使用交互式DBMS工具
```
	重要的是,要理解联结不是物理实体。换句话说,它在实际的数据库表中并不存在。DBMS会根据需要建立联结,它在查询执行期间一直存在。
	许多DBMS提供图形界面,用来交互式地定义表关系,这些工具极其有助于维护引用完整性。在使用关系表时,仅在关系列中插入合法数据是非常重要的。引用完整性表示DBMS强制实施数据完整性规则。
```

12.2 创建联结

1. 创建联结非常简单,指定要联结的所有表以及关联它们的方式即可。例子如下:
	1. 输入:
		```
			SELECT vend_name,prod_name,prod_price
			FROM Vendors Products
			WHERE Vendors.vend_id=Products.vend_id;
		```
	2. 分析:
		1. SELECT语句与前面所有语句一样指定要检索的列。这里最大的差别是所指定的两个列(prod_name和prod_price)在一个表中,而第三列(vend_name)在另一个表中。
		2. 再来看FROM子句。与前面的SELECT语句不一样,这条语句的FROM子句列出了两个表:Vendors和Products。它们就是这条SELECT语句联结的两个表的名字。这两个表用WHERE子句正确的联结,WHERE子句指示DBMS将Vendors表中的vend_id和Products表中的vend_id匹配起来。
		3. 可以看到,要匹配的两列指定为Vendors.vend_id和Products.vend_id。这里需要这种完全限定列名,如果只给出vend_id,DBMS就不知道指的是哪一个(每个表中有一个)。
警告:完全限定列名
```
	在引用的列可能出现歧义时,必须使用完全限定列名(用一个句点分隔表名和列名)。如果引用一个没有用表名限制的具有歧义的列名,大多数DBMS会返回错误。
```

12.2.1 WHERE子句的重要性

1. 使用WHERE子句建立联结关系似乎有点奇怪,但实际上是有个很充分的理由的。要记住,**在一条SELECT语句中联结几个表时,相应的关系是在运行中构造的。**在数据库表的定义中没有指示DBMS如何对表进行联结的内容。你必须自己做这件事。在联结两个表时,实际要做的是将第一个表中的每一行与第二个表中的每一行配对。WHERE子句作为过滤条件,只包含那些匹配给定条件(这里是联结条件)的行。没有WHERE子句,第一个表中的每一行将与第二表中的每一行配对,而不管它们逻辑上是否能配在一起。
笛卡尔积(cartesian product)
```
	由没有联结条件的表关系返回的结果为笛卡尔积。检索出的行的数目将是第一个表中的行数乘以第二表中的行数。
```
注意: 不要忘了WHERE子句
```
	要保证所有联结都有WHERE子句,否则DBMS将返回比想要的数据多得多的数据。同理,要保证WHERE子句的正确性。不正确的过滤条件会导致DBMS返回不正确的数据。
```
提示:叉联结
```
	有时,返回笛卡尔积的联结,也称叉联结(cross join)。
```

12.2.2 内联结

1. 目前为止使用的联结称为等值联结(equijoin),它基于两个表之间的相等测试。这种联结也称为内联结(inner join)。其实,可以对这种联结使用稍微不同的语法,明确指定联结的类型。下面的SELECT语句返回与前面例子完全相同的数据:
	1. 输入:
		```
			SELECT vend_name,prod_name,prod_price
			FROM Vendors INNER JOIN Products
			ON Vendors.vend_id = Products.vend_id;
		```
	2. 分析:
		1. 此语句中的SELECT与前面的SELECT语句相同,但FROM字句不同。这里,两个表之间的关系是以INNER JOIN指定的部分FROM子句。在使用这种语法时,联结条件用特定的ON子句而不是WHERE子句给出。传递给ON的实际条件与传递给WHERE的相同。

12.2.3 联结多个表

1. SQL不限制一条SELECT语句中可以联结的表的数目。创建联结的基本规则也相同。首先列出所有表,然后定义表之间的关系。例如:
	1. 输入:
		```
			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;
		```
注意:性能考虑
```
	DBMS在运行时关联指定的每个表,以处理联结。这种处理可能非常耗费资源,因此应该注意,不要联结不必要的表。联结的表越多,性能下降越厉害。
```
注意:联结中表的最大数目
```
	虽然SQL本身不限制每个联结约束中表的数目,但实际上许多DBMS都有限制。
```

第13课 创建高级联结

13.1 使用表别名

	1. SQL除了可以对列名和计算字段使用别名,还允许给表名起别名。这样做有两个主要理由:
		1. 缩短SQL语句
		2. 允许在一条SELECT语句中多次使用相同的表
注意:Oracle中没有AS
```
	Oracle不支持AS关键字。要在Oracle中使用别名,可以不用AS,简单的指定列名即可。
```
	2. 需要注意,表别名只在查询执行中使用。与列别名不一样,表别名不返回到客户端。 

13.2 使用不同类型的联结

1. 迄今为止,我们使用的只是内联结或等值联结的简单联结。现在来看三种其它联结:自联结(self-join)、自然联结(natural join)和外联结(outer join)。

13.2.1 自联结

1. 自联结就是要联结的表,就是第一个表自身。
提示:用自联结而不用子查询
```
	自联结通常作为外部语句,用来替代从相同表中检索数据的子查询语句。虽然最终的结果是相同的,但许多DBMS处理联结远比处理子查询快的多。
```

13.2.2 自然联结

1. 无论何时对表进行联结,应该至少有一列不止出现在一个表中(被联结的列)。标准的联结返回所有数据,相同的列甚至多次出现。自然连接排除多次出现,使每一列只返回一次。
2. 怎么完成这项工作呢?答案是,系统不完成这项工作,由你自己来完成它。自然联结要求你只能选择那些唯一的列,一般通过对一个表使用通配符(*),而对其它表的列使用明确的子集来完成。下面举了一个例子:
	1. 输入:
		```
			SELECT C.*,O.order_name,O.order_date,
					OI.prod_id,OI.quantity,OI.item_price
			FROM Customers C , Orders O ,OrderItems OI
			WHERE C.cust_id = O.cust_id
			AND OI.order_num = O.order_num
			AND prod_id = 'RGAN01'
		```
	2. 分析:
		1. 在这个例子中,通配符只对第一个表使用。所有其它列明确列出,所以没有重复的列被检索出来。
		2. 事实上,我们迄今为止建立的每个内联结都是自然联结,很可能永远都不会用到不是自然联结的内联结。

13.2.3 外联结

1. 许多联结将一个表中的行与另一个表中的行相关联,但有时候需要包含没有关联行的那些行。连接包含那些在相关表中没有关联行的行,这种联结成为外联结。下面举一个简单的外联结的例子:
	1. 输入:
		```
			SELECT Customers.cust_id,Orders_num
			FROM Customers LEFT OUTER JOIN Orders
			ON Customers.cust_id = Orders.cust_id
		```
	2. 分析:
		1. 这条SELECT语句使用了关键字OUTER JOIN来指定联结类型。因为与外联结不同的是外联结还包括没有关联行的行,所以在使用OUTER JOIN语法时,必须使用RIGHT或LEFT关键字指定包括其所有行的表(RIGHT指出的是OUTER JOIN右边的表,而LEFT指出的是OUTER JOIN左边的表)。上面的例子即为从Customers表中选出所有行。
2. 除了上述的左/右外联结外,还存在另一种联结,就是全外联结(full outer join),它检索两个表中的所有行并关联哪些可以关联的行。下面举一个全外联结的简单例子:
	1. 输入:
		```
			SELECT Customers.cust_id,Orders.order_num
			FROM Orders FULL OUTER JOIN Customers
			ON Orders.cust_id = Customers.cust_id;
		```

13.3 使用带聚集函数的联结

1. 下面举一个,简单的例子:
	1. 输入:
		```
			SELECT Customers.cust_id,
					COUNT(Orders.order_num) AS num_ord
			FROM Customers INNER JOIN Orders
			ON Customers.cust_id = Orders.cust_id
			GROUP BY Customers.cust_id
		```
	2. 分析:
		1. 略

13.4 使用联结和联结条件

1. 注意所使用的联结类型。一般我们使用内联结,但是用外联结也有效
2. 关于确切的联结语法,应当查看具体的文档
3. 保证使用正确的联结条件(不管采用那种语法),否则会返回不正确的数据
4. 应该总是提供联结条件,否则会得出笛卡尔积
5. 在一个联结中包含多个表,甚至对每个联结采用不同的联结类型,都是合法的,一般也很有用,但应该在一起测试他们前分别测试每个联结。这会使故障排除更为简单。

第14课 组合查询

1. 本课讲述如何利用UNION操作符将多条SELECT语句组合成一个结果集。

14.1 组合查询

1. 多数SQL查询只包含从一个或多个表中返回数据的单条SELECT语句。但是,SQL也允许执行多个查询(多条SELECT语句),并将结果作为一个查询结果集返回。这些组合查询通常称为并(union)或复合查询(compound query)。
2. 主要有两种情况需要使用组合查询:
	1. 在一个查询中从不同的表返回结构数据
	2. 对一个表执行多个查询,按一个查询返回数据
组合查询和多个WHERE条件
```
	多数情况下,组合相同表的两个查询所完成的工作与具有多个WHERE子句条件的一个查询所完成的工作相同。换句话说,任何具有多个WHERE子句的SELECT语句都可以作为一个组合查询。
```

14.2 创建组合查询

1. 使用UNION很简单,所要做的只是给出每条SELECT语句,在各条语句之间放上关键字UNION。
2. 下面举个简单的例子:加入需要IL、IN、MI三个州的所有顾客的报表及不管位于哪个州的所有的Fun4All
	1. 输入:
		```
			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'
		```
	2. 这条语句由两条SELECT语句组成,之间用UNION关键字分隔,并把输出组合成一个查询结果集,为了便于参考,这里还给出等价效果的WHERE子句的查询
		1. 输入:
			```
				SELECT cust_name,cust_contact,cust_email
				FROM Customers
				WHERE cust_state IN('IL','IN','MI') 
				OR cust_name = 'Fun4All'
			```
3. 从上面这个简单的例子上看,使用UNION比使用WHERE子句更为复杂,但对于较复杂的过滤条件,或者从多个表(而不是一个表)中检索数据的情形,使用UNION可能会是处理更简单
注意:性能问题
```
	多数DBMS使用内部查询优化程序,在处理各条SELECT语句钱组合他们。理论上讲,这意味着从性能上看使用多条WHERE子句条件还是UNION应该没有实际的差别。不过这仅是从理论上来看,实践中多数查询优化程序并不能达到理想状态。
```

14.2.2 UNION规则

1. 在进行组合时需要注意几条规则
	1. UNION必须由两条或两条以上的SELECT语句组成,语句间用关键字UNION分隔
	2. UNION中的每个查询必须包含相同的列、表达式或聚集函数,不过,每个列不需要以相同的次序列出
	3. 列数据类型兼容:类型不必完全相同,但必须是DBMS可以隐含转换的类型。

14.2.3 包含或取消重复的行

1. UNION从查询结果集中自动去除了重复的行。这是UNION的默认行为,如果愿意也可以改变它,如果想返回所有的匹配行,可使用UNION ALL关键字来联结两个SELECT语句。

14.2.4 对组合查询结果排序

1. 在用UNION组合查询时,只能使用一条ORDER BY 子句,它必须位于最后一条SELECT语句之后,虽然ORDER BY子句似乎只是最后一条SELECT语句的组成部分,但实际上DBMS将用它来排序所有SELECT语句返回的所有结果。

第15课 插入数据

15.1 插入数据

1. INSERT用来将行插入(或添加)到数据库表,插入有几种方式:
	1. 插入完整的行
	2. 插入行的一部分
	3. 插入某些查询的结果
插入及系统安全
```
	使用INSERT语句可能需要客户端/服务器DBMS中的特定安全权限。在你试图使用INSERT前,应该保证自己有足够的安全权限。
```

15.1.1 插入完整的行

1. 把数据插入表中最简单方法是使用基本的INSERT语法,它要求指定表名和插入到新行中的值。下面举一个例子:
	1. 语句
		```
			INSERT INTO Customers VALUES('100000000000000006',...);
		```
	2. 分析:
		1. 这个例子将一个新顾客插入到Customers表中。存储到表中每一列的数据在VALUES子句中给出,必须给每一列提供一个值。如果某列没有值,则应该使用NULL值(假定表允许对该列指定空值)。各列必须以它们在表定义中出现的次序填充。
INTO关键字
```
	在某些SQL实现中,跟在INSERT之后的INTO关键字是可选的。但是,即使不一定需要,最好还是提供这个关键字,这样做将保证SQL代码在DBMS之间可移植。
```
2. 上述例子虽然语法简单,但并不安全,应该尽量避免使用。上面的SQL语句高度依赖于表中列的定义次序,还依赖于其容易获得的次序信息。即使可以得到这种次序信息,也不能保证各列在下一次表结构变动后保持完全相同的次序。因此,编写依赖于特定列次序的SQL语句是很不安全的,这样做迟早会出问题。
3. 编写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语句。给出列能使SQL代码继续发挥作用,即使表结构发生了变化
```
小心使用VALUES
```
	不管使用那种INSERT语法,VALUES的数目都必须正确。如果不提供列名,则必须给每个表列提供一个值;如果提供列名,则必须给列出的每个列一个值。否则,就会产生一条错误信息,相应的行不能成功插入。
```

15.1.2 插入部分行

1. 使用这种语法,还可以省略列,这表示可以指给某些列提供值,给其它列不提供值。
省略列
```
	如果表的定义允许,则可以在INSERT操作中省略某些列。省略的列必须满足以下某些条件。
		1. 该列定义为允许NULL值(无值或空值)
		2. 在表定义中给出默认值。这表示如果不给出值,将使用默认值。
	如果表中不允许有NULL值或者默认值,这是却省略了表中的值,DBMS就会产生错误消息,相应的行不能成功插入。
```

15.1.3 插入检索出的数据

1. INSERT一般用来给表插入具有指定列值的行。INSERT还存在另一种形式,可以利用它将SELECT语句的结果插入表中,这就是所谓的INSERT SELECT。顾名思义,它是由一条INSERT语句和一条SELECT语句组成的。
2. 假如想把另一表中的顾客列合并到Customers表中。不需要每次读取一行再将它用INSERT插入,可以如下进行:
	```
		INSERT INTO Customers(cust_id,
								cust_contact,
								cust_email,
								cust_name,
								cust_address,
								cust_city,
								cust_state,
								cust_zip,
								cust_country)
			SELECT cust_id,
			cust_contact,
			cust_email,
			cust_name,
			cust_address,
			cust_city,
			cust_state,
			cust_zip,
			cust_country
			FROM CustNew;
	```
	1. 分析:
		1. 这个例子使用INSERT SELECT从CustNew中将所有数据导入Customers。SELECT语句从CustNew检索出要插入的值,而不是列出它们。SELECT中列出的每一列对应于Customers表名后所跟的每一列。这条语句将插入多少行呢?这依赖于CustNew表有多少行。如果这个表为空,则没有行被插入(也不产生错误,因为操作依然是合法的)。如果这个表确实没有数据,则所有数据将被插入到Customers。
INSERT SELECT中的列名
```
	为了简单起见,这个例子在INSERT 和 SELECT语句中使用了相同的列名。但是,不一定要求列名匹配。事实上,DBMS一定也不关心SELECT返回的列名。它使用的是列的位置,因此SELECT中的第一列(不管其列名)将用来填充表列中指定的第一列,第二列将用来填充表列中指定的第二列,如此等等。
```
3. INSERT SELECT中SELECT语句可以包含WHERE子句,以过滤插入的数据。
插入多行
```
	INSERT通常只插入一行。要插入多行,必须执行多个INSERT语句。INSERT SELECT是个例外,它可以用一条INSERT插入多行,不管SELECT语句返回多少行,都将被INSERT插入。
```

15.2 从一个表复制到另一个表

1. 有一种数据插入不使用INSERT语句。要将一个表的内容复制到一个全新的表(运行中创建的表),可以使用SELECT INTO 语句。
DB2不支持
```
	DB2不支持这里描述的SELECT INTO
```
2. 与INSERT SELECT 将数据添加到一个已经存在的表不同,SELECT INTO将数据复制到一个新表(有的DBMS可以覆盖已经存在的表,这依赖于所使用的具体的DBMS)。
INSERT SELECT 与 SELECT INTO
```
	它们之间的一个重要差别是前者导出数据,而后者导入数据。
```
3. 下面的例子说明如何使用SELECT INTO:
	```
		SELECT *
		INTO CustCopy
		FROM Customers;
	```
	1. 分析:
		1. 这条SELECT语句创建一个名为CustCopy的新表,并把Customers表的整个内容复制到新表中。因为这里使用的是SELECT *,所以将在CustCopy表中创建(并填充)与Customers表的每一列相同的列。要想只复制部分的列,可以明确给出列名,而不是使用[*]通配符。
4. 在使用SELECT INTO时,需要知道一些事情:
	1. 任何SELECT选线和子句都可以使用,包括WHERE和GROUP BY
	2. 可利用联结从多个表插入数据
	3. 不管从多少个表中检索数据,数据都只能插入到一个表中。
进行表的复制
```
	SELECT INTO是实验新SQL语句前进行表复制的很好工具。先进行复制,可在复制的数据上测试SQL代码,而不会影响实际的数据。
```

15.3 小结

1. 这一课介绍了如何将行插入到数据库表中。我们学习了使用INSERT的几种方法,为什么要明确使用列名,如何用INSERT SELECT从其他表中导入行,如何用SELECT INTO将行导出到一个新表。
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐