《SQL 必知必会》读书笔记

第1课 了解 SQL

这章主要介绍了数据库,表,字段类型,行,列,主键和SQL等基本概念。

  • 数据库:以某种形式存储的数据集合,在计算机上的表现形式可能是一个文件或者一组文件。我们平时所说的数据库,往往指MySQL或者Oracle这些数据库管理系统。数据库管理系统用来创建操作数据库。
  • 数据表:一个二维数据结构。
  • 列和数据类型
  • 数据行
  • 主键:能够唯一标识一条记录的一列(或几列)数据,主键具有下面的特点:
    • 任意两行都不具有相同的主键值;
    • 每一行都必须具有一个主键值(主键列不允许NULL值);
    • 主键列中的值不允许修改或更新;
    • 主键值不能重用(如果某行从表中删除,它的主键不能赋给以后的新行)。

主键通常定义在表的一列上,但并不是必需这么做,也可以一起使用多个列作为主键。在使用多列作为主键时,上述条件必须应用到所有列,所有列值的组合必须是唯一的。

  • SQL语言:结构化查询语言,是一种专门用来和数据库交互的语言。标准SQL由ANSI标准委员会管理,从而称为ANSI SQL。主流的数据库管理系统,比如MySQL,Oracle等都支持标准的SQL。但是数据库厂商都会对ANSL SQL进行扩展,如PL/SQL、Transact-SQL等。

第2课 检索数据(select)

关于SQL语句的一些说明:

有些数据库管理系统需要在语句末尾加上;有的不需要加,当然,如果愿意可以总是加上分号。事实上,即使不一定需要,加上分号也肯定没有坏处。

SQL语句和大小写:SQL语句不区分大小写,因此SELECT与select是相同的。同样,写成Select也没有关系。许多SQL开发人员喜欢对SQL关键字使用大写,而对列名和表名使用小写,这样做使代码更易于阅读和调试。不过,一定要认识到虽然SQL是不区分大小写的,但是表名、列名和值可能有所不同(这有赖于具体的DBMS及其如何实现)。

在处理SQL语句时,其中所有空格都被忽略。SQL语句可以写成长长的一行,也可以分写在多行。

选择多个数据列

SELECT prod_id, prod_name, prod_price
FROM Products;

查询所有列

SELECT * FROM Products;

一般而言,除非你确实需要表中的每一列,否则最好别使用*通配符。虽然使用通配符能让你自己省事,不用明确列出所需列,但检索不需要的列通常会降低检索和应用程序的性能。

检索不同的值(distinct)

SELECT DISTINCT vend_id FROM Products;

DISTINCT关键字作用于所有的列,不仅仅是跟在其后的那一列。例如,你指定 SELECT DISTINCT vend_id, prod_price,因为指定的两列不完全相同,所以所有的行都会被检索出来。

限制查询返回结果数

在SQL Server和Access中使用SELECT时,可以使用TOP关键字来限制最多返回多少行。

SELECT TOP 5 prod_name FROM Products;

如果你使用Oracle,需要基于ROWNUM(行计数器)来计算行

SELECT prod_name FROM Products
WHERE ROWNUM <=5;

如果你使用MySQL、MariaDB、PostgreSQL或者SQLite,需要使用LIMIT子句,像这样:

SELECT prod_name FROM Products
LIMIT 5;

这些数据库还支持offset关键字,用于指定从哪行数据开始返回。比如:limit n offset m 表示从m+1条开始返回,一共返回n条。

MySQL、MariaDB和SQLite支持简化版的LIMIT 4 OFFSET 3语句,即LIMIT 3,4。使用这个语法,逗号之前的值对应OFFSET,逗号之后的值对应LIMIT。

使用注释

在sql语句中,使用“–”来进行语句注释

第3课 排序查询数据

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

SELECT prod_name FROM Products
ORDER BY prod_name;

在指定一条ORDER BY子句时,应该保证它是SELECT语句中最后一条子句。如果它不是最后的子句,将会出现错误消息。
通常,ORDER BY子句中使用的列将是为显示而选择的列。但是,实际上并不一定要这样,用非检索的列排序数据是完全合法的。

按照多个列排序

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

对于上述例子中的输出,仅在多个行具有相同的prod_price值时才对产品按prod_name进行排序。如果prod_price列中所有的值都是唯一的,则不会按prod_name排序。

