《SQL必知必会》学习笔记(一)

文章目录

      • DB基本知识及表的创建
        • 主键
        • 创建数据库及表
        • ALTER 操作列
        • 删除表
      • 检索数据
        • DISTINCT 关键字
        • 限制结果
        • 注释
      • 排序检索数据
        • 排序数据
        • 指定排序方向
      • 过滤数据
        • 使用 WHERE 子句
        • WHERE 子句操作符
        • AND 操作符
        • OR 操作符
        • IN 操作符
        • NOT 操作符
        • LIKE 操作符
          • 百分号(%) 通配符
          • 下划线(—)通配符
          • 通配符使用技巧
      • 创建计算字段
        • 拼接字段
        • 执行算数计算
      • 使用函数处理数据
        • 文本处理函数
        • 日期时间处理函数
        • 数值处理函数
      • 汇总数据
        • 聚集函数
          • AVG() 函数
          • COUNT()函数
          • MAX() 函数
          • SUM() 函数
        • 聚集不同值
        • 组合聚集函数
      • 分组数据
        • 创建分组
        • 过滤分组
        • 分组和排序
        • SELECT 子句顺序
      • 使用子查询
        • 利用子查询进行过滤
        • 作为计算字段使用子查询
      • 联结表
        • 创建联结
          • 内联结
        • 联结多个表
        • 其他类型联结
          • 自联结
          • 自然联结
          • 外联结
        • 使用带聚集函数的联结
      • 结合查询

DB基本知识及表的创建

主键

一列或者一组列,其值能够唯一标识表中的每一行。

主键满足以下条件:

  • 任意两行都不具有相同的主键值
  • 每一行都必须具有一个主键值(主键值不允许NULL)
  • 主键列的值不允许修改或者更新。
  • 主键值不能重用(如果某行从表中删除,它的主键不能赋给以后的新行)

本文所使用的的数据库为mysql

创建数据库及表

创建一个订单系统数据库

CREATE DATABASE IF NOT EXISTS order_sys default character set utf8 COLLATE utf8_general_ci;

创建商品表

CREATE TABLE Products
(
  prod_id    char(10)      NOT NULL ,
  vend_id    char(10)      NOT NULL ,
  prod_name  char(255)     NOT NULL ,
  prod_price decimal(8,2)  NOT NULL ,
  prod_desc  text          NULL 
);

创建顾客表

CREATE TABLE Customers
(
  cust_id      char(10)  NOT NULL ,
  cust_name    char(50)  NOT NULL ,
  cust_address char(50)  NULL ,
  cust_city    char(50)  NULL ,
  cust_state   char(5)   NULL ,
  cust_zip     char(10)  NULL ,
  cust_country char(50)  NULL ,
  cust_contact char(50)  NULL ,
  cust_email   char(255) NULL 
);

创建供应商表

CREATE TABLE Vendors
(
  vend_id      char(10) NOT NULL ,
  vend_name    char(50) NOT NULL ,
  vend_address char(50) NULL ,
  vend_city    char(50) NULL ,
  vend_state   char(5)  NULL ,
  vend_zip     char(10) NULL ,
  vend_country char(50) NULL 
);

创建订单表

CREATE TABLE Orders
(
  order_num  int      NOT NULL ,
  order_date datetime NOT NULL  DEFAULT CURRENT_DATE(),
  cust_id    char(10) NOT NULL 
);

创建每个订单实际商品表

CREATE TABLE OrderItems
(
  order_num  int          NOT NULL ,
  order_item int          NOT NULL ,
  prod_id    char(10)     NOT NULL ,
  quantity   int          NOT NULL  DEFAULT 1,
  item_price decimal(8,2) NOT NULL 
);

设置主键

ALTER TABLE Customers ADD PRIMARY KEY (cust_id);
ALTER TABLE OrderItems ADD PRIMARY KEY (order_num, order_item);
ALTER TABLE Orders ADD PRIMARY KEY (order_num);
ALTER TABLE Products ADD PRIMARY KEY (prod_id);
ALTER TABLE Vendors ADD PRIMARY KEY (vend_id);

设置外键

ALTER TABLE OrderItems ADD CONSTRAINT FK_OrderItems_Orders FOREIGN KEY (order_num) REFERENCES Orders (order_num);
ALTER TABLE OrderItems ADD CONSTRAINT FK_OrderItems_Products FOREIGN KEY (prod_id) REFERENCES Products (prod_id);
ALTER TABLE Orders ADD CONSTRAINT FK_Orders_Customers FOREIGN KEY (cust_id) REFERENCES Customers (cust_id);
ALTER TABLE Products ADD CONSTRAINT FK_Products_Vendors FOREIGN KEY (vend_id) REFERENCES Vendors (vend_id);

