读书笔记之-SQL基础笔记

SQL基础笔记

Posted by 张振 on June 1, 2019

整理SQL笔记, SQL是最不会过时的一门语言

第一章 数据库和SQL

  1. RDBMS(关系型数据库管理系统)的结构图.
  2. SQL语句的种类. 分为三类
    1. DDL DDL(Data Definition Language,数据定义语言) 用来创建或者删除存储数据用的数据库以及数据库中的表等对象。DDL 包含以下几种指令。
      • CREATE : 创建数据库和表等对象
      • DROP : 删除数据库和表等对象
      • ALTER : 修改数据库和表等对象的结构
    2. DML DML(Data Manipulation Language,数据操纵语言) 用来查询或者变更 表中的记录。DML 包含以下几种指令。
      • SELECT :查询表中的数据
      • INSERT :向表中插入新数据
      • UPDATE :更新表中的数据
      • DELETE :删除表中的数据
    3. DCL DCL(Data Control Language,数据控制语言) 用来确认或者取消对数据库中的数据进行的变更。除此之外,还可以对 RDBMS 的用户是否有权限操作数据库中的对象(数据库表等)进行设定。DCL 包含以下几种指令。
      • COMMIT : 确认对数据库中的数据进行的变更
      • ROLLBACK : 取消对数据库中的数据进行的变更
      • GRANT : 赋予用户操作权限
      • REVOKE : 取消用户的操作权限

    实际使用的 SQL 语句当中有 90% 属于 DML

  3. 数据库创建 在创建表之前,一定要先创建用来存储表的数据库。运行CREATE DATABASE 语句就可以在 RDBMS 上创建数据库了
     CREATE DATABASE < 数据库名称 > ;
    
  4. 表的创建
     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 设置默认编码不能忘呀

  5. 数据类型的指定 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 型 用来指定存储日期(年月日)的列的数据类型(日期型)。
  6. 字段约束的设置 约束是除了数据类型之外,对列中存储的数据进行限制或者追加条件的功能。

    例子:

     product_id CHAR(4) NOT NULL,
     product_name VARCHAR(100) NOT NULL,
     product_type VARCHAR(32) NOT NULL,
    

    数据类型的右侧设置了 NOT NULL 约束。NULL 是代表空白(无记录)的关键字。在 NULL 之前加上了表示否定的 NOT,就是给该列设置了不能输入空白,也就是必须输入数据的约束(如果什么都不输入就会出错)。

  7. 主键约束设置 上面的例子中还有这样一句话:
     PRIMARY KEY (product_id)
    

    这是用来给 product_id 列设置主键约束的。 所谓键,就是在指定特定数据时使用的列的组合。键种类多样,主键(primary key)就是可以特定一行数据的列。也就是说,特定一行数据,也可以说是唯一确定一行数据。如果把 product_ id 列指定为主键,就可以通过该列取出特定的商品数据了。
    反之,如果向 product_id 列中输入了重复数据,就无法取出唯一的特定数据了(因为无法确定唯一的一行数据)。这样就可以为某一列设置主键约束了。

  8. 表的删除
     DROP TABLE < 表名 > ;
    
  9. 表定义的增加列 有时好不容易把表创建出来之后才发现少了几列,其实这时无需把表删除再重新创建,只需使用变更表定义的 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
    
  10. 表定义的删除列
    ALTER TABLE < 表名> DROP COLUMN < 列名> 
    /*Oracle 中不用写 COLUMN。
    ALTER TABLE < 表名 > DROP <列名> ;
    另外,在 Oracle 中同时删除多列的时候,可以像下面这样使用括号来实现。
    ALTER TABLE < 表名 > DROP ( <列名> , < 列名 > ,......);*/
    
  11. 表中插入数据
    -- 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;(请予以删除)。
    */
    
  12. 修改表名字 如果把表名字写错,那么怎么修改? 如果还没有向表中插入数据,那么只需要把表删除,再重新创建一个名称正确的表就可以了。 其实很多数据库都提供了可以修改表名的指令(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语句基础

  1. 查询 SELECT
     SELECT < 列名 >,......
     FROM < 表名 >;
    

    该 SELECT 语句包含了 SELECT 和 FROM 两个子句(clause)。子句是 SQL 语 句的组 成要素,是以 SELECT 或者 FROM 等作为起始的短语。
    SELECT 子句中列举了希望从表中查询出的列的名称,而 FROM 子句则指定了选取出数据的表的名称。
    举例:

     SELECT product_id, product_name, purchase_price FROM Product;
     -- SELECT * FROM Product; --查询所有的列:
    
  2. 为列设定别名 AS
     SELECT product_id AS id, product_name AS name, FROM Product;
    

    执行结果 id | name | price ——+———+——- 0001 | T恤衫 | 500 0002 | 打孔器 | 320 0003 | 运动T恤 | 2800
    别名可以使用中文,使用中文时需要用双引号(“)括起来。请注意不是单引号(‘)。

  3. 从结果中删除重复的行 可以通过在 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

  4. 根据where语句来选择记录
     SELECT < 列名 >, ......
     FROM < 表名 >
     WHERE < 条件表达式 >;
    

    举例:

     SELECT product_name, product_type
     FROM Product
     WHERE product_type = ' 衣服 ';
     -- " product _type = ' 衣服 ' "就是用来表示查询条件的表达式(条件表达式)。等号是比较两边的内容是否相等的符号,
    

    SQL 中子句的书写顺序是固定的,不能随意更改。WHERE 子句必须紧跟在 FROM 子句之后,书写顺序发生改变的话会造成执行错误

  5. SQL的注释 有两种方法:
    1. 行注释 书写在”–“之后,只能写在同一行。
    2. 多行注释 书写在”/“和”/”之间,可以跨多行。

    可以穿插在语句之中:

     SELECT DISTINCT product_id, purchase_price
     -- 本SELECT语句会从结果中删除重复行。
     FROM Product;
    

算术运算符

  1. 算数运算符
     四中算数运算符
     加法运算   +
     减法运算   -
     乘法运算   *
     除法运算   /
    
     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
     -- 。运算就是这样以行为单位执行的。
    

    四则运算所使用的运算符 ( + 、 - 、 * 、 / )称为算术运算符。运算符就是使用其两边的值进行四则运算或者字符串拼接、数值大小比较等运算,并返回结果的符号。加法运算符( + ) 前后如果是数字或者数字类型的列名的话,就会返回加法运算后的结果。

  2. 算数运算符中的NULL 运算时,5 + NULL? 1 * NULL? NULL / 0? 是什么吗呢? 正确答案全部都是 NULL 所有包含 NULL 的计算,结果肯定是 NULL

  3. 比较运算符
     运算符     含义
     =        ~ 相等
     <>       ~ 不相等, 也可以用 !=
     >=      大于等于 ~
     >       大于 ~
     <=      小于等于 ~
     <       小于 ~ 
    

    这些比较运算符可以对字符、数字和日期等几乎所有数据类型的列和值进行比较。 举例:

     -- 选取出销售单价大于等于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;
    
  4. 比较运算符中的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;
    

逻辑运算符

  1. 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 运算符的书写方法和工作原理,同时提醒大家不要滥用该运算符。

  2. 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 运算符时可以使用括号。

第三章 聚合和排序

聚合函数

  1. 聚合函数 用于汇总的函数称为聚合函数或者聚集函数
    • COUNT :计算表中的记录数(行数)
    • SUM : 计算表中数值列中数据的合计值
    • AVG : 计算表中数值列中数据的平均值
    • MAX : 求出表中任意列中数据的最大值
    • MIN : 求出表中任意列中数据的最小值
  2. 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 之外的数据行数。

  3. 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 都会被无视。

  4. 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

  5. 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 函数只适用于数值类型的列。

  6. 使用聚合函数删除重复值(关键字 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 子句,可以根据“商品种类”或者“登记日期”等将表分割后再进行汇总。

  1. 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 就像是切分表的一把刀。

  2. 聚合键中包含 NULL 的情况 聚合键中包含 NULL 时,在结果中会以“不确定”行(空行)的形式表现出来。

  3. 使用 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 难以理解的原因之一。

  4. 与聚合函数和 GROUP BY 子句有关的常见错误
    1. 常见错误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. 常见错误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. 常见错误3 —— GROUP BY 子句的结果能排序吗 GROUP BY 子句的结果通常都包含多行,有时可能还会是成百上千行。那么,这些结果究竟是按照什么顺序排列的呢? 答案是:“随机的。”
      我们完全不知道结果记录是按照什么规则进行排序的。可能乍一看是按照行数的降序或者聚合键的升序进行排列的,但其实这些全都是偶然的。当你再次执行同样的 SELECT 语句时,得到的结果可能会按照完全不同的顺序进行排列。

    4. 常见错误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. 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)
    
  2. 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 当然无法为表中不存在的列设定条件了。 */ ```

  3. 相对于 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 子句 = 指定组所对应的条件

对查询结果进行排序

  1. 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子句中书写的列名称为排序键。*/
    
  2. 指定升序或降序 由高到低: 在列名后面使用 DESC 关键字。(descendent(下降的)) 由低到高: 升序进行排列时,正式的书写方式应该是使用关键字 ASC (ascendent(上升的)) 未指定 ORDER BY 子句中排列顺序时会默认使用升序进行排列。 由于 ASC 和 DESC 这两个关键字是以列为单位指定的,因此可以同时指定一个列为升序,指定其他列为降序。

  3. 指定多个排序键 针对某个列的值相同的情况下,采用另一个列的值来排序(如果只指定一个排序的话,那么遇到相同的值的情况下针对该相同的列就是随机排序了) 举例:
     -- 按照销售单价和商品编号的升序进行排序
     SELECT product_id, product_name, sale_price, purchase_price FROM Product ORDER BY sale_price, product_id;
     -- 价格相同时按照商品编号的升序排序
    

    这样一来,就可以在 ORDER BY 子句中同时指定多个排序键了。规则是优先使用左侧的键,如果该列存在相同值的话,再接着参考右侧的键。当然,也可以同时使用 3 个以上的排序键。

  4. 排序中包含NULL的情况 因为NULL是不能排序比较大小的,究竟是在开头显示还是在末尾显示,并没有特殊规定。某些 DBMS中可以指定 NULL 在开头或末尾显示 排序键中包含 NULL 时,会在开头或末尾进行汇总。

  5. 在排序键中使用显示用的别名 在 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 子句中定义的别名。

  6. 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(*);
      

第四章 数据更新

数据的插入(INSERT语句的使用方法)

将列名和值用逗号隔开,分别括在()内,这种形式称为清单 对表中所有列进行 INSERT 操作时可以省略表名后的列清单。

  1. 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');
    
  2. 列清单的省略 对表进行全列 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’);

  3. 插入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 语句插入的数据无法正常插入到表中,但之前已经插入的数据并不会被破坏。

  4. 插入默认值 如果在创建表的同时设定了默认值,就可以在 INSERT 语句中自动为列赋值了。默认值的使用方法通常有显式和隐式两种
    1. 通过显式方法插入默认值 在 VALUES 子句中指定 DEFAULT 关键字
       INSERT INTO ProductIns (product_id, product_name, product_type, sale_price, purchase_price, regist_date) 
       VALUES ('0007', ' 擦菜板 ', ' 厨房用具 ', DEFAULT, 790, '2009-04-28');
      
    2. 通过隐式方法插入默认值 插入默认值时也可以不使用 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 语句的含义也更加容易理解。

  5. 从其他表中复制数据 要插入数据,除了使用 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 语句会留下表(容器),而删除表中的全部数据
  1. DELETE 语句的基本语法 保留数据表,仅删除全部数据行的 DELETE 语句
     DELETE FROM < 表名 >;
    

    补充: 如果语句中忘了写 FROM ,而是写成了“DELETE < 表名 >”,或者写了多余的列名,都会出错,无法正常执行 前者无法正常执行的原因是删除对象不是表,而是表中的数据行(记录)。这样想的话就很容易理解了吧 后者错误的原因也是如此。因为 DELETE 语句的对象是行而不是列,所以 DELETE 语句无法只删除部分列的数据。因此,在 DELETE 语句中指定列名是错误的。 DELETE 语句的删除对象并不是表或者列,而是记录(行)。

  2. 指定删除对象的 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 语句可以更改(更新)表中的数据。

  1. UPDATE 语句的基本语法
     UPDATE < 表名 > 
     SET < 列名 > = < 表达式 >;
    

    eg:

     UPDATE Product SET regist_date = '2009-10-10';
     -- 将这一列的数据全部变为'2009-10-10'
    
  2. 指定条件的 UPDATE 语句(搜索型 UPDATE)
     UPDATE < 表名 >
     SET < 列名> = <表达式>
     WHERE < 条件 >;
    

    eg:

     UPDATE Product SET sale_price = sale_price * 10 WHERE product_type = '厨房用具 ';
     -- 将product_type = '厨房用具 '条件下的 sale_price提升为原来的十倍
    
  3. 使用 NULL 进行更新
     UPDATE Product SET regist_date = NULL WHERE product_id = '0008';
     --将该行的regist_date设置为NULL
    

    使用 UPDATE 语句可以将值清空为 NULL (但只限于未设置 NOTNULL 约束的列)。

  4. 多列更新
     -- 使用逗号对列进行分隔排列
     UPDATE Product
     SET sale_price = sale_price * 10, purchase_price = purchase_price / 2
     WHERE product_type = '厨房用具 ';
     -- 可以同事更新两个或者多个字段
    

事务

事务处理的终止指令包括 COMMIT (提交处理)和 ROLLBACK (取消处理)两种。 DBMS 的事务具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)四种特性。通常将这四种特性的首字母结合起来,统称为 ACID 特性。 事务是需要在同一个处理单元中执行的一系列更新处理的集合。

  1. 创建事务(提交,回滚)
     事务开始语句;
     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;
    
  2. 扩展:事务何时开始 几乎所有的数据库产品的事务都无需开始指令.像这样不使用指令而悄悄开始事务的情况下,应该如何区分各个事务呢?通常会有如下两种情况。
    • 每条 SQL 语句就是一个事务(自动提交模式)
    • 直到用户执行 COMMIT 或者 ROLLBACK 为止算作一个事务

    默认使用自动提交模式的DBMS 有 SQL Server、PostgreSQL 和 MySQL 等 在默认使用 B 模式的 Oracle 中,事务都是直到用户自己执行提交或者回滚指令才会结束。 可以这样理解,默认你写一条数据提交一条,但是如果你使用了开始事务这个语句,在接下来的所有语句都不会自动提交,除非你使用了COMMIT或者ROLLBACK
    自动提交的情况需要特别注意的是 DELETE 语句。如果不是自动提交,即使使用 DELETE 语句删除了数据表,也可以通过 ROLLBACK 命令取消该事务的处理,恢复表中的数据。但这仅限于明示开始事务,或者关闭自动提交的情况。如果不小心在自动提交模式下执行了 DELETE 操作,即使再回滚也无济于事了。这是一个很严重的问题,初学者难免会碰到这样的麻烦。一旦误删了数据,如果无法重新插入,是不是想哭的心都有了?所以一定要特别小心。

第五章 复杂查询

视图

  1. 视图 视图究竟是什么呢?如果用一句话概述的话,就是“从 SQL 的角度来看视图就是一张表” 使用视图时并不会将数据保存到存储设备之中,而且也不会将数据保存到其他任何地方。实际上视图保存的是 SELECT 语句 表中存储的是实际数据,而视图中保存的是从表中取出数据所使用的 SELECT 语句。 优点:
    • 第一点是由于视图无需保存数据,因此可以节省存储设备的容量。
    • 第二个优点就是可以将频繁使用的 SELECT 语句保存成视图,这样就不用每次都重新书写了。

    应该将经常使用的 SELECT 语句做成视图。

  2. 创建视图
     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 的性能.因此最好使用单一视图

  3. 视图的限制 视图的限制有两个
    1. 定义视图时不能使用 ORDER BY 子句 为什么不能使用 ORDER BY 子句呢? 这是因为视图和表一样,数据行都是没有顺序的。 实际上,有些 DBMS 在定义视图的语句中是可以使用 ORDER BY 子句的 A ,但是这并不是通用的语法。因此,在定义视图时请不要使用 ORDER BY 子句。
    2. 对视图进行更新 如果定义视图的 SELECT 语句能够满足某些条件,那么这个视图就可以被更新。下面就给大家列举一些比较具有代表性的条件。
      1. SELECT 子句中未使用 DISTINCT
      2. FROM 子句中只有一张表
      3. 未使用 GROUP BY 子句
      4. 未使用 HAVING 子句 视图和表需要同时进行更新,因此通过汇总得到的视图无法进行更新。 对视图操作也就是对原表操作,只要你对视图操作的数据可以满足条件的放到原表中,就可以. 如果你视图成功增加了一条数据,原表也就增加了一条相关的数据了. 视图存的是sql语句,并不是一张真正的表,虽然它看起来或者用起来和真表一样
  4. 删除视图
     DROP VIEW 视图名称(< 视图列名 1>, < 视图列名 2>, ...... )
    

    eg:

     DROP VIEW ProductSum;
    

子查询

一言以蔽之,子查询就是一次性视图(SELECT 语句)。与视图不同,子查询在 SELECT 语句执行完毕之后就会消失。

  1. 子查询和视图 子查询就是将用来定义视图的 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 语句
    

    子查询作为内层查询会首先执行。

  2. 标量子查询 标量就是单一的意思,在数据库之外的领域也经常使用。 而标量子查询则有一个特殊的限制,那就是必须而且只能返回 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 等子句当中。

关联子查询

把子查询作为一个表,关联子查询就是查询的时候,查询的表要和子表做关联(筛选条件)

  1. 普通的子查询和关联子查询的区别 问题:选取出各商品种类中高于该商品种类的平均销售单价的商品. 错误的执行
     -- 按照商品种类计算平均价格
     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 两个别名。在使用关联子查询时,需要在表所对应的列名之前加上表的别名,以“< 表名 >.< 列名 > ”的形式记述。

  2. 关联子查询也是用来对集合进行切分的 换个角度来看,其实关联子查询也和 GROUP BY 子句一样,可以对集合进行切分。

  3. 结合条件一定要写在子查询中
     -- 错误的关联子查询书写方法
     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表达式

函数的种类很多,无需全都记住,只需要记住具有代表性的函数就可以了,其他的可以在使用时再进行查询。

各种各样的函数

  1. 函数的种类
    • 算术函数(用来进行数值计算的函数)
    • 字符串函数(用来进行字符串操作的函数)
    • 日期函数(用来进行日期操作的函数)
    • 转换函数(用来转换数据类型和值的函数)
    • 聚合函数(用来进行数据聚合的函数)
  2. 算数函数
    • +(加法)
    • -(减法)
    • *(乘法)
    • /(除法)
    1. ABS——绝对值
       ABS(数值 )
       -- ABS 是计算绝对值的函数。NULL还是NULL
      

      eg:

       SELECT m, ABS(m) AS abs_col FROM SampleMath;
      
    2. 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;
      
    3. ROUND——四舍五入
       ROUND( 对象数值,保留小数的位数 )
       -- ROUND 函数用来进行四舍五入操作。四舍五入在英语中称为 round。如果指定四舍五入的位数为 1 ,那么就会对小数点第 2 位进行四舍五入处理。如果指定位数为 2 ,那么就会对第 3 位进行四舍五入处理。
      

      eg:

       SELECT m, n, ROUND(m, n) AS round_col FROM SampleMath;
      
  3. 字符串函数
    1. ||——拼接
       字符串 1 ||字符串 2
       --在实际业务中,我们经常会碰到 abc + de = abcde 这样希望将字符串进行拼接的情况。在 SQL 中,可以通过由两条并列的竖线变换而成的" || "函数来实现
       -- 当然也可以多个||相加
      

      只是适用于: Oracle DB2 PostgreSQL SQL Server使用“+”运算符(函数)来连接字符串 MySQL使用 CONCAT 函数来完成字符串的拼接 eg:

       SELECT str1, str2, str1 | | str2 AS str_concat FROM SampleStr;
      
    2. 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字节”。

    3. LOWER——小写转换
      LOWER( 字符串)
      

      LOWER 函数只能针对英文字母使用,它会将参数中的字符串全都转换为小写。因此,该函数并不适用于英文字母以外的场合。此外,该函数并不影响原本就是小写的字符。 eg:

      SELECT str1, LOWER(str1) AS low_str FROM SampleStr WHERE str1 IN ('ABC', 'aBC', 'abc', ' 山田 ');
      

      同理 UPPER. UPPER就是大写转换函数

    4. REPLACE——字符串的替换
       REPLACE( 对象字符串,替换前的字符串,替换后的字符串 )
       -- 使用 REPLACE 函数,可以将字符串的一部分替换为其他的字符串
      
    5. SUBSTRING——字符串的截取
       SUBSTRING (对象字符串 FROM 截取的起始位置 FOR 截取的字符数)
       --  SUBSTRING 函数(PostgreSQL/MySQL 专用语法)
      

      eg:

       SELECT str1, SUBSTRING(str1 FROM 3 FOR 2) AS sub_str FROM SampleStr;
      
  4. 日期函数
    1. 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;
      -- 获取当前日期
      
    2. CURRENT_TIME——当前时间
       SELECT CURRENT_TIME
      

      CURRENT_TIME 函数能够取得 SQL 执行的时间,也就是该函数执行时的时间(如: 19:05:33)

    3. CURRENT_TIMESTAMP——当前日期和时间
       SELECT CURRENT_TIMESTAMP;
      
    4. 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
      
  5. 转换函数
    1. 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
      
    2. 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就取下一位我们的默认值

谓词

  1. 什么是谓词 谓词就是返回值为真值的函数。 例如, = 、 < 、 >、 <> 等比较运算符,其正式的名称就是比较谓词 通俗来讲谓词就是中介绍的函数中的一种,是需要满足特定条件的函数,该条件就是返回值是真值。对通常的函数来说,返回值有可能是数字、字符串或者日期等,但是谓词的返回值全都是真值( TRUE/FALSE/UNKNOWN)。这也是谓词和函数的最大区别。

  2. 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 个字符”的记录。
      
  3. 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;
    
  4. 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;
    
  5. 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 来进行判断。

  6. 使用子查询作为 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 表达式的全部功能,

  1. 什么是 CASE 表达式 CASE 表达式,和“ 1 + 1 ”或者“ 120 / 4 ”这样的表达式一样,是一种进行运算的功能。这就意味着 CASE 表达式也是函数的一种。

  2. 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 ”或者“’ 你好 ‘ ”这样简单的值。

  3. 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 不能省略。

  4. 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。 对该结果进行汇总处理,就能够得到特定商品种类的销售单价合计值了。

第七章 集合运算

集合运算就是对满足同一规则的记录进行的加减等四则运算。 通过集合运算,可以得到两张表中记录的集合或者公共记录的集合,又或者其中某张表中的记录的集合。像这样用来进行集合运算的运算符称为集合运算符。

表的加减法

  1. 表的加法——UNION(并集) 就是将两张表并集的形式展示出来
     SELECT product_id, product_name FROM Product
     UNION
     SELECT product_id, product_name FROM Product2;
    

    集合运算符会除去重复的记录。

  2. 集合运算的注意事项
    1. 注意事项1 ——作为运算对象的记录的列数必须相同
       -- 列数不一致时会发生错误
       SELECT product_id, product_name FROM Product
       UNION
       SELECT product_id, product_name, sale_price FROM Product2;
      
    2. 注意事项2 ——作为运算对象的记录中列的类型必须一致 从左侧开始,相同位置上的列必须是同一数据类型。例如下面的 SQL语句,虽然列数相同,但是第 2 列的数据类型并不一致(一个是数值类型,一个是日期类型),因此会发生错误。
       -- 数据类型不一致时会发生错误
       SELECT product_id, sale_price FROM Product
       UNION
       SELECT product_id, regist_date FROM Product2;
      

      一定要使用不同数据类型的列时,可以使用 6-1 节中的类型转换函数CAST。

    3. 注意事项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;
      
  3. 包含重复行的集合运算——ALL 选项 在 UNION 的结果中保留重复行的语法。其实非常简单,只需要在 UNION 后面添加 ALL 关键字就可以了。这里的 ALL 选项,在 UNION 之外的集合运算符中同样可以使用
     SELECT product_id, product_name FROM Product
     UNION ALL
     SELECT product_id, product_name FROM Product2;
    

    在集合运算符中使用 ALL 选项,可以保留重复行。

  4. 选取表中公共部分——INTERSECT(交集) mysql中不支持
     SELECT product_id, product_name FROM Product
     INTERSECT
     SELECT product_id, product_name FROM Product2 ORDER BY product_id;
    

    展示两个表中相同的数据

联结(以列为单位对表进行联结)

联结(JOIN)就是将其他表中的列添加过来,进行“添加列”的集合运算。UNION 是以行(纵向)为单位进行操作,而联结则是以列(横向)为单位进行的。

  1. 内联结——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 子句等工具,这样理解起来就容易多了。

  2. 外联结——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. 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';
    

    多张表也可以无限累加

  4. 交叉联结——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 子句,这是因为交叉联结是对两张表中的全部记录进行交叉组合,因此结果中的记录数通常是两张表中行数的乘积。