按照列位置排序

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

ORDER BY 2表示按SELECT清单中的第二个列prod_price进行排序。ORDER BY 2, 3表示先按prod_price,再按prod_name进行排序。

不建议使用这个排序方式

指定排序方向

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

DESC关键字只应用到直接位于其前面的列名。在上例中,只对prod_price列指定DESC,对prod_name列不指定。因此,prod_price列以降序排序,而prod_name列(在每个价格内)仍然按标准的升序排序。

默认升序排序(aesc),降序使用desc

第4课 过滤数据

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

WHERE子句操作符

上面列出的某些操作符是冗余的(如< >与!=相同,! <相当于>=)。并非所有DBMS都支持这些操作符。想确定你的DBMS支持哪些操作符,请参阅相应的文档。

SELECT prod_name, prod_price
FROM Products
WHERE prod_price BETWEEN 5 AND 10

问题:between是否包括边界值

上面也可以写成

prod_price > 5 and prod_price < 10

不能写成

5<prod_price<10

在sql语句中,字符串用单引号包裹,而不是双引号

第5课 高级数据过滤

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

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

SQL(像多数语言一样)在处理OR操作符前,优先处理AND操作符(and 的优先级高于or)。当SQL看到上述WHERE子句时,它理解为:由供应商BRS01制造的价格为10美元以上的所有产品,以及由供应商DLL01制造的所有产品,而不管其价格如何。

此问题的解决方法是使用圆括号对操作符进行明确分组。

SELECT prod_name, prod_price
FROM Products
WHERE (vend_id = 'DLL01' OR vend_id = 'BRS01') AND prod_price >= 10;

圆括号具有比AND或OR操作符更高的求值顺序,所以DBMS首先过滤圆括号内的OR条件。这时,SQL语句变成了选择由供应商DLL01或BRS01制造的且价格在10美元及以上的所有产品,这正是我们希望的结果。

任何时候使用具有AND和OR操作符的WHERE子句,都应该使用圆括号明确地分组操作符。不要过分依赖默认求值顺序,即使它确实如你希望的那样。使用圆括号没有什么坏处,它能消除歧义。

in操作的优点

  • 在有很多合法选项时,IN操作符的语法更清楚,更直观。
  • 在与其他AND和OR操作符组合使用IN时,求值顺序更容易管理。
  • IN操作符一般比一组OR操作符执行得更快(在上面这个合法选项很少的例子中,你看不出性能差异)。
  • IN的最大优点是可以包含其他SELECT语句,能够更动态地建立WHERE子句。(子查询)

NOT操作符

WHERE子句中的NOT操作符有且只有一个功能,那就是否定其后所跟的任何条件。

第6课 用通配符进行过滤

like 操作读符

利用like可以构建比较特殊的查询条件,比如查询包含某个关键字的行。

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

百分号(%)通配符

%表示任何字符出现任意次数。%代表搜索模式中给定位置的0个、1个或多个字符。

根据DBMS的不同及其配置,搜索可以是区分大小写的。如果区分大小写,则’fish%’与Fish bean bag toy就不匹配。
通配符%看起来像是可以匹配任何东西,但有个例外,这就是NULL。子句WHERE prod_name LIKE ‘%’不会匹配产品名称为NULL的行。

下划线(_)通配符

下划线的用途与%一样,但它只匹配单个字符,而不是多个字符。

方括号([ ])通配符

方括号([])通配符用来指定一个字符集,它必须匹配指定位置(通配符的位置)的一个字符。

找出所有名字以J或M起头的联系人,可进行如下查询:

SELECT cust_contact
FROM Customers
WHERE cust_contact LIKE '[JM]%'

此通配符可以用前缀字符^(脱字号)来否定。例如,下面的查询匹配以J和M之外的任意字符起头的任意联系人名:

SELECT cust_contact
FROM Customers
WHERE cust_contact LIKE '[^JM]%'

当然,也可以使用NOT操作符得出类似的结果。

SELECT cust_contact
FROM Customers
WHERE NOT cust_contact LIKE '[JM]%'

使用通配符的技巧

SQL的通配符很有用。但这种功能是有代价的,即通配符搜索一般比前面讨论的其他搜索要耗费更长的处理时间。