ALTER 操作列

添加新的列

ALTER TABLE Vendors
ADD vend_phone CHAR(20);

删除列

ALTER TABLE Vendors
DROP COLUMN vend_phone;

删除表

DROP TABLE Vendors;

检索数据

DISTINCT 关键字

返回过滤掉重复的数据

SELECT DISTINCT vend_id FROM Products;

限制结果

  • Oracle:ROWNUM

    SELECT prod_name FROM Products ROWNUM <=5;
    
  • Mysql、MariaDB、PostgreSQL、SQLite

    SELECT prod_name FROM Products LIMIT 5;
    SELECT prod_name FROM Products LIMIT 5 OFFSET 3;
    #LIMIT 5 OFFSET 3 表示查询从第3行起 的5条数据
    #LIMIT指定返回的行数,OFFSET 指定从哪里开始
    #Mysql MariaDB也可以写成如下:
    SELECT prod_name FROM Products LIMIT 3,5; #即 表示从第3行起,查询5条数据
    

注释

  • 行内注释

    SELECT prod_name -- 注释
    FROM Products;
    
  • 整行注释

    # 整行注释
    SELECT prod_name 
    FROM Products;
    
  • 多行注释

    /*SELECT prod_name 
    FROM Products; */
    SELECT prod_name 
    FROM Products;
    

排序检索数据

排序数据

  • 单列排序

     SELECT prod_name
     FROM Products
     ORDER BY prod_name;
    

    ORDER BY 需要保证它是SELECT 语句中最后一条字句

  • 多列排序

    SELECT prod_id, prod_price, prod_name
    FROM Products
    ORDER BY prod_price, prod_name;
    

    首先先按照prod_price 排序,仅有当多行具有相同的prod_price时,才根据prod_name排序。

  • 按列位置排序

    SELECT prod_id, prod_price, prod_name
    FROM Products
    ORDER BY 2, 3;
    

    ORDER BY 后指定的是SELECT清单中的相对位置,即2代表上面查询的字段prod_price,3代表prod_name,排序的先后顺序为先prod_price,仅有当多行具有相同的prod_price时,才根据prod_name排序。

指定排序方向

  • 默认是升序排序,关键字ASCASCENDING

  • 降序排序 使用关键字DESCDESCENDING

    SELECT prod_id, prod_price, prod_name
    FROM Products
    ORDER BY prod_price DESC, prod_name;
    

    首先按照 prod_price 进行降序排序,当有多行具有相同的 prod_price 时 对 prod_name 进行升序排序。即如果要对多列进行降序排序,那么需要在每个列名后都添加DESC关键字。

    DESC只应用到直接位于其前面的列

过滤数据

使用 WHERE 子句

SELECT prod_name, prod_price
FROM Products
WHERE prod_price = xxx;

表示从 Products 表中查询 prod_price 为xxx的数据。

同时使用ORDER BYWHERE 子句时,应该让 ORDER BY 位于 WHERE 之后。

WHERE 子句操作符

操作符 说明
= 等于
<> 不等于
!= 不等于
< 小于
<= 小于等于
!< 不小于(mysql等不支持)
> 大于
>= 大于等于
!> 不大于(mysql等不支持)
BETWEEN 在指定的两值之间(闭区间)
为NULL值

Tip: NULL 值为无值,它与字段包含0、空字符串或仅仅包含空格不同

AND 操作符

AND 操作符可以为子句添加条件

SELECT prod_id, prod_price, prod_name
FROM Products
WHERE vend_id = 'DLL01' AND prod_price <= 4;

这条语句是查询 vend_id 为 ‘DLL01’ 并且 prod_price 小于等于4的产品,AND 需要 DBMS 返回满足所有条件的子句的结果。

OR 操作符

OR 操作符指示 DBMS 检索匹配任一条件的行。

SELECT prod_name, prod_price
FROM Products
WHERE vend_id = 'DLL01' OR vend_id = 'BRS01';

这条语句是查询 vend_id 为 ‘DLL01’ 或者 'BRS01’的产品。

在SQL中AND 操作符的优先级要高于 OR。所以在使用AND和OR时 要尽量的加入圆括号,以避免产生歧义。

IN 操作符

IN 操作符用来指定条件范围

SELECT prod_name, prod_price
FROM Products
WHERE vend_id IN ('DLL01', 'BRS01')

此语句查询了 vend_id 为 DLL01 和 BRS01的所有商品。它在子句中的功能和 OR 相当。

