读书笔记:SQL必知必会 【第13-15课】

第13课 创建高级联结

13.1 使用表别名

SQL除了可以对列名和计算字段使用别名,还允许给表名起别名,这样做有两个主要理由:

  • 缩短SQL语句;
  • 允许在一条SELECT语句中多次使用相同的表

下例是使用了别名的SELECT语句:

mysql> SELECT cust_name,cust_contact
    -> FROM Customers AS C, Orders AS O,OrderItems AS OI
    -> WHERE C.cust_id = O.cust_id AND OI.order_num = O.order_num AND prod_id = 'RGAN01';
+---------------+--------------------+
| cust_name     | cust_contact       |
+---------------+--------------------+
| Fun4All       | Denise L. Stephens |
| The Toy Store | Kim Howard         |
+---------------+--------------------+
2 rows in set (0.01 sec)

13.2 使用不同类型的联结

至今为止,我们使用的只是内联结或等值联结这些简单联结,接下来要看三种其他的联结:

13.2.1 自联结

如前所述,使用表别名的一个主要原因,是能在一条SELECT语句中不止一次引用相同的表。

下面举一个例子:

假如要给Jim Jones同一公司的所有顾客发送一封信件,这个查询要求首先找出Jim Jones工作的公司,然后找出在该公司工作的所有顾客,下面是解决此问题的一个方法:

mysql> 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 |
+------------+-----------+--------------------+
2 rows in set (0.00 sec)

这个是第一种解决方案,使用了子查询。

下面是使用联结的相同查询:

mysql> SELECT c1.cust_name,c1.cust_id,c1.cust_contact
    -> FROM Customers AS c1,Customers AS c2
    -> WHERE c1.cust_name = c2.cust_name AND c2.cust_contact = 'Jim Jones';
+-----------+------------+--------------------+
| cust_name | cust_id    | cust_contact       |
+-----------+------------+--------------------+
| Fun4All   | 1000000003 | Jim Jones          |
| Fun4All   | 1000000004 | Denise L. Stephens |
+-----------+------------+--------------------+
2 rows in set (0.00 sec)

也可使用Inner Join的形式:

mysql> SELECT c1.cust_name,c1.cust_id,c1.cust_contact
    -> FROM Customers AS c1 INNER JOIN Customers AS c2
    -> ON c1.cust_name = c2.cust_name AND c2.cust_contact = 'Jim Jones';
+-----------+------------+--------------------+
| cust_name | cust_id    | cust_contact       |
+-----------+------------+--------------------+
| Fun4All   | 1000000003 | Jim Jones          |
| Fun4All   | 1000000004 | Denise L. Stephens |
+-----------+------------+--------------------+
2 rows in set (0.00 sec)

这里的查询,实际上是将两张相同的表进行联结,并使其中一张表限定了Jim Jones的名字,在这种情况下,需要使用别名。而一旦在这种情况使用别名,则SELECT后所接的列名也必须写成完全形式,否则两个表的列名是相同的,会出错。

自联结通常作为外部语句,用来替代从相同的表中检索数据的使用子查询语句,虽然最终结果是相同的,但是很多DBMS处理联结是远比子查询快的。

13.2.2 自然联结

无论何时对表进行联结,应该至少有一列不止出现在一个表中,标准的联结返回的是所有数据,相同的列甚至多次出现。

而自然联结排除多次出现,使每一列只出现一次。怎样完成这件工作呢?答案是,系统不完成这样的工作,而要由我们自己来完成,自然联结要求我们选择那些唯一的列,一般通过对一个表使用通配符,而对其他的表的列使用明确的子集来完成。

下面举一个例子:

