第1课 数据库

这一课介绍SQL究竟是什么,它能做什么事情。

 

1.1 数据库基础

下面是一些数据库概念的简要介绍,如果你刚开始接触数据库,可以由此了解必需的基本知识。

 

1.1.1 数据库

数据库这个术语的用法很多,但就本书而言(从SQL的角度来看),数据库是一个以某种有组织的方式存储的数据集合。最简单的办法是将数据库想象为一个文件柜。这个文件柜是一个存放数据的物理位置,不管数据是什么,也不管数据是如何组织的。 

数据库(database)
保存有组织的数据的容器(通常是一个文件或一组文件)。

 

注意:误用导致馄淆
人们通常用数据库这个术语来代表他们使用的数据库软件,这是不正确的,也因此产生了许多混淆。确切地说,数据库软件应称为数据库管理系统(即DBMS)。数据库是通过DBMS创建和操纵的容器,而具体它宄竟是什么,形式如何,各种数据库都不一样。

 

1.1.2 表

你往文件柜里放资料时,并不是随便将它们扔进某个抽屉就完事了,而是在文件柜中创建文件,然后将相关的资料放入特定的文件中。在数据库领域中,这种文件称为表。表是一种结构化的文件,可用来存储某种特定类型的数据。表可以保存顾客清单、产品目录,或者其他信息清单。

表(table)
某种特定类型数据的结构化清单。

 

1.1.3 列和数据类型

表由列组成。列存储表中某部分的信息。理解列的最好办法是将数据库表想象为一个网格,就像个电子表格那样。网格中每一列存储着某种特定的信息。例如,在顾客表中,一列存储顾客编号,另一列存储顾客姓名。

列 Ccolumn)
表中的一个字段。所有表都是由一个或多个列组成的。

数据库中每个列都有相应的数据类型。数据类型(datatype)定义了列可以存储哪些数据种类。例如,如果列中存储的是数字(或许是订单中的物品数),则相应的数据类型应该为数值类型。如果列中存储的是日期、文本、注释、金额等,则应该规定好恰当的数据类型。

数据类型
所允许的数据的类型。每个表列都有相应的数据类型,它限制(或允许)该列中存储的数据。

 

1.1.4 行

表中的数据是按行存储的,所保存的每个记录存储在自己的行内。如果将表想象为网格,网格中垂直的列为表列,水平行为表行。例如,顾客表可以每行存储一个顾客。表中的行编号为记录的编号。

行(row)
表中的一个记录。

 

1.1.5 主键

表中每一行都应该有一列(或几列)可以唯一标识自己,称为主键列,主键列不允许NULL值。例如,顾客表可以使用顾客编号,而订单表可以使用订单编号。

主键(primary key)
一列(或一组列),其值能够唯一标识表中每一行。

1.2 什么是SQL

SQL (发音为字母S-Q-L或sequel)是Structured Query Language(结构化查询语言)的缩写。SQL是一种专门用来与数据库沟通的语言。

与其他语言(如英语或Java、C、PHP这样的编程语言)不一样,SQL中只有很少的词,这是有意而为的。设计SQL的目的是很好地完成一项任务 一 提供一种从数据库中读写数据的简单有效的方法。

SQL有如下的优点。

  • SQL不是某个特定数据库供应商专有的语言。几乎所有重要的DBMS都支持SQL,所以学习此语言使你几乎能与所有数据库打交道。
  • SQL简单易学。它的语句全都是由有很强描述性的英语单词组成,而且这些单词的数目不多。
  • SQL虽然看上去很简单,但实际上是一种强有力的语言,灵活使用其语言元素,可以进行非常复杂和高级的数据库操作。

 

1.3 动手实践

与其他任何语言一样,学习SQL的最好方法是自己动手实践。为此,需要一个数据库和用来测试SQL语句的应用系统。

附录A给出了具体的样例表,并介绍了获得(或创建)它们的详细步骤。附录B介绍在各种应用程序中执行SQL所需的步骤。这里给出下载样例表的地址:http://forta.com/books/0672336073/。

(笔者使用的是MySQL5.6,所以下面的语句若不做介绍,皆默认使用MySQL版本的)

第2课 检索数据

这一课介绍如何使用SELECT语句从表中检索一个或多个数据列。

 

2.1 SELECT 语句

正如第1课所述,SQL语句是由简单的英语单词构成的。这些单词称为关键字,每个SQL语句都是由一个或多个关键字构成的。最经常使用的SQL语句大概就是SELECT语句了。它的用途是从一个或多个表中检索信息。

关键字(keyword)
作为SQL组成部分的保留字。关键字不能用作表或列的名字。附录E列出了某些经常使用的保留字。

为了使用SELECT检索表数据,必须至少给出两条信息一想选择什么,以及从什么地方选择。

 

2.2 检索单个列

我们将从简单的SQL SELECT语句讲起,此语句如下所示:

输入▼


SELECT prod_name FROM Products;


分析▼