优点:

  • IN 的语法更清楚,直观
  • 在于其他 AND 和 OR 操作符组合时会用 IN 时,求值顺序更容易管理
  • IN 操作符的执行速度一般比OR更快
  • IN 可以包含其他 SELECT 语句,能够更动态的建立 WHERE 子句

NOT 操作符

NOT 需要与其他操作符配合使用,用来否定其后所跟的条件。

SELECT prod_name
FROM Products
WHERE NOT vend_id = 'DLL01'

此语句查询了除了 vend_id 为’ DLL01 '的其他所有商品。

NOT 还可以与 IN 共同使用

SELECT prod_name, prod_price
FROM Products
WHERE NOT vend_id IN ('DLL01', 'BRS01');

在这里 NOT 操作符也可以加在 IN 之前。

LIKE 操作符

通配符:用来匹配值的一部分的特殊字符。

通配符搜索只能用于文本字段(字符串),非文本数据类型不能使用通配符。

在搜索子句中使用通配符必须使用 LIKE 操作符,意在指示 DBMS 后跟的搜索模式利用通配符而不是简单的相等匹配。

百分号(%) 通配符

在搜索串中 % 表示任何字符出现任意次数(包括0个)。

SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE 'Fish%';

此语句是找到所有以词 Fish 开头的产品。

通配符 % 无法匹配 NULL。

下划线(—)通配符

下划线表示只匹配单个字符

SELECT prod_id, prod_name 
FROM Products 
WHERE prod_name LIKE '_ish bean bag toy'

下划线 _ 总是刚好匹配一个字符,不能多也不能少。

通配符使用技巧
  • 不要过度使用通配符,因为搜索耗费的处理时间较长
  • 在确定需要使用通配符时,也尽量不要把他们用再搜索模式的开始出。在通配符的开始出,搜索起来是最慢的
  • 仔细注意通配符的位置

创建计算字段

拼接字段

Access和 SQL Serve 使用 + 号
DB2、Oracle、PostgreSQL、SQLite使用 || 
Mysql 和 MariaDB 使用 Concat() 函数

比如查询供应商表中的 vend_name 和 vend_country , 并将国家与名字进行拼接

SELECT Concat(vend_name,'(',vend_country,')')
FROM Vendors
ORDER BY vend_name;

其结果如下:

+----------------------------------------+
| Concat(vend_name,'(',vend_country,')') |
+----------------------------------------+
| Bear Emporium(USA)                     |
| Bears R Us(USA)                        |
| Doll House Inc.(USA)                   |
| Fun and Games(England)                 |
| Furball Inc.(USA)                      |
| Jouets et ours(France)                 |
+----------------------------------------+

通过结果可以看到 Concat 函数拼接了4个参数,一个是vend_name ,一个左括号,一个 vend_country ,一个右括号。

上面的结果头部字段名很长 没有一个字段表示,那么我们可以为其添加一个别名(AS)。

SELECT Concat(vend_name,'(', vend_country,')')
AS vend_title
FROM Vendors
ORDER BY vend_name;

结果如下:

+------------------------+
| vend_title             |
+------------------------+
| Bear Emporium(USA)     |
| Bears R Us(USA)        |
| Doll House Inc.(USA)   |
| Fun and Games(England) |
| Furball Inc.(USA)      |
| Jouets et ours(France) |
+------------------------+

执行算数计算

对检索出的数据进行算数运算。

比如检索订单中的数量和单价,那么可以通过数量和单价计算出商品的总价。

SELECT prod_id,
quantity, 
item_price, 
quantity * item_price AS totle_price 
FROM OrderItems WHERE order_num = 20008;

结果如下:

+---------+----------+------------+-------------+
| prod_id | quantity | item_price | totle_price |
+---------+----------+------------+-------------+
| RGAN01  |        5 |       4.99 |       24.95 |
| BR03    |        5 |      11.99 |       59.95 |
| BNBG01  |       10 |       3.49 |       34.90 |
| BNBG02  |       10 |       3.49 |       34.90 |
| BNBG03  |       10 |       3.49 |       34.90 |
+---------+----------+------------+-------------+

使用函数处理数据

文本处理函数

函数 说明
LEFT() 返回字符串左边的字符
RIGHT() 返回字符串右边的字符
LENGTH() 返回字符串长度
LOWER() 将字符串转换成小写
UPPER() 将字符串转换成大写
LTRIM() 去掉字符串左边的空格
RTRIM() 去掉字符串右边的空格

日期时间处理函数