这里给出一些使用通配符时要记住的技巧。

  • 不要过度使用通配符。如果其他操作符能达到相同的目的,应该使用其他操作符。
  • 在确实需要使用通配符时,也尽量不要把它们用在搜索模式的开始处。把通配符置于开始处,搜索起来是最慢的
  • 仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。

第7课 创建计算字段

这一课介绍什么是计算字段,如何创建计算字段,以及如何从应用程序中使用别名引用它们。

计算字段

存储在数据库表中的数据一般不是应用程序所需要的格式,下面举几个例子。

  • 需要显示公司名,同时还需要显示公司的地址,但这两个信息存储在不同的表列中。
  • 城市、州和邮政编码存储在不同的列中(应该这样),但邮件标签打印程序需要把它们作为一个有恰当格式的字段检索出来。
  • 列数据是大小写混合的,但报表程序需要把所有数据按大写表示出来。
  • 物品订单表存储物品的价格和数量,不存储每个物品的总价格(用价格乘以数量即可)。但为打印发票,需要物品的总价格。
  • 需要根据表数据进行诸如总数、平均数的计算。

在上述每个例子中,存储在表中的数据都不是应用程序所需要的。我们需要直接从数据库中检索出转换、计算或格式化过的数据,而不是检索出数据,然后再在客户端应用程序中重新格式化

这就是计算字段可以派上用场的地方了。与前几课介绍的列不同,计算字段并不实际存在于数据库表中。计算字段是运行时在SELECT语句内创建的。

在SQL语句内可完成的许多转换和格式化工作都可以直接在客户端应用程序内完成。但一般来说,在数据库服务器上完成这些操作比在客户端中完成要快得多。

拼接字段

关于字符串拼接

Access和SQL Server使用+号。DB2、Oracle、PostgreSQL、SQLite和Open Office Base使用||。在MySQL和MariaDB中,必须使用特殊的函数。

但是基本上,所有的数据库都有拼接函数,建议使用拼接函数来进行字符串拼接。

SELECT Concat(vend_name, ' (', vend_country, ')')
FROM Vendors

说明:TRlM函数
大多数DBMS都支持RTRIM()(正如刚才所见,它去掉字符串右边的空格)、LTRIM()(去掉字符串左边的空格)以及TRIM()(去掉字符串左右两边的空格)。

别名的使用

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

Oracle 中没有 as 关键字。

执行算术计算

计算字段的另一常见用途是对检索出的数据进行算术计算。

SELECT prod_id,
    quantity,
    item_price,
    quantity*item_price AS expanded_price FROM OrderItems
WHERE order_num = 2008;

支持加减乘除等运算符。

第8课 使用函数处理数据

函数

SQL也可以用函数来处理数据。函数一般是在数据上执行的,为数据的转换和处理提供了方便。

虽然使用函数可以给我们带来方便,但是我们也应该要意识到函数给我们带来的问题:每个数据库都有自己的函数实现,只有很少几个函数是可以在多数数据库中通用的,也就是说你使用了一个特定的函数后,移植到其他数据库就需要重新调整sql。

使用函数

大多数SQL实现支持以下类型的函数。

  • 用于处理文本字符串(如删除或填充值,转换值为大写或小写)的文本函数。
  • 用于在数值数据上进行算术操作(如返回绝对值,进行代数运算)的数值函数。
  • 用于处理日期和时间值并从这些值中提取特定成分(如返回两个日期之差,检查日期有效性)的日期和时间函数。
  • 返回DBMS正使用的特殊信息(如返回用户登录信息)的系统函数。

日期处理函数,每个数据库的实现差异性都很大。

数值处理函数仅处理数值数据。这些函数一般主要用于代数、三角或几何运算,因此不像字符串或日期-时间处理函数使用那么频繁。

具有讽刺意味的是,在主要DBMS的函数中,数值函数是最一致、最统一的函数下面列出一些常用的数值处理函数。

第9课 汇总数据(聚合函数)重点

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

聚集函数(聚合函数)

我们经常需要汇总数据而不用把它们实际检索出来,为此SQL提供了专门的函数。使用这些函数,SQL查询可用于检索数据,以便分析和报表生成。这种类型的检索例子有:

  • 确定表中行数(或者满足某个条件或包含某个特定值的行数);
  • 获得表中某些行的和;
  • 找出表列(或所有行或某些特定的行)的最大值、最小值、平均值。