上述语句利用SELECT语句从Products表中检索一个名为prod_name的列。所需的列名写在SELECT关键字之后,FROM关键字指出从哪个表中检索数据。

提示:SQL语句和大小写
请注意,SQL语句不区分大小写,因此SELECT与select是相同的。同样,写成Select也没有关系。许多SQL开发人员喜欢对SQL关键字使用大写,而对列名和表名使用小写,这样做使代码更易于阅读和调试。不过,一定要认识到虽然SQL是不区分大小写的,但是表名、列名和值可能有所不同(这有赖于具体的DBMS及其如何配置)。
提示:使用空格
在处理SQL语句时,其中所有空格都被忽略。SQL语句可以写成长长的一行,也可以分写在多行。多数SQL开发人员认为,将SQL语句分成多行更容易阅读和调试。

 

2.3 检索多个行

要想从一个表中检索多个列,仍然使用相同的SELECT语句。唯一的不同是必须在SELECT关键字后给出多个列名,列名之间必须以逗号分隔。

输入▼


SELECT prod_id, prod_name, prod_price FROM Products;


分析▼


这条语句使用SELECT语句从表Products中选择多个数据。在这个例子中,指定了3个列名,列名之间用逗号分隔。

 

2.4 检索所有列

除了指定所需的列外(如上所述,一个或多个列),SELECT语句还可以检索所有的列而不必逐个列出它们。在实际列名的位置使用星号(*)通配符可以做到这点,如下所示。

输入▼


SELECT * FROM Products;


分析▼


如果给定一个通配符(*),则返回表中所有列。列的顺序一般是列在表定义中出现的物理顺序,但并不总是如此。

 

2.5 检索不同的值

如前所述,SELECT语句返回所有匹配的行。但是,如果你不希望每个值每次都出现,该怎么办呢?办法就是使用DISTINCT关键字,顾名思义,它指示数据库只返回不同的值。

输入▼


SELECT DISTINCT vend_id FROM Products;


分析▼


SELECT DISTINCT vend_id告诉DBMS只返回不同(具有唯一性)的vend_id行。如果使用DISTINCT关键字,它必须直接放在列名的前面。

警告:不能部分使用DISTINCT
DISTINCT关键字作用于所有的列,不仅仅是跟在其后的那一列。例如,你指定SELECT DISTINCT vend_id,prod_price,除非指定的两列完全相同,否则所有的行都会被检索出来。

 

2.6 限制结果

SELECT语句返回指定表中所有匹配的行,很可能是每一行。如果你只想返回第一行或者一定数量的行,该怎么办呢?这是可行的,然而遗憾的是,各种数据库中的这一 SQL实现并不相同。

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

输入▼


SELECT prod_name FROM Products LIMIT 5;


分析▼


上述代码使用SELECT语句来检索单独的一列数据。LIMIT 5指示MySQL等DBMS返回不超过5行的数据。

为了得到后面的5行数据,需要指定从哪儿开始以及检索的行数,像这样:

输入▼


SELECT prod_name FROM Products LIMIT 5 OFFSET 5;


分析▼


LIMIT 5 OFFSET 5指示MySQL等DBMS返回从第5行起的5行数据。第一个数字是指从哪儿开始,第二个数字是检索的行数。

警告:第0行
第一个被检索的行是第0行,而不是第1行。因此,LIMIT 1 OFFSET 1会检索第2行,而不是第1行。
提示:MySQL和MariaDB快捷键
MySQL和MariaDB支持简化版的LIMIT 4 OFFSET 3语句,即LIMIT 3,4。使用这个语法,之前的值对应LIMIT,之后的值对应OFFSET。

 

2.7 使用注释

我们先来看行内注释:

输入▼


SELECT prod_name FROM Products; --这是一条注释


分析▼


注释使用--(两个连字符)嵌在行内。--之后的文本就是注释。

你也可以进行多行注释,注释可以在脚本的任何位置停止和开始。

输入▼


/* SELECT prod_name, vend_id

FROM Products; */

SELECT prod_name FROM Products;


分析▼


注释从/*开始,到*/结束,/*和*/之间的任何内容都是注释。这种方式常用于给代码加注释,就如这个例子演示的。

第3课 排序检索数据

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

 

3.1 排序数据

为了明确地排序用SELECT语句检索出的数据,可使用ORDER BY子句。ORDER BY子句取一个或多个列的名字,据此对输出进行排序。请看下面的例子:

输入▼


SELECT prod_name FROM Products
ORDER BY prod_name;


分析▼


ORDER BY子句,指示DBMS软件对prod_name列以字母顺序排序数据。

注意:ORDER BY子句的位置
在指定一条ORDER BY子句时,应该保证它是SELECT语句中最后一条子句。如果它不是最后的子句,将会出现错误消息。

 

3.2 按多个列排序

下面的代码检索3个列,并按其中两个列对结果进行排序 一 首先按价格,然后按名称排序。

输入▼


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


分析▼


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

 

3.3 按列位置排序