SELECT order_num
FROM Orders
WHERE EXTRACT(YEAR FROM order_date) = 2012;

此语句查询 order_date 年份为2012年的订单号。

EXTRACT() 可以提取出年、月、日、小时等。

比如将 Oders 表中的日期按照年月日检索出来

SELECT EXTRACT(YEAR FROM order_date) AS order_year,
EXTRACT(MONTH FROM order_date) AS order_month,
EXTRACT(DAY FROM order_date) AS order_day
FROM Orders;

结果如下:

+------------+-------------+-----------+
| order_year | order_month | order_day |
+------------+-------------+-----------+
|       2012 |           5 |         1 |
|       2012 |           1 |        12 |
|       2012 |           1 |        30 |
|       2012 |           2 |         3 |
|       2012 |           2 |         8 |
+------------+-------------+-----------+

除了 EXTRACT 函数 mysql 还可以使用YEAR()MONTH()DAY() 提取年月日。

DATE_ADD() 可以向日期添加指定的时间间隔

SELECT DATE_ADD(order_date, INTERVAL 10 DAY)
AS inv_date 
FROM Orders 
WHERE order_date='2012-05-01 00:00:00';

返回如下:

+---------------------+
| inv_date            |
+---------------------+
| 2012-05-11 00:00:00 |
+---------------------+

DATE_FORMAT() 用来格式化日期

SELECT DATE_FORMAT(order_date,'%m-%d-%Y %H:%i:%s') AS format_date FROM Orders;

返回如下:

+---------------------+
| format_date         |
+---------------------+
| 05-01-2012 00:00:00 |
| 01-12-2012 00:00:00 |
| 01-30-2012 00:00:00 |
| 02-03-2012 00:00:00 |
| 02-08-2012 00:00:00 |
+---------------------+

具体参数详见

数值处理函数

函数 说明
ABS() 绝对值
COS() 返回一个角度的余弦
SIN() 返回一个角度的正弦
TAN() 返回一个角度的正切
EXP() 返回一个数的指数值
PI() 返回圆周率
SQRT() 返回一个数的平方根

汇总数据

聚集函数

函数 说明
AVG() 返回某列的平均值
COUNT() 返回某列的行数
MAX() 返回某列的最大值
MIN() 返回某列的最小值
SUM() 返回某列值的和
AVG() 函数

比如需要返回 Products 中的所有商品中的平均价钱

SELECT AVG(prod_price) AS avg_price
FROM Products;

返回:

+-----------+
| avg_price |
+-----------+
|  6.823333 |
+-----------+

AVG函数忽略值为NULL的列

COUNT()函数
  • COUNT( * ) 对表中的行的数目进行计数,不管表列中包含的值是否为NULL
  • COUNT( column ) 对特定的列中具有值的行进行计数,忽略 NULL

下面先让我们看一下 Customers 表中 email 的数据,其中有2个 NULL 值

SELECT cust_email FROM Customers;

+-----------------------+
| cust_email            |
+-----------------------+
| sales@villagetoys.com |
| NULL                  |
| jjones@fun4all.com    |
| dstephens@fun4all.com |
| NULL                  |
+-----------------------+

如果我们使用 COUNT( * )

SELECT COUNT(*)
FROM Customers;
+----------+
| COUNT(*) |
+----------+
|        5 |
+----------+

改为使用 COUNT( column )

SELECT COUNT(cust_email)
FROM Customers;
+-------------------+
| COUNT(cust_email) |
+-------------------+
|                 3 |
+-------------------+
MAX() 函数

MAX() 函数一般用于找出最大的数值或者日期

对于数值函数使用,返回指定列的最大的数

SELECT MAX(prod_price) AS max_price
FROM Products;

结果

+-----------+
| max_price |
+-----------+
|     11.99 |
+-----------+

对于非数值函数使用,用于文本数据时,MAX() 返回按该列排序后的最后一行。

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

SUM() 函数

SUM() 用来返回指定列值的和。

SELECT SUM(quantity) AS items_ordered
FROM OrderItems;

此语句返回订单中所有物品数量的总和。

也可用于执行算数操作符

SELECT 
SUM(quantity* item_price) 
AS totle_price
FROM OrderItems;

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

聚集不同值

对于聚集函数都可以使用 ALL 或 DISTICT

  • DISTINCT:只包含不同的值
  • ALL:包含所有的值 默认为ALL

比如使用 AVG() 返回所有产品的平均价格,使用 DISTINCT 参数,平均值只计算各个不同的价格

SELECT 
AVG( DISTINCT prod_price) 
AS avg_price
FROM Products;