上面的列子中,需要原始数据参与计算,但是最后的结构又不需要这些原始数据,而是需要计算出来的汇总信息。

为方便这种类型的检索,SQL给出了5个聚集函数。这些函数能进行上述检索。与前一章介绍的数据处理函数不同,SQL的聚集函数在各种主要SQL实现中得到了相当一致的支持

AVG()函数

SELECT AVG(prod_price) AS avg_price
FROM Products
WHERE vend_id = 'DLL01';

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

COUNT()函数
COUNT()函数进行计数。可利用COUNT()确定表中行的数目或符合特定条件的行的数目。

COUNT()函数有两种使用方式:

  • 使用COUNT(*)对表中行的数目进行计数,不管表列中包含的是空值(NULL)还是非空值。
  • 使用COUNT(column)对特定列中具有值的行进行计数,忽略NULL值。

MAX()函数

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

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

min函数*
用法和max函数类似

SUM()函数
SUM()用来返回指定列值的和(总计)。

SELECT SUM(item_price*quantity) AS total_price
FROM OrderItems
WHERE order_num = [20005](//tel:20005/);

如本例所示,利用标准的算术操作符,所有聚集函数都可用来执行多个列上的计算。
SUM()函数忽略列值为NULL的行。

PS:我们写sql语句时也要有判断空值的意识,想着我们选择的字段要是null,程序会不会报异常,会不会影响我们的业务逻辑

聚集不同

以上5个聚集函数都可以如下使用。

  • 对所有行执行计算,指定ALL参数或不指定参数(因为ALL是默认行为)。
  • 只包含不同的值,指定DISTINCT参数。
SELECT AVG(DISTINCT prod_price) AS avg_price
FROM Products
WHERE vend_id = 'DLL01';

加了distinct参数后,如果有多个prod_price的值一样,只取其中一个进行计算。

DISTINCT不能用于COUNT(*)。类似地,DISTINCT必须使用列名,不能用于计算或表达式。

组合聚集函数

目前为止的所有聚集函数例子都只涉及单个函数。但实际上,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;

第10课 分组数据

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

数据分组

所谓数据分组,就是先对选出来的数据分组,再对这些分组数据进行聚合计算。

group by 一般和聚合函数配合使用。

创建分组

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

使用GROUP BY子句前,需要知道一些重要的规定。

  • GROUP BY子句可以包含任意数目的列,因而可以对分组进行嵌套,更细致地进行数据分组。
  • 如果在GROUP BY子句中嵌套了分组,数据将在最后指定的分组上进行汇总。换句话说,在建立分组时,指定的所有列都一起计算(所以不能从个别的列取回数据)。
  • GROUP BY子句中列出的每一列都必须是检索列或有效的表达式(但不能是聚集函数)。如果在SELECT中使用表达式,则必须在GROUP BY子句中指定相同的表达式。不能使用别名。
  • 大多数SQL实现不允许GROUP BY列带有长度可变的数据类型(如文本或备注型字段)。
  • 除聚集计算语句外,SELECT语句中的每一列都必须在GROUP BY子句中给出。
  • 如果分组列中包含具有NULL值的行,则NULL将作为一个分组返回。如果列中有多行NULL值,它们将分为一组。
  • GROUP BY子句必须出现在WHERE子句之后,ORDER BY子句之前。

过滤分组

过滤分组的作用:根据分组以后得到的统计数据,进一步对数据进行过滤。

比如:你可能想要列出至少有两个订单的所有顾客。

我们已经看到了WHERE子句的作用。但是,在这个例子中WHERE不能完成任务,因为WHERE过滤指定的是行而不分组。事实上,WHERE没有分组的概念。

那么,不使用WHERE使用什么呢?SQL为此提供了另一个子句,就是HAVING子句。HAVING非常类似于WHERE。事实上,目前为止所学过的所有类型的WHERE子句都可以用HAVING来替代。唯一的差别是,WHERE过滤行,而HAVING过滤分组。

WHERE子句的条件(包括通配符条件和带多个操作符的子句)。这些有关WHERE的所有技术和选项都适用于HAVING。它们的句法是相同的,只是关键字有差别。

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

HAVING和WHERE的差别
WHERE在数据分组前进行过滤,HAVING在数据分组后进行过滤。这是一个重要的区别,WHERE排除的行不包括在分组中。这可能会改变计算值,从而影响HAVING子句中基于这些值过滤掉的分组。

分组和排序

GROUP BY和ORDER BY经常完成相同的工作,但它们非常不同,理解这一点很重要。

我们经常发现,用GROUP BY分组的数据确实是以分组顺序输出的。但并不总是这样,这不是SQL规范所要求的。

一般在使用GROUP BY子句时,应该也给出ORDER BY子句。这是保证数据正确排序的唯一方法。千万不要仅依赖GROUP BY排序数据。

SELECT子句顺序

第11课 使用子查询

子查询

即嵌套在其他查询中的查询。

利用子查询进行过滤

SELECT cust_id
FROM Orders
WHERE order_num IN (SELECT order_num
           FROM OrderItems
           WHERE prod_id = 'RGAN01');

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

注意:只能是单列
作为子查询的SELECT语句只能查询单个列。企图检索多个列将返回错误。
子查询的性能:如果子查询嵌套的层次太多,性能会出问题。而且又是多层次的子查询并不是解决问题的最好方法。

作为计算字段使用子查询

前面介绍的功能是作为in操作的条件。

SELECT cust_name,
    cust_state,
    (SELECT COUNT(*)
    FROM Orders
WHERE Orders.cust_id = Customers.cust_id) AS orders
FROM Customers
ORDER BY cust_name;

第12课 联结表

联结

将两张表通过联系字段拼接起来。

创建联结

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

笛卡儿积(cartesian product)
由没有联结条件的表关系返回的结果为笛卡儿积。检索出的行的数目将是第一个表中的行数乘以第二个表中的行数。

内联结
上面使用的联结称为等值联结(equijoin),它基于两个表之间的相等测试。这种联结也称为内联结(inner join)。其实,可以对这种联结使用稍微不同的语法,明确指定联结的类型。下面的SELECT语句返回与前面例子完全相同的数据:

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

ANSI SQL规范首选INNER JOIN语法,之前使用的是简单的等值语法。其实,SQL语言纯正论者是用鄙视的眼光看待简单语法的。

联结多个表
SQL不限制一条SELECT语句中可以联结的表的数目。创建联结的基本规则也相同。首先列出所有表,然后定义表之间的关系。

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](//tel:20007/);

注意:性能考虑
DBMS在运行时关联指定的每个表,以处理联结。这种处理可能非常耗费资源,因此应该注意,不要联结不必要的表。联结的表越多,性能下降越厉害。
注意:联结中表的最大数目
虽然SQL本身不限制每个联结约束中表的数目,但实际上许多DBMS都有限制。

第13课 创建高级联结

使用表别名

SQL除了可以对列名和计算字段使用别名,还允许给表名起别名。

注意:Oracle中没有AS
Oracle不支持AS关键字。要在Oracle中使用别名,可以不用AS,简单地指定列名即可(因此,应该是Customers C,而不是Customers AS C)。

使用不同类型的联结

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

自联结
假如要给与Jim Jones同一公司的所有顾客发送一封信件。

常规做法:子查询

SELECT cust_id, cust_name, cust_contact
FROM Customers
WHERE cust_name = (SELECT cust_name
          FROM Customers
          WHERE cust_contact = 'Jim Jones');

自链接的做法:

SELECT c1.cust_id, c1.cust_name, c1.cust_contact
FROM Customers AS c1, Customers AS c2
WHERE c1.cust_name = c2.cust_name
 AND c2.cust_contact = 'Jim Jones';

提示:用自联结而不用子查询
自联结通常作为外部语句,用来替代从相同表中检索数据的使用子查询语句。虽然最终的结果是相同的,但许多DBMS处理联结远比处理子查询快得多。应该试一下两种方法,以确定哪一种的性能更好。

自然联结
可以不用重点关注

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

  • 对每个顾客下的订单进行计数,包括那些至今尚未下订单的顾客;

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

SELECT Customers.cust_id, Orders.order_num
FROM Customers LEFT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id;

与内联结关联两个表中的行不同的是,外联结还包括没有关联行的行。在使用OUTER JOIN语法时,必须使用RIGHT或LEFT关键字指定包括其所有行的表(RIGHT指出的是OUTER JOIN右边的表,而LEFT指出的是OUTER JOIN左边的表)。

使用带聚集函数的联结

聚合函数一般用来对表中数据进行汇总统计,或者对分组数据进行分组统计。

这些函数也可以与联结一起使用。

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;

第14课 组合查询

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

组合查询

SQL允许执行多个查询(多条SELECT语句),并将结果作为一个查询结果集返回。这些组合查询通常称为并(union)或复合查询(compound query)。
主要有两种情况需要使用组合查询:

  • 在一个查询中从不同的表返回结构数据;
  • 对一个表执行多个查询,按一个查询返回数据。
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';

上面的列子,也可以使用下面的sql实现。

SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI')
 OR cust_name = 'Fun4All';

在这个简单的例子中,使用UNION可能比使用WHERE子句更为复杂。但对于较复杂的过滤条件,或者从多个表(而不是一个表)中检索数据的情形,使用UNION可能会使处理更简单。

提示:UNION的限制
使用UNION组合SELECT语句的数目,SQL没有标准限制。但是,最好是参考一下具体的DBMS文档,了解它是否对UNION能组合的最大语句数目有限制。

理论上讲,从性能上看使用多条WHERE子句条件还是UNION应该没有实际的差别。不过我说的是理论上,实践中多数查询优化程序并不能达到理想状态,所以最好测试一下这两种方法,看哪种工作得更好。

进行组合时需要注意几条规则。

  • UNION必须由两条或两条以上的SELECT语句组成,语句之间用关键字UNION分隔(因此,如果组合四条SELECT语句,将要使用三个UNION关键字)。
  • UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过,各个列不需要以相同的次序列出)。
  • 列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含转换的类型(例如,不同的数值类型或不同的日期类型)。