除了能用列名指出排序顺序外,ORDER BY还支持按相对列位置进行排序。为理解这一内容,我们来看个例子:

输入▼


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


分析▼


SELECT清单中指定的是选择列的相对位置而不是列名。ORDER BY 2 表示按SELECT清单中的第二个列prod_name进行排序。ORDER BY 2, 3 表示先按prod_price,再按prod_name进行排序。

 

3.4 指定排序方向

数据排序不限于升序排序(从A到Z),这只是默认的排序顺序。还可以使用ORDER BY子句进行降序(从Z到A)排序。为了进行降序排序,必须 指定DESC关键字。例子如下:

输入▼


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列(在每个价格内)仍然按标准的升序排序。

警告:在多个列上降序排序
如果想在多个列上进行降序排序,必须对每一列指定DESC关键字。

第4课 过滤数据

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

 

4.1 使用WHERE子句

在SELECT语句中,数据根据WHERE子句中指定的搜索条件进行过滤。WHERE子句在表名(FROM子句)之后给出,如下所示:

输入▼


SELECT prod_name, prod_price FROM Products

WHERE prod_price = 3.49;


分析▼


这条语句从products表中检索两个列,但不返回所有行,只返回prod_price值为3.49的行。

 

4.2 WHERE子句操作符

SQL支持下表列出的所有条件操作符。

操作符 说明
= 等于
!= 不等于
< 小于
<= 小于等于
> 大于
>= 大于等于
BETWEEN 在指定的两个值之间
IS NULL 为NULL值

 

4.2.1 范围内检查

要检查某个范围的值,可以使用BETWEEN操作符。其语法与其他WHERE子句的操作符稍有不同,因为它需要两个值,即范围的开始值和结束值。下面的例子说明如何使用BETWEEN操作符,它检索价格在5美元和10美元之间的所有产品:

输入▼


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


分析▼


从这个例子可以看到,在使用BETWEEN时,必须指定两个值——所需范围的低端值和高端值。这两个值必须用AND关键字分隔。

 

4.2.2 空值检查

确定值是否为NULL,不能简单地检查是否=NULL。SELECT语句有一个特殊的WHERE子句,可用来检查具有NULL值的列。这个WHERE子句就是IS NULL子句。其语法如下:

输入▼


SELECT prod_name FROM Products

WHERE prod_price IS NULL;


分析▼


这条语句返回所有没有价格(空prod_price字段,不是价格为0)的产品。

第5课 高级数据过滤

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

 

5.1 组合WHERE子句

第4课介绍的所有WHERE子句在过滤数据时使用的都是单一的条件。为了进行更强的过滤控制,SQL允许给出多个WHERE子句。这些子句有两种使用方式,即以AND子句或OR子句的方式使用。

 

5.1.1 AND操作符

要通过不止一个列进行过滤,可以使用AND操作符给WHERE子句附加条件。下面的代码给出了一个例子:

输入▼


SELECT prod_id, prod_price, prod_name FROM Products

WHERE vend_id = 'DLL01' AND prod_price <= 4;


分析▼


此SQL语句检索由供应商DLL01制造且价格小于等于4美元的所有产品的名称和价格。

 

5.1.2 OR操作符

OR操作符与AND操作符正好相反,它指示DBMS检索匹配任一条件的行。事实上,许多DBMS在OR WHERE子句的第一个条件得到满足的情况下, 就不再计算第二个条件了。例子如下:

输入▼


SELECT prod_name, prod_price FROM Products

WHERE vend_id = 'DLL01' OR vend_id = 'BRS01';


分析▼


此SQL语句检索由任一个指定供应商制造的所有产品的产品名和价格。

 

5.2 IN操作符

IN操作符用来指定条件范围,范围中的每个条件都可以进行匹配。IN取一组由逗号分隔、括在圆括号中的合法值。下面的例子说明了这个操作符:

输入▼


SELECT prod_name, prod_price FROM Products

WHERE vend_id IN ( 'DLL01', 'BRS01' );


分析▼


此SELECT语句检索由供应商DLL01和BRS01制造的所有产品。IN操作符后跟由逗号分隔的合法值,这些值必须括在圆括号中。 你可能会猜测IN操作符完成了与OR相同的功能,恭喜你猜对了!

 

5.3 NOT操作符

WHERE子句中的NOT操作符有且只有一个功能,那就是否定其后所跟的任何条件。NOT关键字可以用在要过滤的列前,而不仅是在其后。下面的例子说明了这个操作符:

输入▼


SELECT prod_name FROM Products

WHERE NOT vend_id = 'DLL01';


分析▼


这里的NOT否定跟在其后的条件,因此,DBMS不是匹配vend_id为DLL01,而是匹配非DLL01之外的所有东西。

第6课 用通配符进行过滤

这一课介绍什么是通配符、如何使用通配符以及怎样使用LIKE操作符进行通配搜索,以便对数据进行复杂过滤。

 

6.1 LIKE操作符

利用通配符,可以创建比较特定数据的搜索模式。例如,如果你想找出名称包含bean bag的所有产品,可以构造一个通配符搜索模式,找出在产品名的任何位置出现bean bag的产品。

 