返回结果:

+-----------+
| avg_price |
+-----------+
|  7.490000 |
+-----------+

DISTINCT 不能用于COUNT( * ),如果指定列名,则 DISTINCT 只能用于COUNT()。

组合聚集函数

SELECT 语句可以根据需要包含多个聚集函数。

SELECT COUNT(*) AS num_items,
MIN(prod_price) AS price_min,
MAX(prod_price) AS price_max,
AVG(prod_price) AS price_avg
FROM Products;

返回结果:

+-----------+-----------+-----------+-----------+
| num_items | price_min | price_max | price_avg |
+-----------+-----------+-----------+-----------+
|         9 |      3.49 |     11.99 |  6.823333 |
+-----------+-----------+-----------+-----------+

聚合函数用来汇总数据,使用聚合函数会很高效。

分组数据

创建分组

GROUP BY 根据一个或者多个列对结果集进行分组。

比如我们想要查询某个产品中每个供应商的个数

SELECT vend_id, COUNT(*) AS num_prods
FROM Products
GROUP BY vend_id;

返回结果:

+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
| BRS01   |         3 |
| DLL01   |         4 |
| FNG01   |         2 |
+---------+-----------+

此语句中 COUNT( * ) 为计算字段,GROUP BY 表示根据 vend_id 排序并分组,所以COUNT( * ) 计算的是每个分组的个数。

GROUP BY 可以指定多个列:

SELECT vend_id, COUNT(*) AS num_prods , prod_price
FROM Products
GROUP BY vend_id, prod_price;
+---------+-----------+------------+
| vend_id | num_prods | prod_price |
+---------+-----------+------------+
| BRS01   |         1 |       5.99 |
| BRS01   |         1 |       8.99 |
| BRS01   |         1 |      11.99 |
| DLL01   |         3 |       3.49 |
| DLL01   |         1 |       4.99 |
| FNG01   |         2 |       9.49 |
+---------+-----------+------------+

此sql语句表示每个供应商的每个商品价钱的分组。

通过结果我们发现 GROUP BY X, Y 应用多列时,就是将具有相同 X 和 Y 放到一个分组中。

使用技巧:

  • GROUP BY 子句可以包含任意数目的列

  • GROUP BY 子句中嵌套了分组时,数据将在最后指定的分组上进行汇总(如上面例子所示,COUNT( * ) 是针对 prod_price 进行的汇总)。

  • GROUP BY 中的列名必须是索引列或是有效的表达式,即如果在SELECT 中使用表达式,那么必须在 GROUP BY 子句中使用相同的表达式,不可以使用别名。

  • 除了聚合计算语句外,SELECT 语句中的每一列都必须在 GROUP BY 子句中给出。

  • 如果分组列中包含具有 NULL 值的行,那么 NULL 将作为一个分组返回。

  • GROUP BY 子句必须出现在 WHERE 子句后,ORDER BY 子句前。

  • 可以通过相对位置指定列,例如 GROUP BY 2, 1表示先按照第二个列分组,然后再按照第一个列分组。

过滤分组

HAVING 用于过滤分组

WHERE 用于过滤行

SELECT cust_id,COUNT(*) AS orders
FROM Orders
GROUP BY cust_id
HAVING COUNT(*) >= 2;

返回结果

+------------+--------+
| cust_id    | orders |
+------------+--------+
| 1000000001 |      2 |
+------------+--------+

此sql语句返回了两个以上订单的分组。

那么可不可以将 WHERE 和 HAVING 一起使用?

例如我们想列出具有两个以上产品并且其价格大于 4 的供应商

SELECT vend_id, COUNT(*) AS num_prods
FROM Products
WHERE prod_price >= 4
GROUP BY vend_id
HAVING COUNT(*) >= 2;

返回结果:

+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
| BRS01   |         3 |
| FNG01   |         2 |
+---------+-----------+

可见,WHERE 是数据分组前进行的过滤,HAVING 在数据分组后进行过滤。

如果不指定 GROUP BY 那么大多数的 DBMS 会将 WHERE 和 HAVING 同等对待。

分组和排序

GROUP BY ORDER BY
对行分组,但输出可能不是分组的顺序 对产生的输出排序
只可能使用选择列或表达式列,而且必须使用每个选择列表达式 任意列都可以使用,甚至非选择列也可以
如果与聚集函数一起使用列,则必须使用 不一定需要

检索包含三个或更多物品的订单号和订购物品的数目