mysql> 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 prod_id = 'RGAN01';
+------------+---------------+---------------------+-----------+------------+----------+--------------+--------------------+-----------------------+-----------+---------------------+---------+----------+------------+
| cust_id    | cust_name     | cust_address        | cust_city | cust_state | cust_zip | cust_country | cust_contact       | cust_email            | order_num | order_date          | prod_id | quantity | item_price |
+------------+---------------+---------------------+-----------+------------+----------+--------------+--------------------+-----------------------+-----------+---------------------+---------+----------+------------+
| 1000000004 | Fun4All       | 829 Riverside Drive | Phoenix   | AZ         | 88888    | USA          | Denise L. Stephens | [email protected] |     20007 | 2012-01-30 00:00:00 | RGAN01  |       50 |       4.49 |
| 1000000005 | The Toy Store | 4545 53rd Street    | Chicago   | IL         | 54545    | USA          | Kim Howard         | NULL                  |     20008 | 2012-02-03 00:00:00 | RGAN01  |        5 |       4.99 |
+------------+---------------+---------------------+-----------+------------+----------+--------------+--------------------+-----------------------+-----------+---------------------+---------+----------+------------+
2 rows in set (0.00 sec)

13.2.3 外联结

许多联结将一个表中的行与另一个表中的行相关联,单有时候需要包含没有关联行的那些行,例如,可能需要联结完成一下工作:

  • 对每个顾客下的订单进行计数,包括哪些至今没下订单的顾客;
  • 列出所有产品以及订购数量,包括没有人订购的产品;
  • 计算平均销售规模,包括哪些至今尚未下订单的顾客

在上述例子中,联结包含了那些在相关表中没有关联行的行,这种联结被称为外联结。

首先,下面的语句给出一个简单地外联结,检索所有顾客与订单:

mysql> SELECT Customers.cust_id,Orders.order_num
    -> FROM Customers INNER JOIN Orders
    -> ON Customers.cust_id = Orders.cust_id;
+------------+-----------+
| cust_id    | order_num |
+------------+-----------+
| 1000000001 |     20005 |
| 1000000001 |     20009 |
| 1000000003 |     20006 |
| 1000000004 |     20007 |
| 1000000005 |     20008 |
+------------+-----------+
5 rows in set (0.00 sec)

外联结与之的语法是类似的,要检索包括没有订单的顾客在内的所有顾客,可如下进行:

mysql> SELECT Customers.cust_id,Orders.order_num
    -> FROM Customers LEFT 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 |
+------------+-----------+
6 rows in set (0.00 sec)

可以看到,这条SELECR语句使用关键字LEFT JOIN,其所指定的行,包括了左侧表(即Customers)的所有行,与右侧表进行联结。在右侧表不存在对应值的时候,值显示为NULL。

RIGHT JOIN类似,指定右侧表的所有行。此外,RIGHT JOIN等同于RIGHT OUTER JOIN,LEFT JOIN等同于LEFT OUTER JOIN。

还存在一种外联结,被称为全外联结,它检索两个表中所有的行,并对那些可以关联的行进行关联。与左外联结或右外联结那些包含一个表的不关联的行不同,全外联结包含两个表的不关联的行。

这种语法使用FULL JOIN,但是要注意的是MySQL不支持FULL JOIN,需要类似的功能的话,要使用UNION或UNION ALL:

mysql> SELECT Customers.cust_id,Orders.order_num
    -> FROM Customers LEFT JOIN Orders
    -> ON Customers.cust_id = Orders.cust_id
    -> UNION ALL
    -> SELECT Customers.cust_id,Orders.order_num
    -> FROM Customers RIGHT 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 |
| 1000000001 |     20005 |
| 1000000001 |     20009 |
| 1000000003 |     20006 |
| 1000000004 |     20007 |
| 1000000005 |     20008 |
+------------+-----------+
11 rows in set (0.00 sec)

13.3 使用带聚集函数的链接

如第9课所述,聚集函数用来汇总数据,虽然至今为止我们举的聚集函数的例子都只是从一个表中汇总数据,单这些函数也可以与联结一起使用。

下例要求检索所有顾客以及每个顾客所下的订单数,使用COUNT()函数完成此工作:

mysql> 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 |
+------------+---------+
4 rows in set (0.00 sec)

这条SELECT语句使用INNER JOIN将Customers和Orders互相关联,GROUP BY子句按顾客分组数据,因此,函数调用COUNT(Orders.order_num)对每个顾客的订单计数返回。

聚集函数也可以方便地与其他联结一同使用:

mysql> SELECT Customers.cust_id,COUNT(Orders.order_num) AS num_ord
-> FROM Customers LEFT 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 |
+————+———+
5 rows in set (0.00 sec)