6.1.1 百分号(%)通配符

最常使用的通配符是百分号(%)。在搜索串中,%表示任何字符出现任意次数。例如,为了找出所有以词Fish起头的产品,可发布以下SELECT语句:

输入▼


SELECT prod_id, prod_name FROM Products

WHERE prod_name LIKE 'Fish%';


分析▼


此例子使用了搜索模式'Fish%’。在执行这条子句时,将检索任意以Fish起头的词。%告诉DBMS接受Fish之后的任意字符,不管它有多少字符。

 

6.1.2 下划线(_)通配符

另一个有用的通配符是下划线(_)。下划线的用途与%—样,但它只匹配单个字符,而不是多个字符。例子如下:

输入▼


SELECT prod_id,prod_name FROM Products

WHERE prod_name LIKE '__ inch teddy bear';


分析▼


这个WHERE子句中的搜索模式给出了后面跟有文本的两个通配符。

 

6.1.3 方括号([])通配符

方括号([])通配符用来指定一个字符集,它必须匹配指定位置(通配符的位置)的一个字符。例如,找出所有名字以J或M起头的联系人,可进行如下查询:

输入▼


SELECT cust_contact FROM Customers

WHERE cust_contact LIKE '[JM]%';


分析▼


这一搜索模式使用了两个不同的通配符。[JM]匹配任何以方括号中字母开头的联系人名,它也只能匹配单个字符。[JM]之后的%通配符匹配第一个字符之后的任意数目的字符,返回所需结果。

第7课 创建计算字段

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

 

7.1 计算字段

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

  • 需要显示公司名,同时还需要显示公司的地址,但这两个信息存储在不同的表列中。
  • 物品订单表存储物品的价格和数量,不存储每个物品的总价格(用价格乘以数量即可)。但为打印发票,需要物品的总价格。
  • 需要根据表数据进行诸如总数、平均数的计算。

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

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

 

7.2 拼接字段

为了说明如何使用计算字段,我们来举一个简单例子,创建由两列组成的标题。在SQL中的SELECT语句中,可使用一个特殊的操作符来拼接两个列。根据你所使用的DBMS,此操作符可用加号(+)或两个竖杠(II)表示。在MySQL和MariaDB中,必须使用特殊的函数。

下面是MySQL和MariaDB中使用的语句:

输入▼


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


分析▼


使用Concat函数拼接vend_name,一个空格' '和vend_country。

 

使用别名

SELECT语句可以很好地拼接地址字段,但这个新计算列的名字是什么呢?实际上它没有名字,它只是一个值。如 果仅在SQL查询工具中查看一下结果,这样没有什么不好。但是,一个未命名的列不能用于客户端应用中,因为客户端没有办法引用它。

为了解决这个问题,SQL支持列别名。别名(alias)是一个字段或值的替换名。别名用AS关键字赋予。请看下面的SELECT语句:

输入▼


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


分析▼


这里的计算字段之后跟了文本AS vend_title,它指示SQL创建一个包含指定计算结果的名为vend_title的计算字段。现在列名为vend_title,任何客户端应用都可以按名称引用这个列,就像它是一个实际的表列一样。

 

7.3 执行算数计算

计算字段的另一常见用途是对检索出的数据进行算术计算。例如,可以汇总2008年这一年订单物品的价格(单价乘以订购数量):

输入▼


SELECT prod_id, quantity, item_price,quantity*item_price AS expanded_price

FROM OrderItems WHERE order_num = 20008;


分析▼


这里的计算字段之后跟了文本AS vend_title,它指示SQL创建一个包含指定计算结果的名为vend_title的计算字段。现在列名为vend_title,任何客户端应用都可以按名称引用这个列,就像它是一个实际的表列一样。

SQL支持基本的加减乘除这4种算术操作符。

第8课 使用数据处理函数

这一课介绍什么是函数,DBMS支持何种函数,以及如何使用这些函数;还将讲解为什么SQL函数的使用可能会带来问题。

8.1 函数

与大多数其他计算机语言一样,SQL也可以用函数来处理数据。函数一般是在数据上执行的,为数据的转换和处理提供了方便。

函数带来的问题

与几乎所有DBMS都等同地支持SQL语句(如SELECT)不同,每一个DBMS都有特定的函数。事实上,只有少数几个函数被所有主要的DBMS等同地支持。虽然所有类型的函数一般都可以在每个DBMS中使用,但各个函数的名称和语法可能极其不同。这就表示为特定SQL实现编写的代码在其他实现中可能不正常。

 

8.2 使用函数

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

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

 

8.2.1 文本处理函数

下面例子使用的是UPPER()函数:

输入▼


SELECT vend_name, UPPER(vend_name) AS vend_name_upcase FROM Vendors;


分析▼


UPPER()将文本转换为大写。

下表列出了一些常用的文本处理函数。