SELECT order_num, COUNT(*) AS items
FROM OrderItems
GROUP BY order_num
HAVING COUNT(*) >= 3;
+-----------+-------+
| order_num | items |
+-----------+-------+
|     20006 |     3 |
|     20007 |     5 |
|     20008 |     5 |
|     20009 |     3 |
+-----------+-------+

如果我们希望订购的物品数按照从大到小输出怎么办

SELECT order_num, COUNT(*) AS items 
FROM OrderItems 
GROUP BY order_num 
HAVING COUNT(*) >=3 
ORDER BY items DESC, order_num DESC;

返回结果

+-----------+-------+
| order_num | items |
+-----------+-------+
|     20008 |     5 |
|     20007 |     5 |
|     20009 |     3 |
|     20006 |     3 |
+-----------+-------+

SELECT 子句顺序

子句 说明 是否必须使用
SELECT 要返回的列或表达式
FROM 从中检索数据的表 仅在从表选择数据时使用
WHERE 行级过滤
GROUP BY 分组 仅在按组计算聚合时使用
HAVING 组级过滤
ORDER BY 输出排序

使用子查询

利用子查询进行过滤

子查询:嵌套在其他查询中的查询。

本文中 Orders 表中存储的是订单id、日期、顾客id

OrderItems 表中存储的是订单id、商品id、订单价钱、订单数量等信息

Customers 中存储顾客信息

如果想要列出订购物品 RGAN01 的所有顾客,我们需要进行以下步骤:

  1. 检索包含物品 RGAN01 的所有订单的编号
  2. 检索出具有前一步骤列出的订单编号的所有顾客id
  3. 根据顾客id 检索出顾客信息

在 SELECT 语句中,子查询总是从内向外处理。

SELECT cust_name, cust_contact 
FROM Customers 
WHERE cust_id IN( 
  SELECT cust_id  
  FROM Orders  
  WHERE order_num IN (
    SELECT order_num      
    FROM OrderItems    
    WHERE prod_id = 'RGAN01'));

返回结果

+---------------+--------------------+
| cust_name     | cust_contact       |
+---------------+--------------------+
| Fun4All       | Denise L. Stephens |
| The Toy Store | Kim Howard         |
+---------------+--------------------+

作为子查询的 SELECT 语句只能查询单个列。

作为计算字段使用子查询

如果需要显示 Customers 表中,每个顾客的订单数,订单与相对应的顾客ID 存储在Orders表中。

需要进行以下步骤:

  1. 从Customers检索出顾客列表
  2. 根据顾客id 统计其在Orders表中的订单数
SELECT 
cust_name,
cust_state, 
  (SELECT COUNT(*)
   FROM Orders 
   WHERE Orders.cust_id = Customers.cust_id)
   AS orders 
FROM Customers 
ORDER BY cust_name;

返回结果

+---------------+------------+--------+
| cust_name     | cust_state | orders |
+---------------+------------+--------+
| Fun4All       | IN         |      1 |
| Fun4All       | AZ         |      1 |
| Kids Place    | OH         |      0 |
| The Toy Store | IL         |      1 |
| Village Toys  | MI         |      2 |
+---------------+------------+--------+

此 sql 语句返回了每个顾客的 cust_name、cust_state、orders 信息。其中 orders 是一个计算字段,通过子查询所建立,该子查询对检索出的顾客执行一次,在此例中,该子查询执行了5次,因为检索出了5个顾客。

子查询常用在 WHERE 子句的IN操作符中,以及用来填充计算列。

联结表

关系表:关系表的设计就是要把信息分解成多个表,一类数据一个表。各表通过某些共同的值互相关联。

在我们的例子总 Vendors 是供应商表,每个供应商信息是一行。

Products 表只存储产品信息,一列存储供应商ID。

创建联结

SELECT vend_name, prod_name, prod_price
FROM Vendors, Products
WHERE Vendors.vend_id = Products.vend_id;

返回结果

+-----------------+---------------------+------------+
| vend_name       | prod_name           | prod_price |
+-----------------+---------------------+------------+
| Bears R Us      | 8 inch teddy bear   |       5.99 |
| Bears R Us      | 12 inch teddy bear  |       8.99 |
| Bears R Us      | 18 inch teddy bear  |      11.99 |
| Doll House Inc. | Fish bean bag toy   |       3.49 |
| Doll House Inc. | Bird bean bag toy   |       3.49 |
| Doll House Inc. | Rabbit bean bag toy |       3.49 |
| Doll House Inc. | Raggedy Ann         |       4.99 |
| Fun and Games   | King doll           |       9.49 |
| Fun and Games   | Queen doll          |       9.49 |
+-----------------+---------------------+------------+