这里需要注意的是,之所以使用COUNT(Orders.order_num)而不是COUNT(),就是因为考虑到这种情况:左联结的情况下,左边的表Customers的所有行都被选中,其中cust_id为1000000002的顾客并没有下订单,对应的Orders表的order_num为空,因此如果COUNT(Orders.order_num)的情况,该顾客的订单数会被归为0,如果使用COUNT()的话,那就是1了。

第14课 组合查询

14.1 组合查询

多数SQL查询,只包含从一个或多个表中返回数据的单条SELECT语句。但是,SQL也允许执行多个查询(多条SELECT语句),并将结果作为一个查询结果集返回。这些组合查询通常称为并(UNION)或复合查询(compound query)。

主要有两种情况,需要使用组合查询:

  • 在一个查询中,从不同的表返回结构数据;
  • 对一个表执行多个查询,按一个查询返回数据。

14.2 创建组合查询


可以用UNION操作符来组合数条SQL语句,利用UNION可给出多条SELECT语句,将它们的结果组成一个结果集。

14.2.1 使用UNION

使用UNION很简单,所要做的只是给出每条SELECT语句,在各条语句之间放上关键字UNION。
举个例子,假如需要Illinois,Indiana和Michigan等美国几个州所有顾客的报表,还想包括不管位于哪个州的所有Fun4All。WHERE是可以实现的,不过这里使用UNION。

mysql> SELECT cust_name,cust_contact,cust_email FROM Customers WHERE cust_state in ('MI','IL','IN')
    -> UNION
    -> SELECT cust_name,cust_contact,cust_email FROM Customers WHERE cust_name = 'FUN4ALL';
+---------------+--------------------+-----------------------+
| cust_name     | cust_contact       | cust_email            |
+---------------+--------------------+-----------------------+
| Village Toys  | John Smith         | [email protected] |
| Fun4All       | Jim Jones          | [email protected]    |
| The Toy Store | Kim Howard         | NULL                  |
| Fun4All       | Denise L. Stephens | [email protected] |
+---------------+--------------------+-----------------------+
4 rows in set (0.00 sec)

14.2.2 UNION规则

使用UNION进行组合时,要注意几条规则:

  • UNION必须由两条或以上的SELECT语句组成,语句之间用关键字UNION分割。
  • UNION中的每个查询必须包含相同的列,表达式或聚集函数。
  • 列数据类型必须兼容:类型不必完全相同,但是必须是DBMS可以隐含转换的类型。

14.2.3 包含或取消重复的行

UNION从查询结果集中自动去除了重复的行,如果想返回所有的匹配行,可以使用UNION ALL而非UNION。

具体代码不赘述。

14.2.4 对组合查询结果排序

SELECT语句的输出用ORDER BY子句排序,在用UNION组合查询时,只能使用一条ORDER BY子句,它必须位于最后一条SELECT语句之后,对于结果集,不存在用一种方式排序一部分,而用另一种方式排序另一部分的情况,因此不允许使用多条ORDER BY子句。

下面的例子对前面的UNION返回的结果排序:

mysql> SELECT cust_name,cust_contact,cust_email FROM Customers WHERE cust_state in ('MI','IL','IN')
    -> UNION
    -> SELECT cust_name,cust_contact,cust_email FROM Customers WHERE cust_name = 'FUN4ALL'
    -> ORDER BY cust_name,cust_contact;
+---------------+--------------------+-----------------------+
| cust_name     | cust_contact       | cust_email            |
+---------------+--------------------+-----------------------+
| Fun4All       | Denise L. Stephens | [email protected] |
| Fun4All       | Jim Jones          | [email protected]    |
| The Toy Store | Kim Howard         | NULL                  |
| Village Toys  | John Smith         | [email protected] |
+---------------+--------------------+-----------------------+
4 rows in set (0.00 sec)

第15课 插入数据

15.1 数据插入

顾名思义,INSERT语句用于将行插入到数据表,插入有几种方式:

  • 插入完整的行。
  • 插入行的一部分
  • 插入某些查询的结果。

下面逐一介绍这些内容。

15.1.1 插入完整的行