函 数 说 明
LEFT()(或使用子字符串函数) 返回字符串左边的字符
LENGTH() (也使用 DATALENGTH()或LEN()) 返回字符串的长度
LOWER() (Access使用 LCASE()) 将字符串转换为小写
LTRIM() 去掉字符串左边的空格
RIGHT() (或使用子字符串函数) 返回字符串右边的字符
RTRIM() 去掉字符串右边的空格
SOUNDEX() 返回字符串的SOUNDEX值
UPPER() (Access使用UCASE()) 将字符串转换为大写

 

8.2.2 日期和时间处理函数

MySQL和MariaDB用户可使用名为YEAR()的函数从日期中提取年份:

输入▼


SELECT order_num FROM Orders

WHERE YEAR(order_date) = 2012;


分析▼


DBMS提供的功能远不止简单的日期成分提取。大多数DBMS具有比较日期、执行基于日期的运算、选择日期格式等的函数。但是,可以看到,不同DBMS的日期-时间处理函数可能不同。关于具体DBMS支持的日期-时间处理函数,请参阅相应的文档。

 

8.2.3 数值处理函数

数值处理函数仅处理数值数据。这些函数一般主要用于代数、三角或几何运算,因此不像字符串或日期-时间处理函数使用那么频繁。具有讽刺意味的是,在主要DBMS的函数中,数值函数是最一致、最统一的函数。下表列出了一些常用的数值处理函数。

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

第9课 汇总数据

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

 

9.1 聚集函数

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

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

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

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

 

9.1.1 AVG()函数

AVG()通过对表中行数计数并计算其列值之和,求得该列的平均值。下面的例子使用AVG()返回Products表中所有产品的平均价格:

输入▼


SELECT AVG(prod_price) AS avg_price FROM Products

WHERE vend_id = 'DLL01';


分析▼


此SELECT语句返回值avg_price,它包含Products表中所有产品的平均价格。

 

9.1.2 COUNT()函数

C0UNT()函数进行计数。可利用C0UNT()确定表中行的数目或符合特定条件的行的数目。C0UNT()函数有两种使用方式:

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

输入▼


SELECT COUNT(*) AS num_cust FROM Customers;


分析▼


在此例子中,利用C0UNT(*)对所有行计数,不管行中各列有什么值。计数值在num_cust中返回。

 

9.2 聚集不同值

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

  • 对所有行执行计算,指定ALL参数或不指定参数(因为ALL是默认行为)。
  • 只包含不同的值,指定DISTINCT参数。

下面的例子使用AVG()函数返回特定供应商提供的产品的平均价格。它与上面的SELECT语句相同,但使用了DISTINCT参数,因此平均值只考虑各个不同的价格:

输入▼


SELECT AVG(DISTINCT prod_price) AS avg_price FROM Products

WHERE vend_id = 'DLL01';


分析▼


使用了DISTINCT后,会排除相同的价格。

 

9.3 组合聚集函数

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


分析▼


这里用单条SELECT语句执行了4个聚集计算,返回4个值(Products表中物品的数目,产品价格的最高值、最低值以及平均值)。

第10课 分组数据

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

 

10.1 数据分组

如果要返回每个供应商提供的产品数目,该怎么办?或者返回只提供一项产品的供应商的产品,或者返回提供10个以上产品的供应商的产品, 怎么办?这就是分组大显身手的时候了。使用分组可以将数据分为多个逻辑组,对每个组进行聚集计算。

 

10.2 创建分组

分组是使用SELECT语句的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 |
+---------+-----------+

分析▼


上面的SELECT语句指定了两个列:vend_id包含产品供应商的ID,num_prods为计算字段,GROUP BY子句指示DBMS 按vend_id排序并分组数据。这就会对每个vend_id而不是整个表计算num_prods—次。

 

10.3 过滤分组

除了能用GROUP BY分组数据外,SQL还允许过滤分组,规定包括哪些分组,排除哪些分组。例如,你可能想要列出至少有两个订单的所有顾客。

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

输入▼


SELECT cust_id,COUNT(*) AS orders FROM Orders

GROUP BY cust_id HAVING COUNT(*) >= 2;


分析▼


这条SELECT语句的前三行类似于上面的语句。最后一行增加了HAVING子句,它过滤COUNT(*) >= 2(两个以上订单)的那些分组。

第11课 使用子查询

这一课介绍什么是子查询,如何使用它们。

 

11.1 子查询

SQL还允许创建子查询(subquery),即嵌套在其他查询中的查询。为什么要这样做呢?理解这个概念的最好方法是考察几个例子。

 

11.2 利用子查询进行过滤

订单存储在两个表中。每个订单包含订单编号、客户 ID、订单日期,在Orders表中存储为一行。各订单的物品存储在相关的Orderltems表中。Orders表不存储顾客信息,只存储顾客ID。顾客的实际信息存储在Customers表中。

现在,假如需要列出订购物品RGAN01的所有顾客,应该怎样检索?下面列出具体的步骤。

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