此 sql 语句使用 WHERE 子句将两个表正确的联结,这里是联结条件。

在引用的列可能出现歧义时,必须使用完全限定列名(即表名.列名)。

在联结2个表时实际要做的就是将第一表中的每一行与第二个表中的每一行配对,如果没有 WHERE 子句,那么两个表将做笛卡尔积。

内联结

以上所演示的我们称之为等值联结,它基于两个表之间的相等测试。这种联结也称为内联结

下面我们使用另一种方式实现内联结

SELECT vend_name, prod_name, prod_price
FROM Vendors 
INNER JOIN Products
ON Vendors.vend_id = Products.vend_id;

返回结果

+-----------------+---------------------+------------+
| vend_name       | prod_name           | prod_price |
+-----------------+---------------------+------------+
| Bears R Us      | 8 inch teddy bear   |       5.99 |
| Bears R Us      | 12 inch teddy bear  |       8.99 |
| Bears R Us      | 18 inch teddy bear  |      11.99 |
| Doll House Inc. | Fish bean bag toy   |       3.49 |
| Doll House Inc. | Bird bean bag toy   |       3.49 |
| Doll House Inc. | Rabbit bean bag toy |       3.49 |
| Doll House Inc. | Raggedy Ann         |       4.99 |
| Fun and Games   | King doll           |       9.49 |
| Fun and Games   | Queen doll          |       9.49 |
+-----------------+---------------------+------------+

联结多个表

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;

返回结果

+---------------------+-----------------+------------+----------+
| prod_name           | vend_name       | prod_price | quantity |
+---------------------+-----------------+------------+----------+
| 18 inch teddy bear  | Bears R Us      |      11.99 |       50 |
| Fish bean bag toy   | Doll House Inc. |       3.49 |      100 |
| Bird bean bag toy   | Doll House Inc. |       3.49 |      100 |
| Rabbit bean bag toy | Doll House Inc. |       3.49 |      100 |
| Raggedy Ann         | Doll House Inc. |       4.99 |       50 |
+---------------------+-----------------+------------+----------+

此 sql 语句显示订单20007中的物品。

  1. 订单物品存储在 OrderItems 中,其中每个商品的ID 也存储在其中,
  2. 通过商品ID可以在 Products 表中查询商品信息以及供应商ID,
  3. 再通过供应商ID在 Vendors 表中查询供应商信息。

如果我们实现上面子查询中的“如果想要列出订购物品 RGAN01 的所有顾客”这个例子,可以写成这样

SELECT cust_name, cust_contact
FROM OrderItems, Orders, Customers
WHERE Orders.cust_id= Customers.cust_id
AND OrderItems.order_num = Orders.order_num
AND OrderItems.prod_id='RGAN01';

返回结果

+---------------+--------------------+
| cust_name     | cust_contact       |
+---------------+--------------------+
| Fun4All       | Denise L. Stephens |
| The Toy Store | Kim Howard         |
+---------------+--------------------+

接下来再看下 INNER JOIN ON 实现方式

SELECT 
cust_name, 
cust_contact 
FROM (Customers 
      INNER JOIN Orders 
      ON Customers.cust_id = Orders.cust_id)
      INNER JOIN OrderItems 
      ON OrderItems.order_num = Orders.order_num
      WHERE OrderItems.prod_id = 'RGAN01';

返回结果

+---------------+--------------------+
| cust_name     | cust_contact       |
+---------------+--------------------+
| Fun4All       | Denise L. Stephens |
| The Toy Store | Kim Howard         |
+---------------+--------------------+

其他类型联结

自联结

假如要给与 Jim Jones 同公司的所有顾客发送一封邮件。这个要求包括下面两步:

  1. 找到 Jim Jones 工作公司
  2. 找出在该公司的顾客
SELECT cust_id, cust_name, cust_contact
FROM Customers
WHERE cust_name =
	(SELECT cust_name 
	FROM Customers 
	WHERE cust_contact='Jim Jones');
+------------+-----------+--------------------+
| cust_id    | cust_name | cust_contact       |
+------------+-----------+--------------------+
| 1000000003 | Fun4All   | Jim Jones          |
| 1000000004 | Fun4All   | Denise L. Stephens |
+------------+-----------+--------------------+

使用自联结

SELECT C1.cust_id, C1.cust_name, C1.cust_contact
FROM Customers AS C1 
INNER JOIN Customers AS C2 
ON C1.cust_name = C2.cust_name 
WHERE C2.cust_contact = 'Jim Jones';
+------------+-----------+--------------------+
| cust_id    | cust_name | cust_contact       |
+------------+-----------+--------------------+
| 1000000003 | Fun4All   | Jim Jones          |
| 1000000004 | Fun4All   | Denise L. Stephens |
+------------+-----------+--------------------+