如果查询出来的数据完全相同,union 会默认只返回其中一条,如果想让数据全部返回,可以使用 union all

提示:UNION与WHERE
这一课一开始我们说过,UNION几乎总是完成与多个WHERE条件相同的工作。UNION ALL为UNION的一种形式,它完成WHERE子句完成不了的工作。如果确实需要每个条件的匹配行全部出现(包括重复行),就必须使用UNION ALL,而不是WHERE。

在用UNION组合查询时,只能使用一条ORDER BY子句,它必须位于最后一条SELECT语句之后。但是它是对整个返回结果集的排序。

第15课 插入数据

顾名思义,INSERT用来将行插入(或添加)到数据库表。插入有几种方式:

  • 插入完整的行;
  • 插入行的一部分;
  • 插入某些查询的结果。
INSERT INTO Customers(cust_id,
            cust_name,cust_address,
            cust_city,
            cust_state,
            cust_zip,
            cust_country,
            cust_contact,
            cust_email)
VALUES('[1000000006](//tel:1000000006/)',
'Toy Land',
    '123 Any Street',
    'New York',
    'NY',
    '1111',
    'USA',
    NULL,
    NULL);

VALUES中的第一个值对应于第一个指定列名,第二个值对应于第二个列名,如此等等。
因为提供了列名,VALUES必须以其指定的次序匹配指定的列名,不一定按各列出现在表中的实际次序。其优点是,即使表的结构改变,这条INSERT语句仍然能正确工作。