上述每个步骤都可以单独作为一个查询来执行。可以把一条SELECT语句返回的结果用于另一条SELECT语句的WHERE子句。也可以使用子查询来把3个查询组合成一条语句。使用子查询的语句如下所示:

输入▼


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'));


分析▼


为了执行上述SELECT语句,DBMS实际上必须执行三条SELECT语句。最里边的子查询返回订单号列表,此列表用于其外面的子查询的WHERE子句。外面的子查询返回顾客ID列表,此顾客ID列表用于最外层查询的WHERE子句。最外层查询返回所需的数据。

第12课 联结表

这一课会介绍什么是联结,为什么使用联结,如何编写使用联结的SELECT语句。

 

12.1 联结

SQL最强大的功能之一就是能在数据查询的执行中联结(join)表。联结是利用SQL的SELECT能执行的最重要的操作,很好地理解联结及其语法是学习SQL的极为重要的部分。

 

12.1.1 关系表

有一个包含产品目录的数据库表,其中每类物品占一行。对于每一种物品,要存储的信息包括产品描述、价格,以及生产该产品的供应商。

现在有同一供应商生产的多种物品,那么在何处存储供应商名、地址、联系方法等供应商信息呢?将这些数据与产品信息分开存储的理由是:

  • 同一供应商生产的每个产品,其供应商信息都是相同的,对每个产品重复此信息既浪费时间又浪费存储空间;
  • 如果供应商信息发生变化,例如供应商迁址或电话号码变动,只需修改一次即可;
  • 如果有重复数据(即每种产品都存储供应商信息),则很难保证每次输入该数据的方式都相同。不一致的数据在报表中就很难利用。

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

在这个例子中可建立两个表:一个存储供应商信息,另一个存储产品信息。Vendors表包含所有供应商信息,每个供应商占一行,具有唯一的标识。

Products表只存储产品信息,除了存储供应商ID (Vendors表的主键)外,它不存储其他有关供应商的信息。Vendors表的主键将Vendors表 与Products表关联,利用供应商ID能从Vendors表中找出相应供应商的详细信息。

 

12.1.2 为什么使用联结

如前所述,将数据分解为多个表能更有效地存储,更方便地处理,并且可伸缩性更好。但这些好处是有代价的。如果数据存储在多个表中,怎样用一条SELECT语句就检索出数据呢?

答案是使用联结。简单说,联结是一种机制,用来在一条SELECT语句中关联表,因此称为联结。使用特殊的语法,可以联结多个表返回一组输出,联结在运行时关联表中正确的行。

 

12.2 创建联结

创建联结非常简单,指定要联结的所有表以及关联它们的方式即可。请看下面的例子:

输入▼


SELECT vend_name, prod_name, prod_price FROM Vendors, Products

WHERE Vendors.vend_id = Products.vend_id;


分析▼


SELECT语句与前面所有语句一样指定要检索的列。这里最大的差别是所指定的两列(prod_name和prod_price)在一个表中,而第三列(vend_name)在另一个表中。

这条语句的FROM子句列出了两个表:Vendors和Products。它们就是这条SELECT语句联结的两个表的名字。这两个表用WHERE子句正确地联结,WHERE子句指示DBMS将Vendors表中的vend_id与Products表中的vend_id匹配起来。

可以看到,要匹配的两列指定为Vendors.vend_id和Products.vend_id。这里需要这种完全限定列名,如果只给出vend_id,DBMS就不知道指的是哪一个(每个表中有一个)。

 

12.3 联结多个表

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;


分析▼


这个例子显示订单20007中的物品。订单物品存储在OrderItems表中。每个产品按其产品ID存储,它引用Products表中的产品。这些产品通过供应商ID联结到Vendors表中相应的供应商,供应商ID存储在每个产品的记录中。这里的FROM子句列出三个表,WHERE子句定义这两个联结条件, 而第三个联结条件用来过滤出订单20007中的物品。

第13课 创建高级联结

本课讲解另外一些联结(包括它们的含义和使用方法),介绍如何使用表别名,如何对被联结的表使用聚集函数。

 

13.1 使用表别名

请看下面的SELECT语句。它与前一课例子中所用的语句基本相同,但改成了使用表别名:

输入▼


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';


分析▼


可以看到,FROM子句中的三个表全都有别名。Customers AS C使用C作为Customers的别名,如此等等。这样,就可以使用省略的C而不用全名Customers。在这个例子中,表别名只用于WHERE子句。其实它不仅能用于WHERE子句,还可以用于SELECT的列表、ORDER BY子句以及其他语句部分。

 

13.2 使用不同类型的联结

迄今为止,我们使用的只是内联结或等值联结的简单联结。现在来看三种其他联结:自联结(self-join)、自然联结(natura join)和外联结(outer join)。由于篇幅原因,这里不再过多介绍。

第14课 组合查询

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

 

14.1 组合查询

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

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

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

 

14.2 创建组合查询

可用UNION操作符来组合数条SQL查询。利用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';


分析▼