把数据插入表中,最简单的方法就是使用基本的INSERT语法,它要求指定表名和插入到新行中的值。
下面举一个例子:

mysql> INSERT INTO Customers
    -> VALUES(
    -> '1000000006',
    -> 'Toy Land',
    -> '123 Any Street',
    -> 'New York',
    -> 'NY',
    -> '11111',
    -> 'USA',
    -> NULL,
    -> NULL);
Query OK, 1 row affected (0.01 sec)

这样,就将一个新顾客插入到了Customers表:

mysql> SELECT * FROM CUstomers;
+------------+---------------+----------------------+-----------+------------+----------+--------------+--------------------+-----------------------+
| cust_id    | cust_name     | cust_address         | cust_city | cust_state | cust_zip | cust_country | cust_contact       | cust_email            |
+------------+---------------+----------------------+-----------+------------+----------+--------------+--------------------+-----------------------+
| 1000000001 | Village Toys  | 200 Maple Lane       | Detroit   | MI         | 44444    | USA          | John Smith         | [email protected] |
| 1000000002 | Kids Place    | 333 South Lake Drive | Columbus  | OH         | 43333    | USA          | Michelle Green     | NULL                  |
| 1000000003 | Fun4All       | 1 Sunny Place        | Muncie    | IN         | 42222    | USA          | Jim Jones          | [email protected]    |
| 1000000004 | Fun4All       | 829 Riverside Drive  | Phoenix   | AZ         | 88888    | USA          | Denise L. Stephens | [email protected] |
| 1000000005 | The Toy Store | 4545 53rd Street     | Chicago   | IL         | 54545    | USA          | Kim Howard         | NULL                  |
| 1000000006 | Toy Land      | 123 Any Street       | New York  | NY         | 11111    | USA          | NULL               | NULL                  |
+------------+---------------+----------------------+-----------+------------+----------+--------------+--------------------+-----------------------+
6 rows in set (0.00 sec)

这样的语法很简单,但并不安全,上面的SQL语句高度依赖于表中列的顶一次需,还依赖于其容易获得的次序信息。即使可以得到这种次序信息,也不能保证各列在下一次表结构变动后保持完全相同的次序。

更安全也繁琐的方法如下:

mysql> INSERT INTO Customers(
    -> cust_id,
    -> cust_name,
    -> cust_address,
    -> cust_city,
    -> cust_state,
    -> cust_zip,
    -> cust_country,
    -> cust_contact,
    -> cust_email)
    -> VALUES(
    -> '1000000006',
    -> 'Toy Land',
    -> '123 Any Street',
    -> 'New York',
    -> 'NY',
    -> '11111',
    -> 'USA',
    -> NULL,
    -> NULL);
ERROR 1062 (23000): Duplicate entry '1000000006' for key 'PRIMARY'

由于主键重复输入失败,不过形式是这样的。

后者可以使用与原表不同的次序插入。

15.1.2 插入部分行

使用INSERT的推荐方法是明确给出表的列名,使用这种语法也可以省略部分列。
这表示可以只给某些列提供值,而其他的列不提供值。
语句例子省略。

15.1.3 插入检索出的数据

INSERT还存在另一种形式,可以利用它将SELECT语句的结果插入表中,这就是所谓的INSERT SELECT,顾名思义,是由一条INSERT语句和一条SELECT语句组成的。这里,SELECT语句取代的是VALUES的位置。INSERT通常只插入一行,要插入多行,必须执行多个INSERT语句,不过INSERT SELECT是例外。

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

有一种数据插入不使用INSERT语句,要将一个表的内容复制到一个全新的表(运行中创建的表),可以使用SELECT INTO语句。与INSERT SELECT是将数据添加到一个已经存在的表里不同,SELECT INTO将数据复制到一个新表中。

INSERT SELECT和SELECT INTO的一个重要差别是,前者导出数据,后者导入数据。

下面的例子说明如何使用SELECT INTO:

mysql> SELECT *
    -> INTO CustCopy
    -> FROM Customers;
ERROR 1327 (42000): Undeclared variable: CustCopy

要注意的是,MySQL中不支持这样直接创建新表。
要手动建表,然后复制进去才行。

你可能感兴趣的:(SQL必知必会)