注意:省略列
如果表的定义允许,则可以在INSERT操作中省略某些列。省略的列必须满足以下某个条件。

  • 该列定义为允许NULL值(无值或空值)。
  • 在表定义中给出默认值。这表示如果不给出值,将使用默认值。

插入检索出的数据

INSERT INTO Customers(cust_id,
            cust_contact,
            cust_email,
            cust_name,
            cust_address,
            cust_city,
            cust_state,
            cust_zip,
            cust_country)
SELECT cust_id,
    cust_contact,
    cust_email,
    cust_name,
    cust_address,
    cust_city,
    cust_state,
    cust_zip,
    cust_country
FROM CustNew;

从一个表复制到另一个表

要将一个表的内容复制到一个全新的表(运行中创建的表),可以使用SELECT INTO语句。

SELECT *
INTO CustCopy
FROM Customers;

MariaDB、MySQL、Oracle、PostgreSQL和SQLite使用的语法稍有不同:

CREATE TABLE CustCopy AS
SELECT * FROM Customers;

第16课 更新和删除数据

这一课介绍如何利用UPDATE和DELETE语句进一步操作表数据。

注意:不要省略WHERE子句
在使用UPDATE时一定要细心。因为稍不注意,就会更新表中的所有行。

UPDATE Customers
SET cust_email = '[[email protected]](//mailto:[email protected]/)'
WHERE cust_id = '[1000000005](//tel:1000000005/)';