这条语句由前面的两条SELECT语句组成,之间用UNION关键字分隔。UNION指示DBMS执行这两条SELECT语句,并把输出组合成一个查询结果集。

第15课 插入数据

这一课介绍如何利用SQL的INSERT语句将数据插入表中。

 

15.1 数据插入

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

输入▼


INSERT INTO Customers VALUES('1000000006', 'Toy Land', '123 Any Street', 'New York',

'NY', '11111', 'USA', NULL, NULL);


分析▼


这个例子将一个新顾客插入到Customers表中。存储到表中每一列的数据在VALUES子句中给出,必须给每一列提供一个值。如果某列没有值,如上面的cust_contact和cust_email列,则应该使用NULL值(假定表允许对该列指定空值)。各列必须以它们在表定义中出现的次序填充。

虽然这种语法很简单,但并不安全,应该尽量避免使用。上面的SQL语句高度依赖于表中列的定义次序,还依赖于其容易获得的次序信息。即使可以得到这种次序信息,也不能保证各列在下一次表结构变动后保持完全相同的次序。因此,编写依赖于特定列次序的SQL语句是很不安全的,这样做迟早会出问题。

编写INSERT语句的更安全(不过更烦琐)的方法如下:

输入▼


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);


分析▼


这个例子与前一个INSERT语句的工作完全相同,但在表名后的括号里明确给出了列名。在插入行时,DBMS将用VALUES列表中的相应值填入列表中的对应项。因为提供了列名,VALUES必须以其指定的次序匹配指定的列名,不一定按各列出现在表中的实际次序。其优点是,即使表的结构改变,这条INSERT语句仍然能正确工作。

 

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

要将一个表的内容复制到一个全新的表(运行中创建的表),可以使用SELECT INTO语句。MariaDB、MySQL、Oracle、PostgreSQL和SQLite使用的语法如下:

输入▼


CREATE TABLE CustCopy AS SELECT * FROM Customers;


分析▼


这条SELECT语句创建一个名为CustCopy的新表,并把Customers表的整个内容复制到新表中。因为这里使用的是SELECT *,所以将在CustCopy表中创建(并填充)与Customers表的每一列相同的列。要想只复制部分的列,可以明确给出列名,而不是使用*通配符。

第16课 更新和删除数据

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

 

16.1 更新数据

更新(修改)表中的数据,可以使用UPDATE语句。例子如下:

输入▼


UPDATE Customers SET cust_contact = 'Sam Roberts', cust_email = 'sam@toyland.cxx'

WHERE cust_id = '1000000006';


分析▼


UPDATE语句以WHERE子句结束,它告诉DBMS更新哪一行。没有WHERE子句,DBMS将会用这个电子邮件地址更新Customers表中的所有行,这不是我们希望的。

 

16.2 删除数据

从一个表中删除数据,使用DELETE语句。下面的语句从Customers表中删除一行:

输入▼


DELETE FROM Customers WHERE cust_id = '1000000006';


分析▼

这条语句很容易理解。DELETE FROM要求指定从中删除数据的表名,WHERE子句过滤要删除的行。在这个例子中,只删除顾客1000000006。如果省略WHERE子句,它将删除表中每个顾客。

第17课 创建和操纵表

这一课讲授创建、更改和删除表的基本知识。

 

17.1 创建表

用程序创建表,可以使用SQL的CREATE TABLE语句。下面的SQL语句创建本书中所用的Products表:

输入▼


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 DEFAULT 3,

proddesc VARCHAR(1000) NULL

);


分析▼

从上面的例子可以看到,表名紧跟CREATE TABLE关键字。实际的表定义(所有列)括在圆括号之中,各列之间用逗号分隔。有一列的描述增加了DEFAULT 3,指示DBMS,如果不给出价格则使用价格3。

 

17.2 更新表

更新表定义,可以使用ALTER TABLE语句。因为给已有表增加列可能是所有DBMS都支持的唯一操作,所以我们举个这样的例子:

输入▼


ALTER TABLE Vendors ADD vend_phone CHAR(20);


分析▼

这条语句给Vendors表增加一个名为vend_phone的列,其数据类型为CHAR。删除列,就使用DROP COLUMN col_name即可。

 

17.3 删除表

删除表(删除整个表而不是其内容)非常简单,使用DROP TABLE语句即可:

输入▼


DROP TABLE CustCopy;


分析▼

这条语句删除CustCopy表。删除表没有确认,也不能撤销,执行这条语句将永久删除该表。

使用关系规则防止意外删除
许多DBMS允许强制实施有关规则,防止删除与其他表相关联的表。在实施这些规则时,如果对某个表发布一条DROP TABLE语句,且该表是某个关系的组成部分,则DBMS将阻止这条语句执行,直到该关系被删除为止。

