整理SQL笔记, SQL是最不会过时的一门语言
第一章 数据库和SQL
- RDBMS(关系型数据库管理系统)的结构图.
- SQL语句的种类. 分为三类
- DDL
DDL(Data Definition Language,数据定义语言) 用来创建或者删除存储数据用的数据库以及数据库中的表等对象。DDL 包含以下几种指令。
- CREATE : 创建数据库和表等对象
- DROP : 删除数据库和表等对象
- ALTER : 修改数据库和表等对象的结构
- DML
DML(Data Manipulation Language,数据操纵语言) 用来查询或者变更
表中的记录。DML 包含以下几种指令。
- SELECT :查询表中的数据
- INSERT :向表中插入新数据
- UPDATE :更新表中的数据
- DELETE :删除表中的数据
- DCL
DCL(Data Control Language,数据控制语言) 用来确认或者取消对数据库中的数据进行的变更。除此之外,还可以对 RDBMS 的用户是否有权限操作数据库中的对象(数据库表等)进行设定。DCL 包含以下几种指令。
- COMMIT : 确认对数据库中的数据进行的变更
- ROLLBACK : 取消对数据库中的数据进行的变更
- GRANT : 赋予用户操作权限
- REVOKE : 取消用户的操作权限
实际使用的 SQL 语句当中有 90% 属于 DML
- DDL
DDL(Data Definition Language,数据定义语言) 用来创建或者删除存储数据用的数据库以及数据库中的表等对象。DDL 包含以下几种指令。
- 数据库创建
在创建表之前,一定要先创建用来存储表的数据库。运行
CREATE DATABASE
语句就可以在 RDBMS 上创建数据库了CREATE DATABASE < 数据库名称 > ;
- 表的创建
CREATE TABLE < 表名> ( < 列名 1> < 数据类型 > < 该列所需约束 > , < 列名2> < 数据类型 > < 该列所需约束 >, . . . < 该表的约束 1> , < 该表的约束 2> ,......);
举例:
CREATE TABLE Product (product_id CHAR(4) NOT NULL, product_name VARCHAR(100) NOT NULL, product_type VARCHAR(32) NOT NULL, sale_price memo INTEGER, purchase_price INTEGER, regist_date DATE, PRIMARY KEY (product_id)) default charset=utf8; -- 最后这两个是表的约束,以及设置默认编码
default charset=utf8 设置默认编码不能忘呀
- 数据类型的指定
Product 表所包含的列,定义在 CREATE TABLE Product()的括号中。列名右边的 INTEGER 或者 CHAR 等关键字,是用来声明该列的数据类型的,所有的列都必须指定数据类型。
数据类型表示数据的种类,包括数字型、字符型和日期型等。每一列都不能存储与该列数据类型不符的数据。声明为整数型的列中不能存储 ‘abc’ 这样的字符串,声明为字符型的列中也不能存储 1234 这样的数字
四种基本的数据类型:- INTEGER 型 用来指定存储整数的列的数据类型(数字型),不能存储小数。
- CHAR 型
CHAR 是 CHARACTER(字符)的缩写,是用来指定存储字符串的列的数据类型(字符型)。可以像 CHAR(10) 或者 CHAR(200) 这样,在括号中指定该列可以存储的字符串的长度(最大长度)。字符串超出最大长度的部分是无法输入到该列中的。RDBMS 不同,长度单位也不一样,既存在使用字符个数的情况,也存在使用字节长度(字节是计算机内部的数据单位,一个字符通常需要 1 到 3 个字节来表示根据字符的种类和表现方式有所不同.)的情况。
字符串以 定长字符串 的形式存储在被指定为 CHAR 型的列中。所谓定长字符串,就是当列中存储的字符串长度达不到最大长度的时候,使用半角空格进行补足。例如,我们向 CHAR(8) 类型的列中输入 ‘abc’ 的时候,会以 ‘abcxxxxx’(abc后面有 5 个半角空格,用x表示)的形式保存起来。 - VARCHAR 型
同 CHAR 类型一样, VARCHAR 型也是用来指定存储字符串的列的数据类型(字符串类型),也可以通过括号内的数字来指定字符串的长度(最大长度)。但该类型的列是以可变长字符串 的形式来保存字符串的。定长字符串在字符数未达到最大长度时会用半角空格补足,但可变长字符串不同,即使字符数未达到最大长度,也不会用半角空格补足。例如,我们向 VARCHAR(8) 类型的列中输入字符串 ‘ abc ‘ 的时候,保存的就是字符串 ‘ abc ‘
该类型的列中存储的字符串也和 CHAR 类型一样,是区分大小写的。 - DATE 型 用来指定存储日期(年月日)的列的数据类型(日期型)。
-
字段约束的设置 约束是除了数据类型之外,对列中存储的数据进行限制或者追加条件的功能。
例子:
product_id CHAR(4) NOT NULL, product_name VARCHAR(100) NOT NULL, product_type VARCHAR(32) NOT NULL,
数据类型的右侧设置了 NOT NULL 约束。NULL 是代表空白(无记录)的关键字。在 NULL 之前加上了表示否定的 NOT,就是给该列设置了不能输入空白,也就是必须输入数据的约束(如果什么都不输入就会出错)。
- 主键约束设置
上面的例子中还有这样一句话:
PRIMARY KEY (product_id)
这是用来给 product_id 列设置主键约束的。 所谓键,就是在指定特定数据时使用的列的组合。键种类多样,主键(primary key)就是可以特定一行数据的列。也就是说,特定一行数据,也可以说是唯一确定一行数据。如果把 product_ id 列指定为主键,就可以通过该列取出特定的商品数据了。
反之,如果向 product_id 列中输入了重复数据,就无法取出唯一的特定数据了(因为无法确定唯一的一行数据)。这样就可以为某一列设置主键约束了。 - 表的删除
DROP TABLE < 表名 > ;
- 表定义的增加列
有时好不容易把表创建出来之后才发现少了几列,其实这时无需把表删除再重新创建,只需使用变更表定义的 ALTER TABLE 语句就可以了。ALTER 在英语中就是”改变”的意思。
ALTER TABLE < 表名 > ADD COLUMN < 列的定义 > ; /*Oracle 和 SQL Server 中不用写 COLUMN。 ALTER TABLE < 表名 > ADD < 列名 > ; 另外,在 Oracle 中同时添加多列的时候,可以像下面这样使用括号。 ALTER TABLE < 表名 > ADD ( <列名 >, <列名 > ,......); */
举例:
--添加一列可以存储100位的可变长字符串的product_name_pinyin列 ALTER TABLE Product ADD COLUMN product_name_pinyin VARCHAR(100); --DB2, PostgreSQL, MySQL ALTER TABLE Product ADD (product_name_pinyin VARCHAR2(100)); --Oracle ALTER TABLE Product ADD product_name_pinyin VARCHAR(100); --SQL Server
- 表定义的删除列
ALTER TABLE < 表名> DROP COLUMN < 列名> /*Oracle 中不用写 COLUMN。 ALTER TABLE < 表名 > DROP <列名> ; 另外,在 Oracle 中同时删除多列的时候,可以像下面这样使用括号来实现。 ALTER TABLE < 表名 > DROP ( <列名> , < 列名 > ,......);*/
- 表中插入数据
-- DML :插入数据, 适用于:SQL Server PostgreSQL BEGIN TRANSACTION; -- 1 标示,不同DBMS不一致 INSERT INTO Product VALUES ('0001', 'T恤衫', ' 衣服 ', 1000, 500, '2009-09-20'); INSERT INTO Product VALUES ('0002', '打孔器', ' 办公用品', 500, 320, '2009-09-11'); INSERT INTO Product VALUES ('0003', ' 运动T恤', ' 衣服 ', 4000, 2800, NULL); INSERT INTO Product VALUES ('0004', '菜刀 ', ' 厨房用具 ', 3000, 2800, '2009-09-20'); INSERT INTO Product VALUES ('0005', '高压锅 ', ' 厨房用具 ', 6800, 5000, '2009-01-15'); INSERT INTO Product VALUES '0006', ' 叉子 ', ' 厨房用具 ', 500, NULL, '2009-09-20'); INSERT INTO Product VALUES ('0007', '擦菜板', ' 厨房用具 ', 880, 790, '2008-04-28'); INSERT INTO Product VALUES ('0008', ' 圆珠笔 ', ' 办公用品', 100, NULL,'2009-11-11'); COMMIT; /* DBMS 不同,DML 语句也略有不同。 在 MySQL 中运行时,需要把1中的 BEGIN TRANSACTION; 改写成 START TRANSACTION; 在 Oracle 和 DB2 中运行时,无需使用1中的 BEGIN TRANSACTION;(请予以删除)。 */
- 修改表名字
如果把表名字写错,那么怎么修改?
如果还没有向表中插入数据,那么只需要把表删除,再重新创建一个名称正确的表就可以了。
其实很多数据库都提供了可以修改表名的指令(RENAME)来解决这样的问题。
ALTER TABLE Poduct RENAME TO Product; -- Oracle, PostgreSQL RENAME TABLE Poduct TO Product; -- DB2 sp_rename 'Poduct', 'Product'; -- SQL Server RENAME TABLE Poduct to Product; -- MySQL
第二章 查询基础
SELECT语句基础
- 查询 SELECT
SELECT < 列名 >,...... FROM < 表名 >;
该 SELECT 语句包含了 SELECT 和 FROM 两个子句(clause)。子句是 SQL 语 句的组 成要素,是以 SELECT 或者 FROM 等作为起始的短语。
SELECT 子句中列举了希望从表中查询出的列的名称,而 FROM 子句则指定了选取出数据的表的名称。
举例:SELECT product_id, product_name, purchase_price FROM Product; -- SELECT * FROM Product; --查询所有的列:
- 为列设定别名 AS
SELECT product_id AS id, product_name AS name, FROM Product;
执行结果 id | name | price ——+———+——- 0001 | T恤衫 | 500 0002 | 打孔器 | 320 0003 | 运动T恤 | 2800
别名可以使用中文,使用中文时需要用双引号(“)括起来。请注意不是单引号(‘)。 - 从结果中删除重复的行
可以通过在 SELECT 子句中使用
DISTINCT
来实现SELECT DISTINCT product_type FROM Product;
在使用 DISTINCT 时,NULL 也被视为一类数据。NULL 存在于多行中时 , 也会被合并为一条 NULL 数据。
DISTINCT 也可以在多列之前使用.此时,会将多个列的数据进行组合,将重复的数据合并为一条。(需要将查询出来的字段结果都完全一样才会隐藏)SELECT DISTINCT product_type, regist_date FROM Product;
DISTINCT 关键字只能用在第一个列名之前。因此,请大家注意不能写成
regist_date, DISTINCT product_type
。 - 根据where语句来选择记录
SELECT < 列名 >, ...... FROM < 表名 > WHERE < 条件表达式 >;
举例:
SELECT product_name, product_type FROM Product WHERE product_type = ' 衣服 '; -- " product _type = ' 衣服 ' "就是用来表示查询条件的表达式(条件表达式)。等号是比较两边的内容是否相等的符号,
SQL 中子句的书写顺序是固定的,不能随意更改。WHERE 子句必须紧跟在 FROM 子句之后,书写顺序发生改变的话会造成执行错误
- SQL的注释
有两种方法:
- 行注释 书写在”–“之后,只能写在同一行。
- 多行注释 书写在”/“和”/”之间,可以跨多行。
可以穿插在语句之中:
SELECT DISTINCT product_id, purchase_price -- 本SELECT语句会从结果中删除重复行。 FROM Product;
算术运算符
- 算数运算符
四中算数运算符 加法运算 + 减法运算 - 乘法运算 * 除法运算 /
SELECT product_name, sale_price, sale_price * 2 AS "sale_price_x2" FROM Product;
执行结果
product_name | sale_price | sale_price_x2 ---------------+-------------+---------------- T恤衫 | 1000 | 2000 打孔器 | 500 | 1000 运动T恤 | 4000 | 8000 -- 。运算就是这样以行为单位执行的。
四则运算所使用的运算符 ( + 、 - 、 * 、 / )称为算术运算符。运算符就是使用其两边的值进行四则运算或者字符串拼接、数值大小比较等运算,并返回结果的符号。加法运算符( + ) 前后如果是数字或者数字类型的列名的话,就会返回加法运算后的结果。
-
算数运算符中的NULL 运算时,5 + NULL? 1 * NULL? NULL / 0? 是什么吗呢? 正确答案全部都是 NULL 所有包含 NULL 的计算,结果肯定是 NULL
- 比较运算符
运算符 含义 = 和 ~ 相等 <> 和 ~ 不相等, 也可以用 != >= 大于等于 ~ > 大于 ~ <= 小于等于 ~ < 小于 ~
这些比较运算符可以对字符、数字和日期等几乎所有数据类型的列和值进行比较。 举例:
-- 选取出销售单价大于等于1000日元的记录 SELECT product_name, product_type, sale_price FROM Product WHERE sale_price >= 1000;
-- 选取出登记日期在 2009年9月27日之前的记录 SELECT product_name, product_type, regist_date FROM Product WHERE regist_date < '2009-09-27';
小于某个日期就是在该日期之前的意思。想要实现在某个特定日期(包含该日期)之后的查询条件时,可以使用代表大于等于的 >= 运算符。
还可以使用比较运算符对计算结果进行比较SELECT product_name, sale_price, purchase_price FROM Product WHERE sale_price - purchase_price >= 500;
- 比较运算符中的NULL
不能对 NULL 使用比较运算符
SELECT product_name, purchase_price FROM Product WHERE purchase_price <> 2800;
执行结果中并没有”叉子”和”圆珠笔”。这两条记录由于进货单价不明(NULL),因此无法判定是不是 2800 日元。
那如果想选取进货单价为 NULL 的记录的话,条件表达式该怎么写呢? 用purchase_price = NULL
是查询不出的,是错误的.
SQL提供了专门用来判断是否为 NULL 的IS NULL
运算符。SELECT product_name, purchase_price FROM Product WHERE purchase_price IS NULL;
反之,希望选取不是 NULL 的记录时,需要使用
IS NOT NULL
运算符SELECT product_name, purchase_price FROM Product WHERE purchase_price IS NOT NULL;
逻辑运算符
- NOT 运算符
NOT 运算符用来否定某一条件,但是不能滥用。
SELECT product_name, product_type, sale_price FROM Product WHERE NOT sale_price >= 1000; -- 等价于 SELECT product_name, product_type FROM Product WHERE sale_price < 1000;
通过以上的例子大家可以发现,不使用 NOT 运算符也可以编写出效果相同的查询条件。不仅如此,不使用 NOT 运算符的查询条件更容易让人理解。 虽然如此,但是也不能完全否定 NOT 运算符的作用。在编写复杂的SQL 语句时,经常会看到 NOT 的身影。这里只是希望大家了解 NOT 运算符的书写方法和工作原理,同时提醒大家不要滥用该运算符。
- AND 运算符和 OR 运算符
在 WHERE 子句中使用 AND 运算符或者 OR 运算符,可以对多个查询条件进行组合。
- AND 运算符在其两侧的查询条件都成立时整个查询条件才成立,其意思相当于”并且”。
- OR 运算符在其两侧的查询条件有一个成立时整个查询条件都成立,其意思相当于”或者” 。
AND举例:
SELECT product_name, purchase_price FROM Product WHERE product_type = ' 厨房用具 ' AND sale_price >= 3000; -- 必须条件都满足
OR举例:
SELECT product_name, purchase_price FROM Product WHERE product_type = ' 厨房用具 ' OR sale_price >= 3000; -- 其中一个条件满足就返回该条数据
通过括号强化处理 有需求:”商品种类为办公用品”并且”登记日期是 2009 年 9 月 11 日或者 2009 年 9 月 20 日”
SELECT product_name, product_type, regist_date FROM Product WHERE product_type = '办公用品 ' AND regist_date = '2009-09-11' OR regist_date = '2009-09-20'; /* 这样写是不对的,因为AND的优先级比OR要高 被皆是成为这个样子: 「 product_type = ' 办公用品' AND regist_date = '2009-09-11' 」 OR 「 regist_date = '2009-09-20' 」 */
应该这样写
SELECT product_name, product_type, regist_date FROM Product WHERE product_type = ' 办公用品' AND (regist_date = '2009-09-11' OR regist_date = '2009-09-20');
AND 运算符的优先级高于 OR 运算符。想要优先执行 OR 运算符时可以使用括号。
第三章 聚合和排序
聚合函数
- 聚合函数
用于汇总的函数称为聚合函数或者聚集函数
- COUNT :计算表中的记录数(行数)
- SUM : 计算表中数值列中数据的合计值
- AVG : 计算表中数值列中数据的平均值
- MAX : 求出表中任意列中数据的最大值
- MIN : 求出表中任意列中数据的最小值
- COUNT
SELECT COUNT(*) FROM Product; -- 结果为8
COUNT ( ) 中的星号,代表全部列的意思。COUNT 函数的输入值就记述在其后的括号中。此处的输入值称为参数或者 parameter,输出值称为返回值。
计算 NULL 之外的数据的行数 想要计算表中全部数据的行数时,可以像 SELECT COUNT(*) ~ 这样使用星号。 如果想得到 purchase_price 列(进货单价)中非空行数的话,可以采用下面这种方法.SELECT COUNT(purchase_price) FROM Product; -- 结果为6 -- purchase_price 列中有两行数据是NULL, 因此并不应该计算这两行。
对于 COUNT 函数来说,参数列不同计算的结果也会发生变化 该特性是 COUNT 函数所特有的,其他函数并不能将星号作为参数(如果使用星号会出错)。 COUNT 函数的结果根据参数的不同而不同。COUNT(*) 会得到包含 NULL 的数据行数,而 COUNT(< 列名 >) 会得到 NULL 之外的数据行数。
- SUM函数
SUM / AVG 函数只适用于数值类型的列。
SELECT SUM(sale_price) FROM Product;
也可以一起计算出来多个和
SELECT SUM(sale_price), SUM(purchase_price) FROM Product;
聚合函数会将 NULL 排除在外。但 COUNT(*) 例外,并不会排除 NULL。 求和过程中,如果有NULL的话,就相当于是0,不影响.
问题:“四则运算中如果存在 NULL ,结果一定是 NULL ,那此时进货单价的合计值会不会也是 NULL 呢?” 实际上这两者并不矛盾。从结果上说,所有的聚合函数,如果以列名为参数,那么在计算之前就已经把NULL 排除在外了。因此,无论有多少个 NULL 都会被无视。 - AVG 函数
SELECT AVG(sale_price) FROM Product; -- 相当于:(1000+500+4000+3000+6800+500+880+100) / 8
也可以多个
SELECT AVG(sale_price), AVG(purchase_price) FROM Product;
同理,也会忽略NULL
- MAX 和 MIN 函数
MAX/MIN 函数几乎适用于所有数据类型的列
SELECT MAX(sale_price), MIN(purchase_price) FROM Product;
MAX/MIN 函数和 SUM/AVG 函数有一点不同,那就是 SUM/AVG 函数只能对数值类型的列使用,而 MAX/MIN 函数原则上可以适用于任何数据类型的列。 MAX/MIN 函数适用于任何数据类型的列,也就是说,只要是能够排序的数据,就肯定有最大值和最小值,也就能够使用这两个函数。 对日期来说,平均值和合计值并没有什么实际意义,因此不能使用SUM/AVG 函数,可以使用MAX/MIN(日期可以比较大小)。 这点对于字符串类型的数据也适用,字符串类型的数据能够使用 MAX/MIN 函数,但不能使用 SUM/AVG 函数。 MAX/MIN 函数几乎适用于所有数据类型的列。SUM / AVG 函数只适用于数值类型的列。
- 使用聚合函数删除重复值(关键字 DISTINCT)
例如,拿商品种类来说,表中总共有 3 种商品共 8 行数据,其中衣服2 行,办公用品 2 行,厨房用具 4 行。如果想要计算出商品种类的个数,怎么做比较好呢?删除重复数据然后再计算数据行数似乎是个不错的办法。
SELECT COUNT(DISTINCT product_type) FROM Product; -- 请注意,这时 DISTINCT 必须写在括号中。这是因为必须要在计算行数之前删除 product _type 列中的重复数据.
想要计算值的种类时,可以在 COUNT 函数的参数中使用 DISTINCT。
不仅限于 COUNT 函数,所有的聚合函数都可以使用 DISTINCT 。SELECT SUM(sale_price), SUM(DISTINCT sale_price) FROM Product; -- 如果商品价格中有相同的商品,后者在求和过程中只会计算一次,也就是过滤之后再求和
在聚合函数的参数中使用 DISTINCT,可以删除重复数据。
对表进行分组
使用 GROUP BY 子句可以像切蛋糕那样将表分割。通过使用聚合函数和GROUP BY 子句,可以根据“商品种类”或者“登记日期”等将表分割后再进行汇总。
- GROUP BY
语法:
SELECT< 列名 1>, < 列名 2>, < 列名 3>, ...... FROM < 表名> GROUP BY < 列名1>, < 列名 2>, < 列名 3>, ...... ;
GROUP BY 子句中指定的列名(即聚合键) 举例:
SELECT product_type, COUNT(*) FROM Product GROUP BY product_type; /* product_type | count --------------+------ 衣服 | 2 办公用品 | 2 厨房用具 | 4 如上所示,未使用 GROUP BY 子句时,结果只有 1 行,而这次的结果却是多行。 这是因为不使用 GROUP BY 子句时,是将表中的所有数据作为一组来对待的。而使用 GROUP BY 子句时,会将表中的数据分为多个组进行处理 */
你可能会有疑问,如果是这样
SELECT product_type, COUNT(*) FROM Product;
会输出什么? 答案是:会报错,In aggregated query without GROUP BY, expression #1 of SELECT list contains nonaggregated column 'shop.Product.product_type'; this is incompatible with sql_mode=only_full_group_by
也就是在没有分组依据的聚合查询中,选择列表的表达式包含非聚合列。这与sql_mode=only_full_group_by不兼容
GROUP BY 就像是切分表的一把刀。 -
聚合键中包含 NULL 的情况 聚合键中包含 NULL 时,在结果中会以“不确定”行(空行)的形式表现出来。
- 使用 WHERE 子句时 GROUP BY 的执行结果
SELECT < 列名 1>, < 列名 2>, < 列名3>, ...... FROM < 表名 > WHERE GROUP BY < 列名 1>, < 列名 2>, < 列名 3>, ...... ;
像这样使用 WHERE 子句进行汇总处理时,会先根据 WHERE 子句指定的条件进行过滤,然后再进行汇总处理。
举例:SELECT purchase_price, COUNT(*) FROM Product WHERE product_type = '衣服' GROUP BY purchase_price; /*因为上述 SELECT 语句首先使用了 WHERE 子句对记录进行过滤,所以实际上作为聚合对象的记录只有 2 行(只有两件是衣服) 然后根据这两行的不同价格,进行聚合查询*/
子句的书写顺序SELECT → 2. FROM → 3. WHERE → 4. GROUP BY
GROUP BY 和 WHERE 并用时 SELECT 语句的执行顺序
FROM → WHERE → GROUP BY → SELECT
由于在 SQL 语句中,书写顺序和 DBMS 内部的执行顺序并不相同。这也是 SQL 难以理解的原因之一。
- 与聚合函数和 GROUP BY 子句有关的常见错误
- 常见错误1 ——在 SELECT 子句中书写了多余的列
在使用 COUNT 这样的聚合函数时,SELECT 子句中的元素有严格的限制。实际上,使用聚合函数时,SELECT 子句中只能存在以下三种元素。
- 常数
- 聚合函数
- GROUP BY 子句中指定的列名(也就是聚合键)
举例: ```sql SELECT product_name, purchase_price, COUNT() FROM Product GROUP BY purchase_price; / 错误如下: ERROR :列 “product,product_name” 必须包含在 GROUP BY 子句之中,或者必须在聚合函数内使用行 行1: SELECT product_name, purchase_price, COUNT(*)
解析: 通过某个聚合键将表分组之后,结果中的一行数据就代表一组。例如,使用进货单价将表进行分组之后,一行就代表了一个进货单价。问题就出在这里,聚合键和商品名并不一定是一对一的。 例如,进货单价是 2800 日元的商品有“运动 T 恤”和“菜刀”两种, 但是 2800 日元这一行应该对应哪个商品名呢?
*/ ``` 使用 GROUP BY 子句时,SELECT 子句中不能出现聚合键之外的列名。
- 常见错误2 ——在 GROUP BY 子句中写了列的别名
这也是一个非常常见的错误。SELECT 子句中的项目可以通过 AS 关键字来指定别名。但是,在 GROUP BY 子句中是不能使用别名的.
GROUP BY 子句中使用列的别名会引发错误SELECT product_type AS pt, COUNT(*) FROM Product GROUP BY pt; /* 上述语句发生错误的原因之前已经了解过了,是 SQL 语句在 DBMS内部的执行顺序造成的——SELECT 子句在 GROUP BY 子句之后执行。在执行 GROUP BY 子句时,SELECT 子句中定义的别名,DBMS 还并不知道。 */
在 GROUP BY 子句中不能使用 SELECT 子句中定义的别名。
-
常见错误3 —— GROUP BY 子句的结果能排序吗 GROUP BY 子句的结果通常都包含多行,有时可能还会是成百上千行。那么,这些结果究竟是按照什么顺序排列的呢? 答案是:“随机的。”
我们完全不知道结果记录是按照什么规则进行排序的。可能乍一看是按照行数的降序或者聚合键的升序进行排列的,但其实这些全都是偶然的。当你再次执行同样的 SELECT 语句时,得到的结果可能会按照完全不同的顺序进行排列。 - 常见错误4 ——在 WHERE 子句中使用聚合函数
如果我们想要取出恰好包含 2 行数据的组该怎么办呢?满足要求的是“办公用品”和“衣服”。
-- 错误实例 SELECT product_type, COUNT(*) FROM Product WHERE COUNT(*) = 2 GROUP BY product_type; /* 遗憾的是,这样的 SELECT 语句在执行时会发生错误。 ERROR: 不能在WHERE子句中使用聚合 行 3: WHERE COUNT(*) = 2 ^ 实际上,只有 SELECT 子句和 HAVING 子句(ORDER BY 子句)中能够使用 COUNT 等聚合函数。 */
只有 SELECT 子句和 HAVING 子句(以及 ORDERBY 子句)中能够使用聚合函数。
- 常见错误1 ——在 SELECT 子句中书写了多余的列
在使用 COUNT 这样的聚合函数时,SELECT 子句中的元素有严格的限制。实际上,使用聚合函数时,SELECT 子句中只能存在以下三种元素。
为聚合结果指定条件
- HAVING 子句
SELECT < 列名 1>, < 列名 2>, < 列名 3>, ...... FROM < 表名 > GROUP BY < 列名 1>, < 列名 2>, < 列名 3>, ...... HAVING < 分组结果对应的条件 >
HAVING 子句必须写在 GROUP BY 子句之后
问题:针对按照商品种类进行分组后的结果,指定“包含的数据行数为2行”这一条件的SELECT 语句SELECT product_type, COUNT(*) FROM Product GROUP BY product_type HAVING COUNT(*) = 2; -- 我们可以看到执行结果中并没有包含数据行数为 4 行的 “厨房用具”。 -- 另一个例子: SELECT product_type, AVG(sale_price) FROM Product GROUP BY product_type HAVING AVG(sale_price) >= 2500; -- 按照product_type分组,但是在聚合函数中可以使用其它列(比如求该分组每组的平均单价的sale_price)
- HAVING 子句的构成要素
- 常数
- 聚合函数
- GROUP BY 子句中指定的列名(即聚合键) 错误示例: ```sql SELECT product_type, COUNT(*) FROM Product GROUP BY product_type HAVING product_name = ‘ 圆珠笔 ‘;
/*结果: ERROR: 列 “product,product_name” 必须包含在 GROUP BY 子句当中,或者必须在聚合函数中使用 行 4: HAVING product_name = ‘圆珠笔’;
product_name 列并不包含在 GROUP BY 子句之中,因此不允许写在 HAVING 子句里。 可以把这种情况想象为使用 GROUP BY 子句时的 SELECT 子句。汇总之后得到的表中并不存在 product_name 这个列(只有product_type, 和 count),SQL 当然无法为表中不存在的列设定条件了。 */ ```
- 相对于 HAVING 子句,更适合写在 WHERE 子句中的条件
有的条件分别写在 WHERE 子句和 HAVING 子句当中,返回的结果都完全相同
-- 将条件书写在 HAVING 子句中的情况 SELECT product_type, COUNT(*) FROM Product GROUP BY product_type HAVING product_type = ' 衣服'; -- 将条件书写在 WHERE 子句中的情况 SELECT product_type, COUNT(*) FROM Product WHERE product_type = ' 衣服 ' GROUP BY product_type;
聚合键所对应的条件还是应该书写在 WHERE 子句之中。 首先,根本原因是 WHERE 子句和 HAVING 子句的作用不同。如前 所述, HAVING 子句是用来指定“组”的条件的。因此, “行”所对应的 条件还是应该写在 WHERE 子句当中。这样一来,书写出的 SELECT 语句不但可以分清两者各自的功能,理解起来也更加容易。 WHERE 子句 = 指定行所对应的条件 HAVING 子句 = 指定组所对应的条件
对查询结果进行排序
- ORDER BY 子句
SELECT < 列名 1>, < 列名 2>, < 列名3>, ...... FROM < 表名 > ORDER BY < 排序基准列 1>, < 排序基准列 2>, ...... -- 默认由低到高
举例:
-- 按照销售单价由低到高(升序)进行排列 SELECT product_id, product_name, sale_price, purchase_price FROM Product ORDER BY sale_price; /*不论何种情况, ORDER BY 子句都需要写在 SELECT 语句的末尾。这是因为对数据行进行排序的操作必须在结果即将返回时执行。 ORDER BY子句中书写的列名称为排序键。*/
-
指定升序或降序 由高到低: 在列名后面使用 DESC 关键字。(descendent(下降的)) 由低到高: 升序进行排列时,正式的书写方式应该是使用关键字 ASC (ascendent(上升的)) 未指定 ORDER BY 子句中排列顺序时会默认使用升序进行排列。 由于 ASC 和 DESC 这两个关键字是以列为单位指定的,因此可以同时指定一个列为升序,指定其他列为降序。
- 指定多个排序键
针对某个列的值相同的情况下,采用另一个列的值来排序(如果只指定一个排序的话,那么遇到相同的值的情况下针对该相同的列就是随机排序了)
举例:
-- 按照销售单价和商品编号的升序进行排序 SELECT product_id, product_name, sale_price, purchase_price FROM Product ORDER BY sale_price, product_id; -- 价格相同时按照商品编号的升序排序
这样一来,就可以在 ORDER BY 子句中同时指定多个排序键了。规则是优先使用左侧的键,如果该列存在相同值的话,再接着参考右侧的键。当然,也可以同时使用 3 个以上的排序键。
-
排序中包含NULL的情况 因为NULL是不能排序比较大小的,究竟是在开头显示还是在末尾显示,并没有特殊规定。某些 DBMS中可以指定 NULL 在开头或末尾显示 排序键中包含 NULL 时,会在开头或末尾进行汇总。
- 在排序键中使用显示用的别名
在 GROUP BY 子句中不能使用SELECT 子句中定义的别名,但是在 ORDER BY 子句中却是允许使用别名的。(因为执行的顺序不同)
SELECT product_id AS id, product_name, sale_price AS sp, purchase_price FROM Product ORDER BY sp, id;
一定要记住 SELECT 子句(两个关键字之间的句子,比如你使用了AS这样的表达式)的执行顺序在 GROUP BY 子句之后, ORDER BY 子句之前。因此,在执行 GROUP BY 子句时, SELECT 语句中定义的别名无法被识别。对于子在 SELECT 子句之后执行的 ORDER BY 子句来说,就没有这样的问题了。 在 ORDER BY 子句中可以使用 SELECT 子句中定义的别名。
- ORDER BY 子句中可以使用的列
- ORDER BY 子句中也可以使用存在于表中、但并不包含在 SELECT子句之中的列
-- SELECT 子句中未包含的列也可以在 ORDER BY 子句中使用 SELECT product_name, sale_price, purchase_price FROM Product ORDER BY product_id;
- 还可以使用聚合函数
SELECT product_type, COUNT(*) FROM Product GROUP BY product_type ORDER BY COUNT(*);
- ORDER BY 子句中也可以使用存在于表中、但并不包含在 SELECT子句之中的列
第四章 数据更新
数据的插入(INSERT语句的使用方法)
将列名和值用逗号隔开,分别括在()内,这种形式称为清单 对表中所有列进行 INSERT 操作时可以省略表名后的列清单。
- INSERT
INSERT INTO < 表名> ( 列 1 , 列 2 , 列 3, ......) VALUES ( 值 1, 值 2 , 值 3, ......);
eg:
INSERT INTO ProductIns (product_id, product_name, product_type,sale_price, purchase_price, regist_date) VALUES ('0001', 'T 恤衫 ',' 衣服 ', 1000, 500, '2009-09-20'); /*将列名和值用逗号隔开,分别括在()内,这种形式称为清单。 列清单→ (product _id, product_ name, product _type ,sale_price , purchase_price , regist_date) 值清单→ (' 0001 ', ' T恤衫', ' 衣服', 1000 , 500 ,' 2009 - 09 - 20 ')
原则上,执行一次 INSERT 语句会插入一行数据。 但它也仅仅是原则而已,其实很多 RDBMS 都支持一次插入多行数据,这样的功能称为多行 INSERT(multi row INSERT)。
-- 多行INSERT ( Oracle以外) INSERT INTO ProductIns VALUES ('0002','打孔器',' 办公用品 ', 500, 320, '2009-09-11'), ('0003','运动T恤',' 衣服 ', 4000, 2800, NULL), ('0004','菜刀 ',' 厨房用具 ', 3000, 2800, '2009-09-20');
-
列清单的省略 对表进行全列 INSERT 时,可以省略表名后的列清单。这时 VALUES子句的值会默认按照从左到右的顺序赋给每一列。 ```sql – 包含列清单 INSERT INTO ProductIns (product_id, product_name, product_type,sale_price, purchase_price, regist_date) VALUES (‘0005’, ‘ 高压锅 ‘,’ 厨房用具 ‘, 6800, 5000, ‘2009-01-15’); – 省略列清单 INSERT INTO ProductIns VALUES (‘0005’, ‘ 高压锅 ‘, ‘ 厨房用具 ‘, 6800, 5000, ‘2009-01-15’);
- 插入NULL
INSERT 语句中想给某一列赋予 NULL 值时,可以直接在 VALUES子句的值清单中写入 NULL。
INSERT INTO ProductIns (product_id, product_name, product_type, sale_price, purchase_price, regist_date) VALUES ('0006', ' 叉子 ', '厨房用具 ', 500, NULL, '2009-09-20');
但是,想要插入 NULL 的列一定不能设置 NOT NULL 约束。向设置了 NOT NULL 约束的列中插入 NULL 时,INSERT 语句会出错,导致数据插入失败。 插入失败指的是希望通过 INSERT 语句插入的数据无法正常插入到表中,但之前已经插入的数据并不会被破坏。
- 插入默认值
如果在创建表的同时设定了默认值,就可以在 INSERT 语句中自动为列赋值了。默认值的使用方法通常有显式和隐式两种
- 通过显式方法插入默认值
在 VALUES 子句中指定 DEFAULT 关键字
INSERT INTO ProductIns (product_id, product_name, product_type, sale_price, purchase_price, regist_date) VALUES ('0007', ' 擦菜板 ', ' 厨房用具 ', DEFAULT, 790, '2009-04-28');
- 通过隐式方法插入默认值
插入默认值时也可以不使用 DEFAULT 关键字,只要在列清单和VALUES 中省略设定了默认值的列就可以了
INSERT INTO ProductIns (product_id, product_name, product_type, purchase_price, regist_date) VALUES ('0007', ' 擦菜板 ', ' 厨房用具 ', 790, '2009-04-28'); -- 不写sale_price. 这样也可以给 sale_price 赋上默认值 0 。
说到省略列名,还有一点要说明一下。如果省略了没有设定默认值的列,该列的值就会被设定为 NULL。因此,如果省略的是设置了 NOTNULL 约束的列, INSERT 语句就会出错
建议大家使用显式的方法。因为这样可以一目了然地知道 sale _price 列使用了默认值,SQL 语句的含义也更加容易理解。
- 通过显式方法插入默认值
在 VALUES 子句中指定 DEFAULT 关键字
-
从其他表中复制数据 要插入数据,除了使用 VALUES 子句指定具体的数据之外,还可以从其他表中复制数据。 使用
INSERT ... SELECT 语句
-- 将商品表中的数据复制到商品复制表中 INSERT INTO ProductCopy (product_id, product_name, product_type, sale_price, purchase_price, regist_date) SELECT product_id, product_name, product_type, sale_price, purchase_price, regist_date FROM Product; -- 名字不一定要一一对应,顺序会一一对应
多种多样的 SELECT 语句来插入新表INSERT INTO ProductType (product_type, sum_sale_price, sum_purchase_price) SELECT product_type, SUM(sale_price), SUM(purchase_price) FROM Product GROUP BY product_type;
查看结果:
-- 确认插入的数据行 SELECT * FROM ProductType; /* product_type | sum_sale_price | sum_purchase_price --------------+-----------------+-------------------- 衣服 | 5000 | 3300 办公用品 | 600 | 320 厨房用具 | 11180 | 8590 */
INSERT 语句的 SELECT 语句中,可以使用 WHERE 子句或者 GROUP何 SQL 语法 (但使用 ORDER BY 子句并不会产生任何效果, 不会按排序插入).
数据的删除
删除数据的方法大体可以分为以下两种。
- DROP TABLE 语句可以将表完全删除
- DELETE 语句会留下表(容器),而删除表中的全部数据
- DELETE 语句的基本语法
保留数据表,仅删除全部数据行的 DELETE 语句
DELETE FROM < 表名 >;
补充: 如果语句中忘了写 FROM ,而是写成了“DELETE < 表名 >”,或者写了多余的列名,都会出错,无法正常执行 前者无法正常执行的原因是删除对象不是表,而是表中的数据行(记录)。这样想的话就很容易理解了吧 后者错误的原因也是如此。因为 DELETE 语句的对象是行而不是列,所以 DELETE 语句无法只删除部分列的数据。因此,在 DELETE 语句中指定列名是错误的。 DELETE 语句的删除对象并不是表或者列,而是记录(行)。
- 指定删除对象的 DELETE 语句(搜索型 DELETE)
DELETE FROM < 表名 > DELETE < 条件 >;
eg:
DELETE FROM Product WHERE sale_price >= 4000;
与 SELECT 语句不同的是, DELETE 语句中不能使用 GROUP BY 、HAVING 和 ORDER BY 三类子句,而只能使用 WHERE 子句。原因很简单,GROUP BY 和 HAVING 是从表中选取数据时用来改变抽取数据形式的,而 ORDER BY 是用来指定取得结果显示顺序的。因此,在删除表中数据时它们都起不到什么作用。
数据的更新( UPDATE)
使用 UPDATE 语句可以更改(更新)表中的数据。
- UPDATE 语句的基本语法
UPDATE < 表名 > SET < 列名 > = < 表达式 >;
eg:
UPDATE Product SET regist_date = '2009-10-10'; -- 将这一列的数据全部变为'2009-10-10'
- 指定条件的 UPDATE 语句(搜索型 UPDATE)
UPDATE < 表名 > SET < 列名> = <表达式> WHERE < 条件 >;
eg:
UPDATE Product SET sale_price = sale_price * 10 WHERE product_type = '厨房用具 '; -- 将product_type = '厨房用具 '条件下的 sale_price提升为原来的十倍
- 使用 NULL 进行更新
UPDATE Product SET regist_date = NULL WHERE product_id = '0008'; --将该行的regist_date设置为NULL
使用 UPDATE 语句可以将值清空为 NULL (但只限于未设置 NOTNULL 约束的列)。
- 多列更新
-- 使用逗号对列进行分隔排列 UPDATE Product SET sale_price = sale_price * 10, purchase_price = purchase_price / 2 WHERE product_type = '厨房用具 '; -- 可以同事更新两个或者多个字段
事务
事务处理的终止指令包括 COMMIT (提交处理)和 ROLLBACK (取消处理)两种。 DBMS 的事务具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)四种特性。通常将这四种特性的首字母结合起来,统称为 ACID 特性。 事务是需要在同一个处理单元中执行的一系列更新处理的集合。
- 创建事务(提交,回滚)
事务开始语句; DML 语句1 ; DML 语句2 ; DML 语句3 ; . . . 事务结束语句( COMMIT 或者 ROLLBACK ) ;
在标准 SQL 中并没有定义事务的开始语句.比较有代表性的语法如下所示
- SQL Server、PostgreSQL
BEGIN TRANSACTION
- MySQL
STARTTRANSACTION
- Oracle、DB2 无 事务结束语句只有COMMIT 和 ROLLBACK 两 种,在所有的 RDBMS 中都是通用的
eg:
START TRANSACTION; -- mysql数据库 -- 将运动 T 恤的销售单价降低1000日元 UPDATE Product SET sale_price = sale_price - 1000 WHERE product_name = ' 运动 T 恤 '; -- 将T 恤衫的销售单价上浮1000日元 UPDATE Product SET sale_price = sale_price + 1000 WHERE product_name = 'T 恤衫 '; COMMIT;
COMMIT——提交处理 COMMIT 是提交事务包含的全部更新处理的结束指令,相当于文件处理中的覆盖保存。一旦提交,就无法恢复到事务开始前的状态了。START TRANSACTION; ------------------MYSQL数据库 -- 将运动T恤的销售单价降低1000 日元 UPDATE Product SET sale_price = sale_price - 1000 WHERE product_name = ' 运动T恤 '; -- 将T恤衫的销售单价上浮1000 日元 UPDATE Product SET sale_price = sale_price + 1000 WHERE product_name = 'T恤衫 '; COMMIT;
ROLLBACK——取消处理 ROLLBACK 是取消事务包含的全部更新处理的结束指令,相当于文件处理中的放弃保存。一旦回滚,数据库就会恢复到事务开始之前的状态START TRANSACTION; ------------------MYSQL数据库 -- 将运动T恤的销售单价降低1000 日元 UPDATE Product SET sale_price = sale_price - 1000 WHERE product_name = ' 运动T恤 '; -- 将T恤衫的销售单价上浮1000 日元 UPDATE Product SET sale_price = sale_price + 1000 WHERE product_name = 'T恤衫 '; ROLLBACK;
- SQL Server、PostgreSQL
- 扩展:事务何时开始
几乎所有的数据库产品的事务都无需开始指令.像这样不使用指令而悄悄开始事务的情况下,应该如何区分各个事务呢?通常会有如下两种情况。
- 每条 SQL 语句就是一个事务(自动提交模式)
- 直到用户执行 COMMIT 或者 ROLLBACK 为止算作一个事务
默认使用自动提交模式的DBMS 有 SQL Server、PostgreSQL 和 MySQL 等 在默认使用 B 模式的 Oracle 中,事务都是直到用户自己执行提交或者回滚指令才会结束。 可以这样理解,默认你写一条数据提交一条,但是如果你使用了开始事务这个语句,在接下来的所有语句都不会自动提交,除非你使用了COMMIT或者ROLLBACK
自动提交的情况需要特别注意的是 DELETE 语句。如果不是自动提交,即使使用 DELETE 语句删除了数据表,也可以通过 ROLLBACK 命令取消该事务的处理,恢复表中的数据。但这仅限于明示开始事务,或者关闭自动提交的情况。如果不小心在自动提交模式下执行了 DELETE 操作,即使再回滚也无济于事了。这是一个很严重的问题,初学者难免会碰到这样的麻烦。一旦误删了数据,如果无法重新插入,是不是想哭的心都有了?所以一定要特别小心。
第五章 复杂查询
视图
- 视图
视图究竟是什么呢?如果用一句话概述的话,就是“从 SQL 的角度来看视图就是一张表”
使用视图时并不会将数据保存到存储设备之中,而且也不会将数据保存到其他任何地方。实际上视图保存的是 SELECT 语句
表中存储的是实际数据,而视图中保存的是从表中取出数据所使用的 SELECT 语句。
优点:
- 第一点是由于视图无需保存数据,因此可以节省存储设备的容量。
- 第二个优点就是可以将频繁使用的 SELECT 语句保存成视图,这样就不用每次都重新书写了。
应该将经常使用的 SELECT 语句做成视图。
- 创建视图
CREATE VIEW 视图名称 (< 视图列名 1>, < 视图列名2>, ......) AS <SELECT 语句 > /*SELECT 语句需要书写在 AS 关键字之后。 SELECT 语句中列的排列顺序和视图中列的排列顺序相同, SELECT 语句中的第 1 列就是视图中的第 1 列*/
eg:
-- 创建视图 CREATE VIEW ProductSum (product_type, cnt_product) AS SELECT product_type, COUNT(*) FROM Product GROUP BY product_type; -- 使用视图 SELECT product_type, cnt_product FROM ProductSum; -- 就是相当于创建了一张表
当然,也可以在视图上面继续创建视图,但应该尽量避免,多重视图会降低 SQL 的性能.因此最好使用单一视图
- 视图的限制
视图的限制有两个
- 定义视图时不能使用 ORDER BY 子句 为什么不能使用 ORDER BY 子句呢? 这是因为视图和表一样,数据行都是没有顺序的。 实际上,有些 DBMS 在定义视图的语句中是可以使用 ORDER BY 子句的 A ,但是这并不是通用的语法。因此,在定义视图时请不要使用 ORDER BY 子句。
- 对视图进行更新
如果定义视图的 SELECT 语句能够满足某些条件,那么这个视图就可以被更新。下面就给大家列举一些比较具有代表性的条件。
- SELECT 子句中未使用 DISTINCT
- FROM 子句中只有一张表
- 未使用 GROUP BY 子句
- 未使用 HAVING 子句 视图和表需要同时进行更新,因此通过汇总得到的视图无法进行更新。 对视图操作也就是对原表操作,只要你对视图操作的数据可以满足条件的放到原表中,就可以. 如果你视图成功增加了一条数据,原表也就增加了一条相关的数据了. 视图存的是sql语句,并不是一张真正的表,虽然它看起来或者用起来和真表一样
- 删除视图
DROP VIEW 视图名称(< 视图列名 1>, < 视图列名 2>, ...... )
eg:
DROP VIEW ProductSum;
子查询
一言以蔽之,子查询就是一次性视图(SELECT 语句)。与视图不同,子查询在 SELECT 语句执行完毕之后就会消失。
- 子查询和视图
子查询就是将用来定义视图的 SELECT 语句直接用于FROM 子句当中
SELECT product_type, cnt_product FROM ( SELECT Product_type, COUNT(*) AS cnt_product FROM Product GROUP BY product_type ) AS ProductSum; -- 就是将第一个查询出来的视图(表),作为一个新的表来给其查询 -- 首先执行 FROM 子句中的 SELECT 语句(子查询) -- 根据上面的结果执行外层的 SELECT 语句
子查询作为内层查询会首先执行。
- 标量子查询
标量就是单一的意思,在数据库之外的领域也经常使用。
而标量子查询则有一个特殊的限制,那就是必须而且只能返回 1 行 1列的结果,也就是返回表中某一行的某一列的值,例如“10”或者“东京都”这样的值。
标量子查询就是返回单一值的子查询。
由于返回的是单一的值,因此标量子查询的返回值可以用在 = 或者 <> 这样需要单一值的比较运算符之中。这也正是标量子查询的优势所在
-- 在WHERE 子句中不能使用聚合函数 SELECT product_id, product _ name, sale_price FROM Product WHERE sale_price > AVG(sale_price); -- 由于在WHERE 子句中不能使用聚合函数,因此这样的 SELECT 语句是错误的 -- 这样写 SELECT AVG(sale_price) FROM Product; -- 这样查询值返回单个数据,所以我们可以直接运用 -- 正确写法 SELECT product_id, product_name, sale_price FROM Product WHERE sale_price > (SELECT AVG(sale_price) FROM Product); -- 使用子查询的 SQL 会从子查询开始执行。因此,这种情况下也会先执行下述计算平均单价的子查询
标量子查询的书写位置 标量子查询的书写位置并不仅仅局限于 WHERE 子句中,通常任何可以使用单一值的位置都可以使用。 也就是说,能够使用常数或者列名的地 方,无论是 SELECT 子句、 GROUP BY 子句、HAVING 子句,还是ORDER BY 子句,几乎所有的地方都可以使用。
-- 在 SELECT 子句中使用标量子查询 SELECT product_id, product_name, sale_price, (SELECT AVG(sale_price) FROM Product) AS avg_price FROM Product;
-- 在 HAVING 子句中使用标量子查询 SELECT product_type, AVG(sale_price) FROM Product GROUP BY product_type HAVING AVG(sale_price) > (SELECT AVG(sale_price) FROM Product);
使用标量子查询时的注意事项 那就是该子查询绝对不能返回多行结果。也就是说,如果子查询返回了多行结果,那么它就不再是标量子查询,而仅仅是一个普通的子查询了,因此不能被用在 = 或者 <> 等需要单一输入值的运算符当中,也不能用在 SELECT 等子句当中。
关联子查询
把子查询作为一个表,关联子查询就是查询的时候,查询的表要和子表做关联(筛选条件)
- 普通的子查询和关联子查询的区别
问题:选取出各商品种类中高于该商品种类的平均销售单价的商品.
错误的执行
-- 按照商品种类计算平均价格 SELECT AVG(sale_price) FROM Product GROUP BY product_type; -- 但是,如果我们使用前一节(标量子查询)的方法,直接把上述SELECT 语句使用到 WHERE 子句当中的话,就会发生错误。 -- 发生错误的子查询 SELECT product_id, product_name, sale_price FROM Product WHERE sale_price > (SELECT AVG(sale_price) FROM Product GROUP BY product_type); -- 出错 原因前一节已经讲过了,该子查询会返回3行结果(2795、2500、300),并不是标量子查询。
使用关联子查询的解决方案
-- 适用于SQL Server DB2 PostgreSQL MySQL. Oracle中不能使用AS SELECT product_type, product_name, sale_price FROM Product AS P1 WHERE sale_price > (SELECT AVG(sale_price) FROM Product AS P2 WHERE P1.product_type = P2.product_type GROUP BY product_type); -- 注意,这里后一句话, sale_price > ,它的后面只能是一个数值. 所以最后()中的内容返回的是一个数值. -- 相当于是查询每一行, 如第一次的P1.product_type为衣服,其实后一句话,就变成了. 求商品的平均值,按照product_type分组,后选中该组中的内容为衣服的那一组的平均值,得到品均值,然后比较. -- 为了跟前面出错的查询进行对比,这里还是加上了 GROUP BY子句。
这次由于作为比较对象的都是同一张 Product 表,因此为了进行区别,分别使用了 P1 和 P2 两个别名。在使用关联子查询时,需要在表所对应的列名之前加上表的别名,以“< 表名 >.< 列名 > ”的形式记述。
-
关联子查询也是用来对集合进行切分的 换个角度来看,其实关联子查询也和 GROUP BY 子句一样,可以对集合进行切分。
- 结合条件一定要写在子查询中
-- 错误的关联子查询书写方法 SELECT product_type, product_name, sale_price FROM Product AS P1 WHERE P1.product_type = P2.product_type AND sale_price > (SELECT AVG(sale_price) FROM Product AS P2 GROUP BY product_type); /* 上述 SELECT 语句只是将子查询中的关联条件移到了外层查询之中,其他并没有任何更改。但是,该 SELECT 语句会发生错误,不能正确执行。 */
该书写方法究竟违反了什么规则呢?那就是关联名称的作用域. 具体来讲,子查询内部设定的关联名称,只能在该子查询内部使用。换句话说,就是“内部可以看到外部,而外部看不到内部”。 请大家一定不要忘记关联名称具有一定的有效范围。如前所述,SQL是按照先内层子查询后外层查询的顺序来执行的。这样,子查询执行结束时只会留下执行结果,作为抽出源的 P2 表其实已经不存在了 A 。因此,在执行外层查询时,由于 P2 表已经不存在了,因此就会返回“不存在使用该名称的表”这样的错误。
第六章 函数, 谓词, CASE表达式
函数的种类很多,无需全都记住,只需要记住具有代表性的函数就可以了,其他的可以在使用时再进行查询。
各种各样的函数
- 函数的种类
- 算术函数(用来进行数值计算的函数)
- 字符串函数(用来进行字符串操作的函数)
- 日期函数(用来进行日期操作的函数)
- 转换函数(用来转换数据类型和值的函数)
- 聚合函数(用来进行数据聚合的函数)
- 算数函数
- +(加法)
- -(减法)
- *(乘法)
- /(除法)
- ABS——绝对值
ABS(数值 ) -- ABS 是计算绝对值的函数。NULL还是NULL
eg:
SELECT m, ABS(m) AS abs_col FROM SampleMath;
- MOD——求余数
MOD( 被除数,除数 ) -- MOD 是计算除法余数(求余)的函数,是 modulo 的缩写。例如,7 / 3 的余数是 1,因此 MOD( 7, 3 )的结果也是 1 。 -- 因为小数计算中并没有余数的概念,所以只能对整数类型的列使用 MOD 函数。
eg:
-- 适用于Oracle DB2 PostgreSQL MySQL SELECT n, p, MOD(n, p) AS mod_col FROM SampleMath;
- ROUND——四舍五入
ROUND( 对象数值,保留小数的位数 ) -- ROUND 函数用来进行四舍五入操作。四舍五入在英语中称为 round。如果指定四舍五入的位数为 1 ,那么就会对小数点第 2 位进行四舍五入处理。如果指定位数为 2 ,那么就会对第 3 位进行四舍五入处理。
eg:
SELECT m, n, ROUND(m, n) AS round_col FROM SampleMath;
- 字符串函数
- ||——拼接
字符串 1 ||字符串 2 --在实际业务中,我们经常会碰到 abc + de = abcde 这样希望将字符串进行拼接的情况。在 SQL 中,可以通过由两条并列的竖线变换而成的" || "函数来实现 -- 当然也可以多个||相加
只是适用于: Oracle DB2 PostgreSQL SQL Server使用“+”运算符(函数)来连接字符串 MySQL使用 CONCAT 函数来完成字符串的拼接 eg:
SELECT str1, str2, str1 | | str2 AS str_concat FROM SampleStr;
- LENGTH——字符串长度
LENGTH( 字符串)
想要知道字符串中包含多少个字节时,可以使用LENGTH (长度)函数 eg:
SELECT str1, LENGTH(str1) AS len_str FROM SampleStr;
只有SQL Server 使用 LEN 函数来计算字符串的长度。
字节字符专栏 与半角英文字母占用 1 字节不同,汉字这样的全角字符会占用 2 个以上的字节(称为多字节字符)。 因此,使用 MySQL 中的LENGTH这样以字节为单位的函数进行计算时,“LENGTH( 山田 )”的返回结果是4。同样是 LENGTH 函数,不同 DBMS 的执行结果也不尽相同
字节(byte)是计算机中用来表述数据大小的基本单位。如本书所述,通常情况下“1 字符 =1字节”。 - LOWER——小写转换
LOWER( 字符串)
LOWER 函数只能针对英文字母使用,它会将参数中的字符串全都转换为小写。因此,该函数并不适用于英文字母以外的场合。此外,该函数并不影响原本就是小写的字符。 eg:
SELECT str1, LOWER(str1) AS low_str FROM SampleStr WHERE str1 IN ('ABC', 'aBC', 'abc', ' 山田 ');
同理 UPPER. UPPER就是大写转换函数
- REPLACE——字符串的替换
REPLACE( 对象字符串,替换前的字符串,替换后的字符串 ) -- 使用 REPLACE 函数,可以将字符串的一部分替换为其他的字符串
- SUBSTRING——字符串的截取
SUBSTRING (对象字符串 FROM 截取的起始位置 FOR 截取的字符数) -- SUBSTRING 函数(PostgreSQL/MySQL 专用语法)
eg:
SELECT str1, SUBSTRING(str1 FROM 3 FOR 2) AS sub_str FROM SampleStr;
- ||——拼接
- 日期函数
- CURRENT_DATE——当前日期
SELECT CURRENT_DATE
CURRENT_DATE 函数能够返回 SQL 执行的日期,也就是该函数执行时的日期。由于没有参数,因此无需使用括号 执行日期不同, CURRENT_DATE 函数的返回值也不同。如果在2009 年 12 月 13 日执行该函数,会得到返回值“2009-12-13 ”。如果在2010 年 1 月 1 日执行,就会得到返回值“2010-01-01 ” eg:
SELECT CURRENT_DATE; -- 获取当前日期
- CURRENT_TIME——当前时间
SELECT CURRENT_TIME
CURRENT_TIME 函数能够取得 SQL 执行的时间,也就是该函数执行时的时间(如: 19:05:33)
- CURRENT_TIMESTAMP——当前日期和时间
SELECT CURRENT_TIMESTAMP;
- EXTRACT——截取日期元素
EXTRACT(日期元素 FROM日期 )
使用 EXTRACT 函数可以截取出日期数据中的一部分,例如“年”“月”,或者“小时”“秒”等。该函数的返回值并不是日期类型而是数值类型。 eg:
-- 适用于PostgreSQL MySQL SELECT CURRENT_TIMESTAMP, EXTRACT(YEAR FROM CURRENT_TIMESTAMP) AS year, EXTRACT(MONTH FROM CURRENT_TIMESTAMP) AS month, EXTRACT(DAY FROM CURRENT_TIMESTAMP) AS day, EXTRACT(HOUR FROM CURRENT_TIMESTAMP) AS hour, EXTRACT(MINUTE FROM CURRENT_TIMESTAMP) AS minute, EXTRACT(SECOND FROM CURRENT_TIMESTAMP) AS second; -- 执行结果: now | year | month| day | hour | minute | second --------------------------+------+-------+-----+------+-------+------- 2010-04-25 19:07:33.987+09 | 2010 |4 | 25 | 19 | 7 | 33.987
- CURRENT_DATE——当前日期
- 转换函数
- CAST——类型转换
CAST (转换前的值 AS 想要转换的数据类型)
eg:
-- SQL Server PostgreSQL SELECT CAST('0001' AS INTEGER) AS int_col; -- MySQL SELECT CAST('0001' AS SIGNED INTEGER) AS int_col; -- Oracle SELECT CAST('0001' AS INTEGER) AS int_col FROM DUAL; --DB2 SELECT CAST('0001' AS INTEGER) AS int_col FROM SYSIBM.SYSDUMMY1; -- 执行结果 int_col --------- 1
- COALESCE——将 NULL 转换为其他值
COALESCE( 数据 1 ,数据 2,数据 3 ......)
COALESCE 是 SQL 特有的函数。该函数会返回可变参数 A 中左侧开始第 1 个不是 NULL 的值。参数个数是可变的,因此可以根据需要无限增加。
eg:-- SQL Server PostgreSQL MySQL SELECT COALESCE(NULL, 1) AS col_1, COALESCE(NULL, 'test', NULL) AS col_2, COALESCE(NULL, NULL, '2009-11-01') AS col_3; -- Oracle SELECT COALESCE(NULL, 1) AS col_1, COALESCE(NULL, 'test', NULL) AS col_2, COALESCE(NULL, NULL, '2009-11-01') AS col_3 FROM DUAL; -- DB2 SELECT COALESCE(NULL, 1) AS col_1, COALESCE(NULL, 'test', NULL) AS col_2, COALESCE(NULL, NULL, '2009-11-01') AS col_3 FROM SYSIBM.SYSDUMMY1; -- 执行结果 col_1 | col_2 | col_3 -------+-------+----------- 1 | test | 2009-11-01
就是说,类似于设置默认值,取出第一个数据后,如果为NULL就取下一位我们的默认值
- CAST——类型转换
谓词
-
什么是谓词 谓词就是返回值为真值的函数。 例如, = 、 < 、 >、 <> 等比较运算符,其正式的名称就是比较谓词 通俗来讲谓词就是中介绍的函数中的一种,是需要满足特定条件的函数,该条件就是返回值是真值。对通常的函数来说,返回值有可能是数字、字符串或者日期等,但是谓词的返回值全都是真值( TRUE/FALSE/UNKNOWN)。这也是谓词和函数的最大区别。
- LIKE 谓词——字符串的部分一致查询
我们使用字符串作为查询条件的例子中使用的都是 = 。这里的 = 只有在字符串完全一致时才为真。与之相反, LIKE 谓词更加模糊一些,当需要进行字符串的部分一致查询时需要使用该谓词。
其中的 % 是代表“0 字符以上的任意字符串”的特殊符号。
eg:
- 前方一致查询
SELECT * FROM SampleLike WHERE strcol LIKE '%ddd%'; -- 本例中代表“以 ddd 开头的所有字符串”
- 中间一致查询
SELECT * FROM SampleLike WHERE strcol LIKE '%ddd%'; -- 在字符串的起始和结束位 置加上 %,就能取出“包含 ddd 的字符串”了。
- 后方一致查询
SELECT * FROM SampleLike WHERE strcol LIKE '%ddd'; -- 以字符串“ ddd”结尾的记录
此外,我们还可以使用 _(下划线)来代替 % ,与 % 不同的是,它代表了“任意 1 个字符”。SELECT * FROM SampleLike WHERE strcol LIKE 'abc_ _'; -- 值为“ abc + 任意 2 个字符”的记录。
- 前方一致查询
- BETWEEN 谓词——范围查询
使用 BETWEEN 可以进行范围查询。该谓词与其他谓词或者函数的不同之处在于它使用了 3 个参数。
SELECT product_name, sale_price FROM Product WHERE sale_price BETWEEN 100 AND 1000;
BETWEEN 的特点就是结果中会包含 100 和 1000 这两个临界值。如果不想让结果中包含临界值,那就必须使用 < 和 >
SELECT product_name, sale_price FROM Product WHERE sale_price > 100 AND sale_price < 1000;
- IS NULL 、 IS NOT NULL ——判断是否为 NULL
为了选取出某些值为 NULL 的列的数据,不能使用 = ,而只能使用特定的谓词 IS NULL
与此相反,想要选取 NULL 以外的数据时,需要使用 IS NOT NULL
SELECT product_name, purchase_price FROM Product WHERE purchase_price IS NULL;
- IN 谓词——OR 的简便用法
-- 通过 OR 指定多个进货单价进行查询 SELECT product_name, purchase_price FROM Product WHERE purchase_price = 320 OR purchase_price = 500 OR purchase_price = 5000; -- 虽然上述方法没有问题,但还是存在一点不足之处,那就是随着希望选取的对象越来越多, SQL 语句也会越来越长,阅读起来也会越来越困难。
通过 IN 来指定多个进货单价进行查询
SELECT product_name, purchase_price FROM Product WHERE purchase_price IN (320, 500, 5000);
可以使用否定形式 NOT IN 来实现
SELECT product_name, purchase_price FROM Product WHERE purchase_price NOT IN (320, 500, 5000);
但需要注意的是,在使用 IN 和 NOT IN 时是无法选取出 NULL 数据的。实际结果也是如此,上述两组结果中都不包含进货单价为 NULL 的叉子和圆珠笔。 NULL 终究还是需要使用 IS NULL 和 IS NOT NULL 来进行判断。
- 使用子查询作为 IN 谓词的参数
我们还可以说“能够将视图作为 IN 的参数” 。
-- 取得“在大阪店销售的商品的销售单价” SELECT product_name, sale_price FROM Product WHERE product_id IN (SELECT product_id FROM ShopProduct WHERE shop_id = '000C'); --这里我们可以得到product_id的编号. 类似于下面举例的集合
子查询是从内层开始执行的。因此,该 SELECT 语句也是从内层的子查询开始执行,然后像下面这样展开
-- 子查询展开后的结果 SELECT product_name, sale_price FROM Product WHERE product_id IN ('0003', '0004', '0006', '0007');
CASE表达式
CASE 表达式的语法分为简单 CASE 表达式和搜索 CASE 表达式两种。但是,由于搜索 CASE 表达式包含了简单 CASE 表达式的全部功能,
-
什么是 CASE 表达式 CASE 表达式,和“ 1 + 1 ”或者“ 120 / 4 ”这样的表达式一样,是一种进行运算的功能。这就意味着 CASE 表达式也是函数的一种。
- CASE 表达式的语法
--搜索 CASE 表达式 CASE WHEN < 求值表达式 > THEN < 表达式 > WHEN < 求值表达式 > THEN < 表达式 > WHEN < 求值表达式 > THEN < 表达式 > . . . ELSE < 表达式 > END
CASE 表达式会从对最初的 WHEN 子句中的“< 求值表达式 >”进行求值开始执行。所谓求值,就是要调查该表达式的真值是什么。如果结果为真( TRUE ),那么就返回 THEN 子句中的表达式, CASE 表达式的执行到此为止。如果结果不为真,那么就跳转到下一条 WHEN 子句的求值之中。如果直到最后的 WHEN 子句为止返回结果都不为真,那么就会返回 ELSE中的表达式,执行终止。 从 CASE 表达式名称中的“表达式”我们也能看出来,上述这些整体构成了一个表达式。并且由于表达式最终会返回一个值,因此 CASE 表达式在 SQL 语句执行时,也会转化为一个值。虽然使用分支众多的 CASE表达式编写几十行代码的情况也并不少见,但是无论多么庞大的 CASE 表达式,最后也只会返回类似“ 1 ”或者“’ 你好 ‘ ”这样简单的值。
- CASE 表达式的使用方法
考虑一下怎样才能够得到如下结果? A :衣服 B :办公用品 C :厨房用具SELECT product_name, CASE WHEN product_type = ' 衣服' THEN 'A : ' | | product_type WHEN product_type = '办公用品 ' THEN 'B : ' | | product_type WHEN product_type = '厨房用具' THEN 'C : ' | | product_type ELSE NULL END AS abc_product_type FROM Product;
6 行 CASE 表达式代码最后只相当于 1 列( abc _ product _type ) 虽然 CASE 表达式中的 ELSE 子句可以省略,但还是希望大家不要省略。 CASE 表达式中的 END 不能省略。
- CASE 表达式的书写位置
CASE 表达式的便利之处就在于它是一个表达式。之所以这么说,是因为表达式可以书写在任意位置,也就是像“ 1 + 1”这样写在什么位置都可以的意思。
-- 对按照商品种类计算出的销售单价合计值进行行列转换 SELECT SUM(CASE WHEN product_type = '衣服' THEN sale_price ELSE 0 END) AS sum_price_clothes, SUM(CASE WHEN product_type = '厨房用具' THEN sale_price ELSE 0 END) AS sum_price_kitchen, SUM(CASE WHEN product_type = '办公用品 ' THEN sale_price ELSE 0 END) AS sum_price_office FROM Product;
在满足商品种类(product_ type )为“衣服”或者“办公用品”等特定值时,上述 CASE 表达式输出该商品的销售单价( sale _ price ),不满足时输出 0。 对该结果进行汇总处理,就能够得到特定商品种类的销售单价合计值了。
第七章 集合运算
集合运算就是对满足同一规则的记录进行的加减等四则运算。 通过集合运算,可以得到两张表中记录的集合或者公共记录的集合,又或者其中某张表中的记录的集合。像这样用来进行集合运算的运算符称为集合运算符。
表的加减法
- 表的加法——UNION(并集)
就是将两张表并集的形式展示出来
SELECT product_id, product_name FROM Product UNION SELECT product_id, product_name FROM Product2;
集合运算符会除去重复的记录。
- 集合运算的注意事项
- 注意事项1 ——作为运算对象的记录的列数必须相同
-- 列数不一致时会发生错误 SELECT product_id, product_name FROM Product UNION SELECT product_id, product_name, sale_price FROM Product2;
- 注意事项2 ——作为运算对象的记录中列的类型必须一致
从左侧开始,相同位置上的列必须是同一数据类型。例如下面的 SQL语句,虽然列数相同,但是第 2 列的数据类型并不一致(一个是数值类型,一个是日期类型),因此会发生错误。
-- 数据类型不一致时会发生错误 SELECT product_id, sale_price FROM Product UNION SELECT product_id, regist_date FROM Product2;
一定要使用不同数据类型的列时,可以使用 6-1 节中的类型转换函数CAST。
- 注意事项3 ——可以使用任何 SELECT 语句,但 ORDER BY 子句只能在最后使用一次
SELECT product_id, product_name FROM Product WHERE product_type = '厨房用具 ' UNION SELECT product_id, product_name FROM Product2 WHERE product_type = '厨房用具 ' ORDER BY product_id;
- 注意事项1 ——作为运算对象的记录的列数必须相同
- 包含重复行的集合运算——ALL 选项
在 UNION 的结果中保留重复行的语法。其实非常简单,只需要在 UNION 后面添加 ALL 关键字就可以了。这里的 ALL 选项,在 UNION 之外的集合运算符中同样可以使用
SELECT product_id, product_name FROM Product UNION ALL SELECT product_id, product_name FROM Product2;
在集合运算符中使用 ALL 选项,可以保留重复行。
- 选取表中公共部分——INTERSECT(交集)
mysql中不支持
SELECT product_id, product_name FROM Product INTERSECT SELECT product_id, product_name FROM Product2 ORDER BY product_id;
展示两个表中相同的数据
联结(以列为单位对表进行联结)
联结(JOIN)就是将其他表中的列添加过来,进行“添加列”的集合运算。UNION 是以行(纵向)为单位进行操作,而联结则是以列(横向)为单位进行的。
- 内联结——INNER JOIN
针对两张表,他们之间有一列相关联,就可以作为桥梁,然后将查询出来的列汇集到同一个结果之中
SELECT SP.shop_id, SP.shop_name, SP.product_id, P.product_name, P.sale_price FROM ShopProduct AS SP INNER JOIN Product AS P ON SP.product_id = P.product_id;
关于内联结,请大家注意以下三点。
- 内联结要点1 ——FROM 子句
之前的 FROM 子句中只有一张表,而这次我们同时使用了 ShopProduct 和 Product 两张表
FROM ShopProduct AS SP INNER JOIN Product AS P
使用关键字 INNER JOIN 就可以将两张表联结在一起了。 SP 和 P分别是这两张表的别名,但别名并不是必需的。 但由于表名太长会影响 SQL 语句的可读性,因此还是希望大家能够习惯使用别名。
- 内联结要点2 —— ON 子句
第二点要注意的是 ON 后面的联结条件。
ON SP.product_id = P.product_id
我们可以在 ON 之后指定两张表联结所使用的列(联结键),本例中使用的是商品编号( product_id)。也就是说, ON 是专门用来指定联结条件的,它能起到与 WHERE 相同的作用。 需要指定多个键时,同样可以使用 AND 、OR。在进行内联结时 ON 子句是必不可少的(如果没有 ON会发生错误),并且 ON 必须书写在 FROM 和 WHERE 之间。 进行内联结时必须使用 ON 子句,并且要书写在 FROM 和 WHERE 之间。
- 内联结要点3 ——SELECT 子句
第三点要注意的是,在 SELECT 子句中指定的列。
SELECT SP.shop_id, SP.shop_name, SP.product_id, P.product_name, P.sale_price
用“ < 表的别名 > .< 列名 > ”的形式来指定列。和使用一张表时不同,由于多表联结时,某个列到底属于哪张表比较容易混乱,因此采用了这样的防范措施。 使用联结时 SELECT 子句中的列需要按照“< 表的别名 >.< 列名 >”的格式进行书写。
- 内联结和 WHERE 子句结合使用
SELECT SP.shop_id, SP.shop_name, SP.product_id, P.product_name, P.sale_price FROM ShopProduct AS SP INNER JOIN Product AS P ON SP.product_id = P.product_id WHERE SP.shop_id = '000A';
像这样使用联结运算将满足相同规则的表联结起来时,WHERE 、GROUP BY 、HAVING 、 ORDER BY 等工具都可以正常使用。 我们可以将联结之后的结果想象为新创建出来的一张表,对这张表使用WHERE 子句等工具,这样理解起来就容易多了。
- 内联结要点1 ——FROM 子句
之前的 FROM 子句中只有一张表,而这次我们同时使用了 ShopProduct 和 Product 两张表
- 外联结——OUTER JOIN
外联结也是通过 ON 子句的联结键将两张表进行联结,并从两张表中同时选取相应的列的。基本的使用方法并没有什么不同,只是结果却有所不同。
SELECT SP.shop_id, SP.shop_name, SP.product_id, P.product_name, P.sale_price FROM ShopProduct AS SP RIGHT OUTER JOIN Product AS P ON SP.product_id = P.product_id;
关于外联结,请大家注意以下两点。
- 外联结要点1 ——选取出单张表中全部的信息 内联结只能选取出同时存在于两张表中的数据,因此只在 Product 表中存在的 2 种商品并没有出现在结果之中。 相反,对于外联结来说,只要数据存在于某一张表当中,就能够读取出来
- 外联结要点2 ——每张表都是主表吗?
外联结还有一点非常重要,那就是要把哪张表作为主表。最终的结果中会包含主表内所有的数据。
指定主表的关键字是 LEFT 和 RIGHT。顾名思义,使用 LEFT 时 FROM 子句中写在左侧的表是主表,使用 RIGHT时右侧的表是主表。
SELECT SP.shop_id, SP.shop_name, SP.product_id, P.product_name, P.sale_price FROM Product AS P LEFT OUTER JOIN ShopProduct AS SP ON SP.product_id = P.product_id;
外联结中使用 LEFT、RIGHT 来指定主表。使用二者所得到的结果完全相同。
- 3张以上的表的联结
SELECT SP.shop_id, SP.shop_name, SP.product_id, P.product_name, P.sale_price, IP.inventory_quantity FROM ShopProduct AS SP INNER JOIN Product AS P ON SP.product_id = P.product_id INNER JOIN InventoryProduct AS IP ON SP.product_id = IP.product_id WHERE IP.inventory_id = 'P001';
多张表也可以无限累加
- 交叉联结——CROSS JOIN
这种联结在实际业务中并不会使用,那为什么还要在这里进行介绍呢?这是因为交叉联结是所有联结运算的基础。
SELECT SP.shop_id, SP.shop_name, SP.product_id, P.product_name FROM ShopProduct AS SP CROSS JOIN Product AS P;
对满足相同规则的表进行交叉联结的集合运算符是 CROSS JOIN(笛卡儿积)。进行交叉联结时无法使用内联结和外联结中所使用的 ON 子句,这是因为交叉联结是对两张表中的全部记录进行交叉组合,因此结果中的记录数通常是两张表中行数的乘积。