在更新多个列时,只需要使用一条SET命令,每个“列=值”对之间用逗号分隔(最后一列之后不用逗号)。

数据的删除更简单。

注意:不要省略WHERE子句
在使用DELETE时一定要细心。因为稍不注意,就会错误地删除表中所有行。
提示:更快的删除
如果想从表中删除所有行,不要使用DELETE。可使用TRUNCATE TABLE语句,它完成相同的工作,而速度更快(因为不记录数据的变动)。先删除表再建立表结构

第17课 创建和操纵表

创建表

一般有两种创建表的方法:

  • 多数DBMS都具有交互式创建和管理数据库表的工具;
  • 表也可以直接用SQL语句操纵。
CREATE TABLE Products
(
  prod_id    CHAR(10)      NOT NULL,
  vend_id    CHAR(10)NOT NULL,
  prod_name   CHAR(254)     NOT NULL,
  prod_price   DECIMAL(8,2)    NOT NULL,
  prod_desc   VARCHAR(1000)   NULL
);

更新表

使用alter table关键字

删除表

drop table xxx

重命名表

每个数据库不太一样

第18课 使用视图

视图

视图是虚拟的表。与包含数据的表不一样,视图只包含使用时动态检索数据的查询。

为什么使用视图

  • 重用SQL语句。
  • 简化复杂的SQL操作。在编写查询后,可以方便地重用它而不必知道其基本查询细节。
  • 使用表的一部分而不是整个表。
  • 保护数据。可以授予用户访问表的特定部分的权限,而不是整个表的访问权限。
  • 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。

创建视图之后,可以用与表基本相同的方式使用它们。可以对视图执行SELECT操作,过滤和排序数据,将视图联结到其他视图或表,甚至添加和更新数据(添加和更新数据存在某些限制)。

下面是关于视图创建和使用的一些最常见的规则和限制。

  • 与表一样,视图必须唯一命名(不能给视图取与别的视图或表相同的名字)。
  • 对于可以创建的视图数目没有限制。
  • 创建视图,必须具有足够的访问权限。这些权限通常由数据库管理人员授予。
  • 视图可以嵌套,即可以利用从其他视图中检索数据的查询来构造视图。所允许的嵌套层数在不同的DBMS中有所不同(嵌套视图可能会严重降低查询的性能,因此在产品环境中使用之前,应该对其进行全面测试)。
  • 许多DBMS禁止在视图查询中使用ORDER BY子句。
  • 有些DBMS要求对返回的所有列进行命名,如果列是计算字段,则需要使用别名(关于列别名的更多信息,请参阅第7课)。
  • 视图不能索引,也不能有关联的触发器或默认值。
  • 有些DBMS把视图作为只读的查询,这表示可以从视图检索数据,但不能将数据写回底层表。详情请参阅具体的DBMS文档。
  • 有些DBMS允许创建这样的视图,它不能进行导致行不再属于视图的插入或更新。例如有一个视图,只检索带有电子邮件地址的顾客。如果更新某个顾客,删除他的电子邮件地址,将使该顾客不再属于视图。这是默认行为,而且是允许的,但有的DBMS可能会防止这种情况发生。

建议:能不用,就不用视图

创建视图

视图用CREATE VIEW语句来创建。与CREATE TABLE一样,CREATE VIEW 只能用于创建不存在的视图。

说明:视图重命名
删除视图,可以使用DROP语句,其语法为DROP VIEW viewname;。覆盖(或更新)视图,必须先删除它,然后再重新创建。

利用视图简化复杂的联结

一个最常见的视图应用是隐藏复杂的SQL,这通常涉及联结。