[SQL必知必会] 读书笔记的更多相关文章

  1. 读书笔记汇总 - SQL必知必会(第4版)

    本系列记录并分享学习SQL的过程,主要内容为SQL的基础概念及练习过程. 书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL i ...

  2. 读书笔记--SQL必知必会18--视图

    读书笔记--SQL必知必会18--视图 18.1 视图 视图是虚拟的表,只包含使用时动态检索数据的查询. 也就是说作为视图,它不包含任何列和数据,包含的是一个查询. 18.1.1 为什么使用视图 重用 ...

  3. 读书笔记--SQL必知必会--建立练习环境

    书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL in 10 Minutes - Fourth Edition> MyS ...

  4. 读书笔记--SQL必知必会12--联结表

    12.1 联结 联结(join),利用SQL的SELECT在数据查询的执行中联结表. 12.1.1 关系表 关系数据库中,关系表的设计是把信息分解成多个表,一类数据一个表,各表通过某些共同的值互相关联 ...

  5. SQL语法语句总结(《SQL必知必会》读书笔记)

    一.SQL语句语法 ALTER TABLE ALTER TABLE 用来更新已存在表的结构. ALTER TABLE tablename (ADD|DROP column datatype [NULL ...

  6. 《mysql必知必会》读书笔记--存储过程的使用

    以前对mysql的认识与应用只是停留在增删改查的阶段,最近正好在学习mysql相关内容,看了一本书叫做<MySQL必知必会>,看了之后对MySQL的高级用法有了一定的了解.以下内容只当读书 ...

  7. MySQL必知必会1-20章读书笔记

    MySQL备忘 目录 目录 使用MySQL 检索数据 排序检索数据 过滤数据 数据过滤 用通配符进行过滤 用正则表达式进行搜索 创建计算字段 使用数据处理函数 数值处理函数 汇总数据 分组数据 使用子 ...

  8. 《SQL必知必会》学习笔记二)

    <SQL必知必会>学习笔记(二) 咱们接着上一篇的内容继续.这一篇主要回顾子查询,联合查询,复制表这三类内容. 上一部分基本上都是简单的Select查询,即从单个数据库表中检索数据的单条语 ...

  9. 【SQL必知必会笔记(3)】SELECT语句的WHERE子句数据过滤操作

    上个笔记主要介绍了利用SELECT语句检索单个/多个/所有列,并利用DISTINCT关键字检索具有唯一性的值.利用LIMIT/OFFSET子句限制结果:以及利用ORDER BY子句排序检索出的数据,主 ...

  10. 【SQL必知必会笔记(2)】检索数据、排序检索数据

    上个笔记中介绍了一些关于数据库.SQL的基础知识,并且创建我们后续练习所需的数据库.表以及表之间的关系,从本文开始进入我们的正题:SQL语句的练习. 文章目录 1.检索数据(SELECT语句) 1.1 ...

随机推荐

  1. 运算符、流程控制、while循环

    运算符: 1. 算术运算符: “ + ”.“ - ” .“ * ” .“ / ” 分别为加.减.乘.除. # % 是“取模运算符”,就是返回除法的余数.eg. a = 3, b=5, b % a 就是 ...

  2. 基于jQuery的图片加载loading效果插件

    基于jQuery的图片加载loading效果插件 图片loading的效果是网页中比较常见的,尤其是对大图片,loading效果让用户能够明白图片加载的过程. 实现思路也是比较简单的: $.fn.Lo ...

  3. css3自定义流动条

    <style> .item { height: 180px; overflow: auto; width: 180px; float: left; margin: 11px; box-sh ...

  4. Memcached集群之通过Repcached实现主从复制(待实践)

    暂时了解有这东西,不搭建了. Mamcached可以通过Repcached实现主从复制.有以下优缺点: 优点: 1.能够实现Cache的冗余功能 2.主从之间可以互相读写(亮点) 参考: http:/ ...

  5. Symantec Backup Exec部署手册

    转载  http://xiaxiaoguo.blog.51cto.com/858884/402810 Symantec Backup Exec部署手册 目录 1.Backup Exec 12.5安装. ...

  6. [Debug] Inspect and Style an Element in DevTools that Normally Disappears when Inactive

    It’s handy to inspect an element in your browser’s DevTools when you need to experiment or tweak it’ ...

  7. QlikView格式化某一个单元格

    QlikView中能够创建透视表和垂直表,或者一般的Table.假如有的时候须要某一个单元格的样式和其它单元格不一样.颜色或者边框宽度等.能够通过下面方式实现: 工具栏里面有个button叫:Desi ...

  8. C++之new和malloc差别

         在C++程序猿面试中.非常easy被问到new 和 malloc的差别.偶尔在quora上逛.看到Robert Love的总结.才发现自己仅仅知道里面的一两项就沾沾自喜,从来没有像这位大牛一 ...

  9. 分析Linux内核的启动过程

    第一章 环境 Ubuntu 14.10 Linux Kernel 3.18.6 第二章 代码及调试过程 环境搭建与内核准备: cd ~/LinuxKernel/ wget https://www.ke ...

  10. dumpdecrypted进行砸壳

    1.下载源码git clone git://github.com/stefanesser/dumpdecrypted/ 2.进行编译生成 dumpdecrypted.dylibmake 3.将dump ...