通过以上 sql 语句发现 ,如果引用2个相同的表必须指定别名,如果没有别名会报错,因为 DBMS 无法区分你要查找的字段。

自然联结

自然联结要求你只能选择那些唯一的列,一般通过对一个表使用通配符(SELECT * ),而对其他表的列使用明确的子集来完成。

SELECT C.*,
O.order_num, 
O.order_date, 
OI.prod_id, 
OI.quantity,
OI.item_price 
FROM Customers AS C, 
Orders AS O, 
OrderItems AS OI 
WHERE C.cust_id = O.cust_id 
AND OI.order_num = O.order_num 
AND OI.prod_id = 'RGAN01';
外联结

联结包含相关表中没有关联行的行,这种联结称为外联结。

SELECT Customers.cust_id, Orders.order_num
FROM Customers 
LEFT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id;
+------------+-----------+
| cust_id    | order_num |
+------------+-----------+
| 1000000001 |     20005 |
| 1000000001 |     20009 |
| 1000000002 |      NULL |
| 1000000003 |     20006 |
| 1000000004 |     20007 |
| 1000000005 |     20008 |
+------------+-----------+

使用外联结时 必须指定 RIGHT 或 LEFT 关键字指定其所有行的表( RIGHT 指出的是 OUTER JOIN 右边的表,LEFT 指出的是 OUTER JOIN 右边的表)。

上面例子中 LEFT OUTER JOIN 从 FROM 子句左边的表( Customers )中选择所有行。

左外联结:左表中所有不符合条件的数据行,并在其中的右表列填写NULL,即 上面的例子中 cust_id = 1000000002 是不符合条件的数据行,那么添加到 Orders 表中是 NULL 。

那么来看一个右外联结

SELECT Customers.cust_id, Orders.order_num 
FROM Customers 
RIGHT OUTER JOIN Orders 
ON Orders.cust_id = Customers.cust_id;
+------------+-----------+
| cust_id    | order_num |
+------------+-----------+
| 1000000001 |     20005 |
| 1000000001 |     20009 |
| 1000000003 |     20006 |
| 1000000004 |     20007 |
| 1000000005 |     20008 |
+------------+-----------+

发现其中缺少了 cust_id 为 1000000002 的行,因为右外联结是从 Orders 表中选择所有行,将其添加到左表中,因为 Orders 表中没有 cust_id 为 1000000002 的行,也就不存在和左表不匹配的问题了。

右外联结:右表中所有不符合条件的数据行,并在其中的左表列填写NULL

使用带聚集函数的联结

要检索所有顾客及每个顾客所下的订单数

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;
+------------+---------+
| cust_id    | num_ord |
+------------+---------+
| 1000000001 |       2 |
| 1000000003 |       1 |
| 1000000004 |       1 |
| 1000000005 |       1 |
+------------+---------+

同样的使用外联结的方式

SELECT Customers.cust_id,
COUNT(Orders.order_num) AS num_ord
FROM Customers LEFT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id
GROUP BY Customers.cust_id;
+------------+---------+
| cust_id    | num_ord |
+------------+---------+
| 1000000001 |       2 |
| 1000000002 |       0 |
| 1000000003 |       1 |
| 1000000004 |       1 |
| 1000000005 |       1 |
+------------+---------+

这个例子使用了左外联结来包含所有顾客,甚至包含了那些没有任何订单的顾客,因此 1000000002 也出现在了结果中。

结合查询

利用 UNION 操作符将多个 SELECT 语句组合成一个结果集。

使用场景:

  • 在一个查询中从不同的表返回结构数据
  • 对一个表执行多个查询,按一个查询返回条件
SELECT cust_name, cust_contact,cust_email 
FROM Customers 
WHERE cust_state IN ('IL','IN','MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers 
WHERE cust_name='Fun4All';

UNION 使用规则:

  • UNION 必须由两条或两条以上 SELECT 语句组成,语句之间使用 UNION 关键词。
  • UNION 中每个查询必须包含相同的列、表达式或者聚集函数。
  • 列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐式转换的。
  • 如果不希望 UNION 对 SELECT 结果进行去重,可以使用 UNION ALL,即进行并集操作。
  • 在用 UNION 组合查询时 只能使用一条 ORDER BY 子句,它必须位于最后一条 SELECT 语句之后。

你可能感兴趣的:(《SQL必知必会》学习笔记(一))