CREATE VIEW ProductCustomers ASSELECT cust_name, cust_contact, prod_id
FROM Customers, Orders, OrderItems
WHERE Customers.cust_id = Orders.cust_id
 AND OrderItems.order_num = Orders.order_num;

视图极大地简化了复杂SQL语句的使用。利用视图,可一次性编写基础的SQL,然后根据需要多次使用。

用视图重新格式化检索出的数据
视图的另一常见用途是重新格式化检索出的数据。

用视图过滤不想要的数据

使用视图与计算字段

第19课 使用存储过程

什么是存储过程

一条或者多条sql语句的集合,可以将其看成是一个sql函数。

为什么要使用存储过程

  • 通过把处理封装在一个易用的单元中,可以简化复杂的操作(如前面例子所述)。
  • 由于不要求反复建立一系列处理步骤,因而保证了数据的一致性。如果所有开发人员和应用程序都使用同一存储过程,则所使用的代码都是相同的。
    这一点的延伸就是防止错误。需要执行的步骤越多,出错的可能性就越大。防止错误保证了数据的一致性。
  • 简化对变动的管理。如果表名、列名或业务逻辑(或别的内容)有变化,那么只需要更改存储过程的代码。使用它的人员甚至不需要知道这些变化。这一点的延伸就是安全性。通过存储过程限制对基础数据的访问,减少了数据讹误(无意识的或别的原因所导致的数据讹误)的机会。
  • 因为存储过程通常以编译过的形式存储,所以DBMS处理命令所需的工作量少,提高了性能。
  • 存在一些只能用在单个请求中的SQL元素和特性,存储过程可以使用它们来编写功能更强更灵活的代码。换句话说,使用存储过程有三个主要的好处,即简单、安全、高性能。显然,它们都很重要。

不过,在将SQL代码转换为存储过程前,也必须知道它的一些缺陷。

  • 不同DBMS中的存储过程语法有所不同。事实上,编写真正的可移植存储过程几乎是不可能的。不过,存储过程的自我调用(名字以及数据如何传递)可以相对保持可移植。因此,如果需要移植到别的DBMS,至少客户端应用代码不需要变动。
  • 一般来说,编写存储过程比编写基本SQL语句复杂,需要更高的技能,更丰富的经验。因此,许多数据库管理员把限制存储过程的创建作为安全措施(主要受上一条缺陷的影响)。
    尽管有这些缺陷,存储过程还是非常有用的,并且应该使用。事实上,多数DBMS都带有用于管理数据库和表的各种存储过程。更多信息请参阅具体的DBMS文档。

存储过程执行

参考具体的数据库

存储过程创建

参考具体的数据库

事务管理

提示:可以回退哪些语句?
事务处理用来管理INSERT、UPDATE和DELETE语句。不能回退SELECT语句(回退SELECT语句也没有必要),也不能回退CREATE或DROP操作。事务处理中可以使用这些语句,但进行回退时,这些操作也不撤销。

每个数据库的事务实现可能不太一样,针对具体的数据库学习,效果比较好。

第21课 使用游标

游标(cursor)是一个存储在DBMS服务器上的数据库查询,它不是一条SELECT语句,而是被该语句检索出来的结果集。在存储了游标之后,应用程序可以根据要滚动或浏览其中的数据。

第22课 高级sql特性

约束

  • 主键约束
  • 外键约束
  • 唯一约束
  • 检查约束

索引

索引用来排序数据以加快搜索和排序操作的速度。

在开始创建索引前,应该记住以下内容。

  • 索引改善检索操作的性能,但降低了数据插入、修改和删除的性能。在执行这些操作时,DBMS必须动态地更新索引。
  • 索引数据可能要占用大量的存储空间。
  • 并非所有数据都适合做索引。取值不多的数据(如州)不如具有更多可能值的数据(如姓或名),能通过索引得到那么多的好处。
  • 索引用于数据过滤和数据排序。如果你经常以某种特定的顺序排序数据,则该数据可能适合做索引。
  • 可以在索引中定义多个列(例如,州加上城市)。这样的索引仅在以州加城市的顺序排序时有用。如果想按城市排序,则这种索引没有用处。
    没有严格的规则要求什么应该索引,何时索引。大多数DBMS提供了可用来确定索引效率的实用程序,应该经常使用这些实用程序。

触发器

附件

附件A

从上面的地址获取建表语句和数据。