目录

1、基于SELECT语句的1.1列中的查询1.2查询在为表中的所有列表1.3设置别名1.4常量的查询1.5中删除重复行1.6根据WHERE语句记录1.7注释的写入方法2、算术运算符和比较运算符2.1算术运算符2.2 NULL2.3比较运算符2.4字符串不相等时,注意事项2.5为NULL设置比较运算符3、逻辑运算符3.1 NOT运算符3.4本文档介绍如何使用SELECT语句查询SQL以创建、更新和删除表。作业生成的Product表中的数据。

这里使用的 SELECT 语句是 SQL 最基本也是最重要的语句。

请大家在实际运行本文中的 SELECT 语句时,亲身体验一下其书写方法和执行结果。

执行查询操作时可以指定想要查询数据的条件(查询条件)。查询时可以指定一个或多个查询条件,例如“某一列等于这个值”“某一列计算之后的值大于这个值”等。

一、SELECT 语句基础

本节重点

使用 SELECT 语句从表中选取数据。

为列设定显示用的别名。

SELECT 语句中可以使用常数或者表达式。

通过指定 DISTINCT 可以删除重复的信息。

SQL 语句中可以使用注释。

可以通过 WHERE 语句从表中选取出符合查询条件的数据。

1.1 列的查询

从表中选取数据时需要使用 SELECT 语句,也就是只从表中选出(SELECT)必要数据的意思。通过 SELECT 语句查询并选取出必要数据的过程称为匹配查询或查询(query)。

SELECT 语句是 SQL 语句中使用最多的最基本的 SQL 语句。掌握了 SELECT 语句,距离掌握 SQL 语句就不远了。

SELECT 语句的基本语法如下所示。

语法 1 基本的 SELECT 语句

SELECT <列名>,…… FROM <表名>;

该 SELECT 语句包含了 SELECT 和 FROM 两个子句(clause)。子句是 SQL 语句的组成要素,是以 SELECT 或者 FROM 等作为起始的短语。

SELECT 子句中列举了希望从表中查询出的列的名称,而 FROM 子句则指定了选取出数据的表的名称。

接下来,我们尝试从 SQL 如何对表进行创建、更新和删除操作 中创建出的 Product(商品)表中,查询出图 1 所示的 product_id(商品编号)列、product_name(商品名称)列和 purchase_price(进货单价)列。

图 1 查询出 Product 表中的列

对应的 SELECT 语句请参见代码清单 1,该语句正常执行的结果如执行结果所示 [1]。

代码清单 1 从 Product 表中输出 3 列

SELECT product_id, product_name, purchase_price FROM Product;

执行结果:

product_id | product_name | purchase_price-----------+--------------+---------------0001 | T恤衫 | 5000002 | 打孔器 | 3200003 | 运动T恤 | 28000004 | 菜刀 | 28000005 | 高压锅 | 50000006 | 叉子 |0007 | 擦菜板 | 7900008 | 圆珠笔 |

SELECT 语句第一行的 SELECT product_id, product_name, purchase_price 就是 SELECT 子句。查询出的列的顺序可以任意指定。

查询多列时,需要使用逗号进行分隔。查询结果中列的顺序和 SELECT 子句中的顺序相同 [2]。

1.2 查询出表中所有的列

想要查询出全部列时,可以使用代表所有列的星号(*)。

语法 2 查询全部的列

SELECT * FROM <表名>;

例如,查询 Product 表中全部列的语句如代码清单 2 所示。

代码清单 2 输出 Product 表中全部的列

SELECT * FROM Product;

得到的结果和代码清单 3 中的 SELECT 语句的结果相同。

代码清单 3 与代码清单 2 具有相同含义的 SELECT 语句

SELECT product_id, product_name, product_type, sale_price, purchase_price, regist_date FROM Product;

执行结果如下所示:

product_id | product_name | product_type | sale_price | purchase_price | regist_date------------+--------------+--------------+------------+----------------+------------ 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 0003 | 运动T恤 | 衣服 | 4000 | 2800 | 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 0006 | 叉子 | 厨房用具 | 500 | | 2009-09-20 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 0008 | 圆珠笔 | 办公用品 | 100 | | 2009-11-11

法则 1

星号(*)代表全部列的意思。

但是,如果使用星号的话,就无法设定列的显示顺序了。这时就会按照 CREATE TABLE 语句的定义对列进行排序。

专栏

随意使用换行符

SQL 语句使用换行符或者半角空格来分隔单词,在任何位置进行分隔都可以,即使像下面这样通篇都是换行符也不会影响 SELECT 语句的执行。

但是这样可能会由于看不清楚而出错。原则上希望大家能够以子句为单位进行换行(子句过长时,为方便起见可以换行)。

SELECT*FROMProduct;

另外,像下面这样插入空行(无任何字符的行)会造成执行错误,请特别注意。

SELECT * FROM Product;

1.3 为列设定别名

SQL 语句可以使用 AS 关键字为列设定别名。请参见代码清单 4。

代码清单 4 为列设定别名

SELECT product_id AS id, product_name AS name, purchase_price AS price FROM Product;

执行结果:

id | name | price------+---------+-------0001 | T恤衫 | 5000002 | 打孔器 | 3200003 | 运动T恤 | 28000004 | 菜刀 | 28000005 | 高压锅 | 50000006 | 叉子 |0007 | 擦菜板 | 7900008 | 圆珠笔 |

别名可以使用中文,使用中文时需要用 双引号(") 括起来 [3]。请注意不是单引号(')。设定中文别名的 SELECT 语句请参见代码清单 5。

代码清单 5 设定中文别名

SELECT product_id AS "商品编号", product_name AS "商品名称", purchase_price AS "进货单价" FROM Product;

执行结果:

商品编号 | 商品名称 | 进货单价----------+----------+---------0001 | T恤衫 | 5000002 | 打孔器 | 3200003 | 运动T恤 | 28000004 | 菜刀 | 28000005 | 高压锅 | 50000006 | 叉子 |0007 | 擦菜板 | 7900008 | 圆珠笔 |

通过执行结果来理解就更加容易了。像这样使用别名可以让 SELECT 语句的执行结果更加容易理解和操作。

法则 2

设定汉语别名时需要使用双引号(")括起来。

1.4 常数的查询

SELECT 子句中不仅可以书写列名,还可以书写常数。

代码清单 6 中的 SELECT 子句中的第一列 '商品' 是字符串常数,第 2 列 38 是数字常数,第 3 列 '2009-02-24' 是日期常数,它们将与 product_id 列和 product_name 列一起被查询出来。[4]

代码清单 6 查询常数

SELECT '商品' AS string, 38 AS number, '2009-02-24' AS date, product_id, product_name FROM Product;

执行结果:

string | number | date | product_id | product_name---------+----------+-------------+------------+-------------- 商品 | 38 | 2009-02-24 | 0001 | T恤衫 商品 | 38 | 2009-02-24 | 0002 | 打孔器 商品 | 38 | 2009-02-24 | 0003 | 运动T恤 商品 | 38 | 2009-02-24 | 0004 | 菜刀 商品 | 38 | 2009-02-24 | 0005 | 高压锅 商品 | 38 | 2009-02-24 | 0006 | 叉子 商品 | 38 | 2009-02-24 | 0007 | 擦菜板 商品 | 38 | 2009-02-24 | 0008 | 圆珠笔

如上述执行结果所示,所有的行中都显示出了 SELECT 子句中的常数。

此外,SELECT 子句中除了书写常数,还可以书写计算式。我们将在下一节中学习如何书写计算式。

1.5 从结果中删除重复行

想知道 Product 表中保存了哪些商品种类(product_type)时,如果能像图 2 那样删除重复的数据该有多好啊。

图 2 除去重复数据后的商品种类

如上所示,想要删除重复行时,可以通过在 SELECT 子句中使用 DISTINCT 来实现(代码清单 7)。

代码清单 7 使用 DISTINCT 删除 product_type 列中重复的数据

SELECT DISTINCT product_type FROM Product;

执行结果:

product_type--------------- 厨房用具 衣服 办公用品

法则 3

在 SELECT 语句中使用 DISTINCT 可以删除重复行。

在使用 DISTINCT 时,NULL 也被视为一类数据。NULL 存在于多行中时,也会被合并为一条 NULL 数据。

对含有 NULL 数据的 purchase_price(进货单价)列使用 DISTINCT 的 SELECT 语句请参见代码清单 8。

除了两条 2800 的数据外,两条 NULL 的数据也被合并为一条。

代码清单 8 对含有 NULL 数据的列使用 DISTINCT 关键字

SELECT DISTINCT purchase_price FROM Product;

执行结果:

DISTINCT 也可以像代码清单 9 那样在多列之前使用。此时,会将多个列的数据进行组合,将重复的数据合并为一条。

代码清单 9 中的 SELECT 语句,对 product_type(商品种类)列和 regist_date(登记日期)列的数据进行组合,将重复的数据合并为一条。

代码清单 9 在多列之前使用 DISTINCT

SELECT DISTINCT product_type, regist_date FROM Product;

执行结果:

product_type | regist_date--------------+------------ 衣服 | 2009-09-20 办公用品 | 2009-09-11 办公用品 | 2009-11-11 衣服 | 厨房用具 | 2009-09-20 厨房用具 | 2009-01-15 厨房用具 | 2008-04-28

如上述执行结果所示,product_type 列为 '厨房用具',同时 regist_date 列为 '2009-09-20' 的两条数据被合并成了一条。

DISTINCT 关键字只能用在第一个列名之前。因此,请大家注意不能写成 regist_date, DISTINCT product_type。

1.6 根据 WHERE 语句来选择记录

前面的例子都是将表中存储的数据全都选取出来,但实际上并不是每次都需要选取出全部数据,大部分情况都是要选取出满足“商品种类为衣服”“销售单价在 1000 元以上”等某些条件的数据。

SELECT 语句通过 WHERE 子句来指定查询数据的条件。在 WHERE 子句中可以指定“某一列的值和这个字符串相等”或者“某一列的值大于这个数字”等条件。

执行含有这些条件的 SELECT 语句,就可以查询出只符合该条件的记录了。[5]

在 SELECT 语句中使用 WHERE 子句的语法如下所示。

语法 3 SELECT 语句中的 WHERE 子句

SELECT <列名>, …… FROM <表名> WHERE <条件表达式>;

图 3 显示了从 Product 表中选取商品种类(product_type)为 '衣服' 的记录。

图 3 选取商品种类为’衣服’的记录

从被选取的记录中还可以查询出想要的列。为了更加容易理解,我们在查询 product_type 列的同时,把 product_name 列也读取出来。

SELECT 语句请参见代码清单 10。

代码清单 10 用来选取 product_type 列为 '衣服' 的记录的 SELECT 语句

SELECT product_name, product_type FROM Product WHERE product_type = '衣服';

执行结果:

product_name | product_type--------------+-------------- T恤衫 | 衣服 运动T恤 | 衣服

WHERE 子句中的“product_type = '衣服'”就是用来表示查询条件的表达式(条件表达式)。

等号是比较两边的内容是否相等的符号,上述条件就是将 product_type 列的值和 '衣服' 进行比较,判断是否相等。Product 表的所有记录都会被进行比较。

接下来会从查询出的记录中选取出 SELECT 语句指定的 product_name 列和 product_type 列,如执行结果所示,也就是首先通过 WHERE 子句查询出符合指定条件的记录,然后再选取出 SELECT 语句指定的列(图 4)。

图 4 选取行之后,再输出列

代码清单 10 中的语句为了确认选取出的数据是否正确,通过 SELECT 子句把作为查询条件的 product_type 列也选取出来了,其实这并不是必须的。

如果只想知道商品名称的话,可以像代码清单 11 那样只选取出 product_name 列。

代码清单 11 也可以不选取出作为查询条件的列

SELECT product_name FROM Product WHERE product_type = '衣服';

执行结果:

product_name--------------- T恤衫 运动T恤

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

代码清单 12 随意改变子句的书写顺序会造成错误

SELECT product_name, product_type WHERE product_type = '衣服' FROM Product;

执行结果(PostgreSQL):

ERROR: "FROM"或者其前后有语法错误第3行: FROM Product;

法则 4

WHERE 子句要紧跟在 FROM 子句之后。

1.7 注释的书写方法

最后给大家介绍一下注释的书写方法。注释是 SQL 语句中用来标识说明或者注意事项的部分。

注释对 SQL 的执行没有任何影响。因此,无论是英文字母还是汉字都可以随意使用。

注释的书写方法有如下两种。

  • 单行注释
  • 书写在“–”之后,只能写在同一行。[6]
  • 多行注释
  • 书写在“/*”和“*/”之间,可以跨多行。

实际的示例请参见代码清单 13 和代码清单 14。

代码清单 13 单行注释的使用示例

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

代码清单 14 多行注释的使用示例

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

任何注释都可以插在 SQL 语句中(代码清单 15、代码清单 16)。

代码清单 15 在 SQL 语句中插入单行注释

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

代码清单 16 在 SQL 语句中插入多行注释

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

这些 SELECT 语句的执行结果与没有使用注释时完全一样。

注释能够帮助阅读者更好地理解 SQL 语句,特别是在书写复杂的 SQL 语句时,希望大家能够尽量多加简明易懂的注释。

注释不仅可以写在 SELECT 语句中,而且可以写在任何 SQL 语句当中,写多少都可以。

法则 5

注释是 SQL 语句中用来标识说明或者注意事项的部分。

分为单行注释和多行注释两种。

二、算术运算符和比较运算符

本节重点

运算符就是对其两边的列或者值进行运算(计算或者比较大小等)的符号。

使用算术运算符可以进行四则运算。

括号可以提升运算的优先顺序(优先进行运算)。

包含 NULL 的运算,其结果也是 NULL。

比较运算符可以用来判断列或者值是否相等,还可以用来比较大小。

判断是否为 NULL,需要使用 IS NULL 或者 IS NOT NULL 运算符。

2.1 算术运算符

SQL 语句中可以使用计算表达式。代码清单 17 中的 SELECT 语句,把各个商品单价的 2 倍(sale_price 的 2 倍)以 "sale_price_x2" 列的形式读取出来。

代码清单 17 SQL 语句中也可以使用运算表达式

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 菜刀 | 3000 | 6000 高压锅 | 6800 | 13600 叉子 | 500 | 1000 擦菜板 | 880 | 1760 圆珠笔 | 100 | 200

sale_price_x2 列中的 sale_price * 2 就是计算销售单价的 2 倍的表达式。

以 product_name 列的值为 'T 恤衫' 的记录行为例,sale_price 列的值 1000 的 2 倍是 2000,它以 sale_price_x2 列的形式被查询出来。

同样,'打孔器' 记录行的值 500 的 2 倍 1000,'运动 T 恤' 记录行的值 4000 的 2 倍 8000,都被查询出来了。运算就是这样以行为单位执行的。

SQL 语句中可以使用的四则运算的主要运算符如表 1 所示。

表 1 SQL 语句中可以使用的四则运算的主要运算符

含义

运算符

加法运算

+

减法运算

乘法运算

*

除法运算

/

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

加法运算符(+)前后如果是数字或者数字类型的列名的话,就会返回加法运算后的结果。SQL 中除了算术运算符之外还有其他各种各样的运算符。

法则 6

SELECT 子句中可以使用常数或者表达式。

当然,SQL 中也可以像平常的运算表达式那样使用括号 ()。括号中运算表达式的优先级会得到提升,优先进行运算。

例如在运算表达式 (1 + 2) * 3 中,会先计算 1 + 2 的值,然后再对其结果进行 * 3 运算。

括号的使用并不仅仅局限于四则运算,还可以用在 SQL 语句的任何表达式当中。具体的使用方法今后会慢慢介绍给大家。

2.2 需要注意 NULL

像代码清单 2-17 那样,SQL 语句中进行运算时,需要特别注意含有 NULL 的运算。请大家考虑一下在 SQL 语句中进行如下运算时,结果会是什么呢?

A:5 + NULL

B:10 – NULL

C:1 * NULL

D:4 / NULL

E:NULL / 9

F:NULL / 0

正确答案全部都是 NULL。大家可能会觉得奇怪,为什么会这样呢?实际上所有包含 NULL 的计算,结果肯定是 NULL。即使像 F 那样用 NULL 除以 0 时这一原则也适用。

通常情况下,类似 5/0 这样除数为 0 的话会发生错误,只有 NULL 除以 0 时不会发生错误,并且结果还是 NULL。

尽管如此,很多时候我们还是希望 NULL 能像 0 一样,得到 5 + NULL = 5 这样的结果。

不过也不要紧,SQL 中也为我们准备了可以解决这类情况的方法(将会在 SQL 常用的函数 中进行介绍)。

专栏

FROM 子句真的有必要吗?

在第 1 节中我们介绍过 SELECT 语句是由 SELECT 子句和 FROM 子句组成的。

可实际上 FROM 子句在 SELECT 语句中并不是必不可少的,只使用 SELECT 子句进行计算也是可以的。

代码清单 A 只包含 SELECT 子句的 SELECT 语句

SQL Server PostgreSQL MySQL

SELECT (100 + 200) * 3 AS calculation;

执行结果:

calculation————- 900

实际上,通过执行 SELECT 语句来代替计算器的情况基本上是不存在的。不过在极少数情况下,还是可以通过使用没有 FROM 子句的 SELECT 语句来实现某种业务的。

例如,不管内容是什么,只希望得到一行临时数据的情况。

但是也存在像 Oracle 这样不允许省略 SELECT 语句中的 FROM 子句的 RDBMS,请大家注意。

在 Oracle 中,FROM 子句是必需的,这种情况下可以使用 DUAL 这个临时表。另外,DB2 中可以使用 SYSIBM.SYSDUMMY1 这个临时表。

2.3 比较运算符

在第 1 节学习 WHERE 子句时,我们使用符号 = 从 Product 表中选取出了商品种类(product_type)为字符串 '衣服' 的记录。

下面让我们再使用符号 = 选取出销售单价(sale_price)为 500 元(数字 500)的记录(代码清单 18)。

代码清单 18 选取出 sale_price 列为 500 的记录

SELECT product_name, product_type FROM Product WHERE sale_price = 500;

执行结果:

product_name | product_type---------------+-------------- 打孔器 | 办公用品 叉子 | 厨房用具

像符号 = 这样用来比较其两边的列或者值的符号称为比较运算符,符号 = 就是比较运算符。在 WHERE 子句中通过使用比较运算符可以组合出各种各样的条件表达式。

接下来,我们使用“不等于”这样代表否定含义的比较运算符 <> [7],选取出 sale_price 列的值不为 500 的记录(代码清单 19)。

代码清单 19 选取出 sale_price 列的值不是 500 的记录

SELECT product_name, product_type FROM Product WHERE sale_price <> 500;

执行结果:

product_name | product_type---------------+-------------- T恤衫 | 衣服 运动T恤 | 衣服 菜刀 | 厨房用具 高压锅 | 厨房用具 擦菜板 | 厨房用具 圆珠笔 | 办公用品

SQL 中主要的比较运算符如表 2 所示,除了等于和不等于之外,还有进行大小比较的运算符。

表 2 比较运算符

运算符

含义

=

和 ~ 相等

<>

和 ~ 不相等

>=

大于等于 ~

>

大于 ~

<=

小于等于 ~

<

小于 ~

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

例如,从 Product 表中选取出销售单价(sale_price) 大于等于 1000 元的记录,或者登记日期(regist_date)在 2009 年 9 月 27 日 之前的记录,可以使用比较运算符 >= 和 <,在 WHERE 子句中生成如下条件表达式(代码清单 20、代码清单 21)。

代码清单 20 选取出销售单价大于等于 1000 元的记录

SELECT product_name, product_type, sale_price FROM Product WHERE sale_price >= 1000;

执行结果:

product_name | product_type | sale_price---------------+--------------+-------------- T恤衫 | 衣服 | 1000 运动T恤 | 衣服 | 4000 菜刀 | 厨房用具 | 3000 高压锅 | 厨房用具 | 6800

代码清单 21 选取出登记日期在 2009 年 9 月 27 日 之前的记录

SELECT product_name, product_type, regist_date FROM Product WHERE regist_date < '2009-09-27';

执行结果:

product_name | product_type | regist_date---------------+--------------+----------- T恤衫 | 衣服 | 2009-09-20 打孔器 | 办公用品 | 2009-09-11 菜刀 | 厨房用具 | 2009-09-20 高压锅 | 厨房用具 | 2009-01-15 叉子 | 厨房用具 | 2009-09-20 擦菜板 | 厨房用具 | 2008-04-28

小于某个日期就是在该日期之前的意思。想要实现在某个特定日期(包含该日期)之后的查询条件时,可以使用代表大于等于的 >= 运算符。

另外,在使用大于等于(>=)或者小于等于(<=)作为查询条件时,一定要注意不等号(<、>)和等号(=)的位置不能颠倒。

一定要让不等号在左,等号在右。如果写成(=<)或者(=>)就会出错。当然,代表不等于的比较运算符也不能写成(><)。

法则 7

使用比较运算符时一定要注意不等号和等号的位置。

除此之外,还可以使用比较运算符对计算结果进行比较。代码清单 22 在 WHERE 子句中指定了销售单价(sale_price)比进货单价(purchase_price)高出 500 元以上的条件表达式。

为了判断是否高出 500 元,需要用 sale_price 列的值减去 purchase_price 列的值。

代码清单 22 WHERE 子句的条件表达式中也可以使用计算表达式

SELECT product_name, sale_price, purchase_price FROM Product WHERE sale_price - purchase_price >= 500;

执行结果:

product_name | sale_price | purchase_price---------------+-------------+--------------- T恤衫 | 1000 | 500 运动T恤 | 4000 | 2800 高压锅 | 6800 | 5000

2.4 对字符串使用不等号时的注意事项

对字符串使用大于等于或者小于等于不等号时会得到什么样的结果呢?接下来我们使用表 3 中的 Chars 表来进行确认。

虽然该表中存储的都是数字,但 chr 是字符串类型(CHAR 类型)的列。

表 3 Chars 表

chr(字符串类型)

1

2

3

10

11

222

可以使用代码清单 23 中的 SQL 语句来创建 Chars 表。

代码清单 23 创建 Chars 表并插入数据

-- DDL :创建表CREATE TABLE Chars(chr CHAR(3) NOT NULL,PRIMARY KEY (chr));

SQL Server PostgreSQL

-- DML :插入数据BEGIN TRANSACTION; -------------① INSERT INTO Chars VALUES ('1');INSERT INTO Chars VALUES ('2');INSERT INTO Chars VALUES ('3');INSERT INTO Chars VALUES ('10');INSERT INTO Chars VALUES ('11');INSERT INTO Chars VALUES ('222'); COMMIT;

特定的 SQL

代码清单 23 中的 DML 语句根据 DBMS 的不同而略有差异。

在 MySQL 中执行该语句时,请大家把 ① 的部分改成“START TRANSACTION;”。

在 Oracle 和 DB2 中执行时不需用到 ① 的部分,请删除。

那么,对 Chars 表执行代码清单 24 中的 SELECT 语句(查询条件是 chr 列大于 '2')会得到什么样的结果呢?

代码清单 24 选取出大于 '2' 的数据的 SELECT 语句

SELECT chr FROM Chars WHERE chr > '2';

大家是不是觉得应该选取出比 2 大的 3、10、11 和 222 这 4 条记录呢?下面就让我们来看看该 SELECT 语句的执行结果吧。

执行结果:

chr----- 3 222

没想到吧?是不是觉得 10 和 11 比 2 大,所以也应该选取出来呢?大家之所以这样想,是因为混淆了数字和字符串,也就是说 2 和 '2' 并不一样。

现在,chr 列被定为字符串类型,并且在对字符串类型的数据进行大小比较时,使用的是和数字比较不同的规则。

典型的规则就是按照字典顺序进行比较,也就是像姓名那样,按照条目在字典中出现的顺序来进行排序。

该规则最重要的一点就是,以相同字符开头的单词比不同字符开头的单词更相近。

Chars 表 chr 列中的数据按照字典顺序进行排序的结果如下所示。

1101122223

'10' 和 '11' 同样都是以 '1' 开头的字符串,首先判定为比 '2' 小。这就像在字典中“提问”“提议”和“问题”按照如下顺序排列一样。

提问提议问题

或者我们以书籍的章节为例也可以。1-1 节包含在第 1 章当中,所以肯定比第 2 章更靠前。

11-11-21-322-12-23

进行大小比较时,得到的结果是 '1-3' 比 '2' 小('1-3' < '2'),'3' 大于 '2-2'('3' > '2')。

比较字符串类型大小的规则今后还会经常使用,所以请大家牢记 [8]。

法则 8

字符串类型的数据原则上按照字典顺序进行排序,不能与数字的大小顺序混淆。

2.5 不能对 NULL 使用比较运算符

关于比较运算符还有一点十分重要,那就是作为查询条件的列中含有 NULL 的情况。

例如,我们把进货单价(purchase_price)作为查询条件。请注意,商品“叉子”和“圆珠笔”的进货单价是 NULL。

我们先来选取进货单价为 2800 元(purchase_price = 2800)的记录(代码清单 25)。

代码清单 25 选取进货单价为 2800 元的记录

SELECT product_name, purchase_price FROM Product WHERE purchase_price = 2800;

执行结果:

product_name | purchase_price---------------+--------------- 运动T恤 | 2800 菜刀 | 2800

大家对这个结果应该都没有疑问吧?接下来我们再尝试选取出进货单价不是 2800 元(purchase_price <> 2800)的记录(代码清单 26)。

代码清单 26 选取出进货单价不是 2800 元的记录

SELECT product_name, purchase_price FROM Product WHERE purchase_price <> 2800;

执行结果:

product_name | purchase_price---------------+--------------- T恤衫 | 500 打孔器 | 320 高压锅 | 5000 擦菜板 | 790

执行结果中并没有“叉子”和“圆珠笔”。这两条记录由于进货单价不明(NULL),因此无法判定是不是 2800 元。

那如果想选取进货单价为 NULL 的记录的话,条件表达式该怎么写呢?历经一番苦思冥想后,用“purchase_price = NULL”试了试,还是一条记录也取不出来。

代码清单 27 错误的 SELECT 语句(一条记录也取不出来)

SELECT product_name, purchase_price FROM Product WHERE purchase_price = NULL;

执行结果:

即使使用 <> 运算符也还是无法选取出 NULL 的记录 [9]。因此,SQL 提供了专门用来判断是否为 NULL 的 IS NULL 运算符。

想要选取 NULL 的记录时,可以像代码清单 28 那样来书写条件表达式。

代码清单 28 选取 NULL 的记录

SELECT product_name, purchase_price FROM Product WHERE purchase_price IS NULL;

执行结果:

product_name | purchase_price---------------+--------------- 叉子 | 圆珠笔 |

反之,希望选取不是 NULL 的记录时,需要使用 IS NOT NULL 运算符(代码清单 29)。

代码清单 29 选取不为 NULL 的记录

SELECT product_name, purchase_price FROM Product WHERE purchase_price IS NOT NULL;

执行结果:

product_name | purchase_price---------------+--------------- T恤衫 | 500 打孔器 | 320 运动T恤 | 2800 菜刀 | 2800 高压锅 | 5000 擦菜板 | 790

法则 9

希望选取 NULL 记录时,需要在条件表达式中使用 IS NULL 运算符。

希望选取不是 NULL 的记录时,需要在条件表达式中使用 IS NOT NULL 运算符。

除此之外,对 NULL 使用比较运算符的方法还有很多,详细内容将会在接下来的 SQL 常用的函数 中进行介绍。

三、逻辑运算符

本节重点

通过使用逻辑运算符,可以将多个查询条件进行组合。

通过 NOT 运算符可以生成“不是~”这样的查询条件。

两边条件都成立时,使用 AND 运算符的查询条件才成立。

只要两边的条件中有一个成立,使用 OR 运算符的查询条件就可以成立。

值可以归结为真(TRUE)和假(FALSE)其中之一的值称为真值。比较运算符在比较成立时返回真,不成立时返回假。

但是,在 SQL 中还存在另外一个特定的真值——不确定(UNKNOWN)。

将根据逻辑运算符对真值进行的操作及其结果汇总成的表称为真值表。

SQL 中的逻辑运算是包含对真、假和不确定进行运算的三值逻辑。

3.1 NOT 运算符

在第 2 节中我们介绍过,想要指定“不是~”这样的否定条件时,需要使用 <> 运算符。除此之外还存在另外一个表示否定,并且使用范围更广的运算符 NOT。

NOT 不能单独使用,必须和其他查询条件组合起来使用。例如,选取出销售单价(sale_price)大于等于 1000 元的记录的 SELECT 语句如下所示(代码清单 30)。

代码清单 30 选取出销售单价大于等于 1000 元的记录

SELECT product_name, product_type, sale_price FROM Product WHERE sale_price >= 1000;

执行结果:

product_name | product_type | sale_price---------------+--------------+------------ T恤衫 | 衣服 | 1000 运动T恤 | 衣服 | 4000 菜刀 | 厨房用具 | 3000 高压锅 | 厨房用具 | 6800

向上述 SELECT 语句的查询条件中添加 NOT 运算符之后的结果如下所示(代码清单 31)。

代码清单 31 向代码清单 30 的查询条件中添加 NOT 运算符

SELECT product_name, product_type, sale_price FROM Product WHERE NOT sale_price >= 1000;

执行结果:

product_name | product_type | sale_price---------------+--------------+------------- 打孔器 | 办公用品 | 500 叉子 | 厨房用具 | 500 擦菜板 | 厨房用具 | 880 圆珠笔 | 办公用品 | 100

明白了吗?通过否定销售单价大于等于 1000 元(sale_price >= 1000)这个查询条件,就可以选取出销售单价小于 1000 元的商品。

也就是说,代码清单 31 中 WHERE 子句指定的查询条件,与代码清单 32 中 WHERE 子句指定的查询条件(sale_price < 1000)是等价的 [10](图 5)。

代码清单 32 WHERE 子句的查询条件和代码清单 31 中的查询条件是等价的

SELECT product_name, product_type FROM Product WHERE sale_price < 1000;

图 5 使用 NOT 运算符时查询条件的变化

通过以上的例子大家可以发现,不使用 NOT 运算符也可以编写出效果相同的查询条件。不仅如此,不使用 NOT 运算符的查询条件更容易让人理解。

使用 NOT 运算符时,我们不得不每次都在脑海中进行“大于等于 1000 元以上这个条件的否定就是小于 1000 元”这样的转换。

虽然如此,但是也不能完全否定 NOT 运算符的作用。在编写复杂的 SQL 语句时,经常会看到 NOT 的身影。

这里只是希望大家了解 NOT 运算符的书写方法和工作原理,同时提醒大家不要滥用该运算符。

法则 10

NOT 运算符用来否定某一条件,但是不能滥用。

3.2 AND 运算符和 OR 运算符

到目前为止,我们看到的每条 SQL 语句中都只有一个查询条件。但在实际使用当中,往往都是同时指定多个查询条件对数据进行查询的。

例如,想要查询“商品种类为厨房用具、销售单价大于等于 3000 元”或“进货单价大于等于 5000 元或小于 1000 元”的商品等情况。

在 WHERE 子句中使用 AND 运算符或者 OR 运算符,可以对多个查询条件进行组合。

AND 运算符在其两侧的查询条件都成立时整个查询条件才成立,其意思相当于“并且”。

OR 运算符在其两侧的查询条件有一个成立时整个查询条件都成立,其意思相当于“或者” [11]。

例如,从 Product 表中选取出“商品种类为厨房用具(product_type = '厨房用具'),并且销售单价大于等于 3000 元(sale_price >= 3000)的商品”的查询条件中就使用了 AND 运算符(代码清单 33)。

代码清单 33 在 WHERE 子句的查询条件中使用 AND 运算符

SELECT product_name, purchase_price FROM Product WHERE product_type = '厨房用具' AND sale_price >= 3000;

执行结果:

product_name | purchase_price---------------+--------------- 菜刀 | 2800 高压锅 | 5000

该查询条件的文氏图如图 6 所示。左侧的圆圈代表符合查询条件“商品种类为厨房用具”的商品,右侧的圆圈代表符合查询条件“销售单价大于等于 3000 元”的商品。

两个圆重合的部分(同时满足两个查询条件的商品)就是通过 AND 运算符能够选取出的记录。

文氏图

将集合(事物的聚集)的关系通过更加容易理解的图形进行可视化展示。

图 6 AND 运算符的工作效果图

选取出“商品种类为厨房用具(product_type = '厨房用具'),或者销售单价大于等于 3000 元(sale_price >= 3000)的商品”的查询条件中使用了 OR 运算符(代码清单 34)。

代码清单 34 在 WHERE 子句的查询条件中使用 OR 运算符

SELECT product_name, purchase_price FROM Product WHERE product_type = '厨房用具' OR sale_price >= 3000;

执行结果:

product_name | purchase_price---------------+--------------- 运动T恤 | 2800 菜刀 | 2800 高压锅 | 5000 叉子 | 擦菜板 | 790

还是让我们来看看查询条件的文氏图吧(图 7)。

包含在左侧的圆圈(商品种类为厨房用具的商品)或者右侧的圆圈(销售单价大于等于 3000 元的商品)中的部分(两个查询条件中满足任何一个的商品)就是通过 OR 运算符能够取出的记录。

图 7 OR 运算符的工作效果图

通过文氏图可以方便地确认由多个条件组合而成的复杂的 SQL 语句的查询条件,大家可以多多加以利用。

法则 11

多个查询条件进行组合时,需要使用 AND 运算符或者 OR 运算符。

3.3 通过括号强化处理

接下来我们尝试书写稍微复杂一些的查询条件。例如,使用下面的查询条件对 Product 表进行查询的 SELECT 语句,其 WHERE 子句的条件表达式该怎么写呢?

“商品种类为办公用品” 并且 “登记日期是 2009 年 9 月 11 日或者 2009 年 9 月 20 日”

满足上述查询条件的商品(product_name)只有“打孔器”。

把上述查询条件原封不动地写入 WHERE 子句中,得到的 SELECT 语句似乎就可以满足需求了(代码清单 35)。

代码清单 35 将查询条件原封不动地写入条件表达式

SELECT product_name, product_type, regist_date FROM Product WHERE product_type = '办公用品' AND regist_date = '2009-09-11' OR regist_date = '2009-09-20';

让我们马上执行上述 SELECT 语句试试看,会得到下面这样的错误结果:

product_name | product_type | regist_date---------------+--------------+------------ T恤衫 | 衣服 | 2009-09-20 打孔器 | 办公用品 | 2009-09-11 菜刀 | 厨房用具 | 2009-09-20 叉子 | 厨房用具 | 2009-09-20

不想要的 T 恤衫、菜刀 和 叉子 也被选出来了,真是头疼呀。到底为什么会得到这样的结果呢?

这是 AND 运算符优先于 OR 运算符所造成的。代码清单 35 中的条件表达式会被解释成下面这样。

「product_type = '办公用品' AND regist_date = '2009-09-11'」 OR 「regist_date = '2009-09-20'」

也就是,

“商品种类为办公用品,并且登记日期是 2009 年 9 月 11 日” 或者 “登记日期是 2009 年 9 月 20 日”

这和想要指定的查询条件并不相符。想要优先执行 OR 运算符时,可以像代码清单 36 那样使用半角括号 () 将 OR 运算符及其两侧的查询条件括起来。

代码清单 36 通过使用括号让 OR 运算符先于 AND 运算符执行

SELECT product_name, product_type, regist_date FROM Product WHERE product_type = '办公用品' AND ( regist_date = '2009-09-11' OR regist_date = '2009-09-20');

执行结果:

product_name | product_type | regist_date---------------+--------------+------------ 打孔器 | 办公用品 | 2009-09-11

这样就选取出了想要得到的“打孔器”。

法则 13

AND 运算符的优先级高于 OR 运算符。想要优先执行 OR 运算符时需要使用括号。

3.4 逻辑运算符和真值

本节介绍的三个运算符 NOT、AND 和 OR 称为逻辑运算符。这里所说的逻辑就是对真值进行操作的意思。真值就是值为 真(TRUE) 或 假(FALSE) 其中之一的值 [12]。

上一节介绍的比较运算符会把运算结果以真值的形式进行返回。比较结果成立时返回真(TRUE),比较结果不成立时返回假(FALSE)[13]。

例如,对于 purchase_price >= 3000 这个查询条件来说,由于 product_name 列为 '运动 T 恤' 的记录的 purchase_price 列的值是 2800,因此会返回假(FALSE),而 product_name 列为 '高压锅' 的记录的 purchase_price 列的值是 5000,所以返回真(TRUE)。

逻辑运算符对比较运算符等返回的真值进行操作。AND 运算符两侧的真值都为真时返回真,除此之外都返回假。

OR 运算符两侧的真值只要有一个不为假就返回真,只有当其两侧的真值都为假时才返回假。

NOT 运算符只是单纯的将真转换为假,将假转换为真。真值表(truth table)就是对这类操作及其结果进行的总结(表 4)。

表 4 真值表

AND

P

Q

P AND Q

OR

P

Q

P OR Q

NOT

P

NOT P

请将表 4 中的 P 和 Q 想象为“销售单价为 500 元”这样的条件。逻辑运算的结果只有真和假两种,对其进行排列组合将会得到 2 × 2 = 4 种结果。

在 SELECT 语句的 WHERE 子句中,通过 AND 运算符将两个查询条件连接起来时,会查询出这两个查询条件都为真的记录。

通过 OR 运算符将两个查询条件连接起来时,会查询出某一个查询条件为真或者两个查询条件都为真的记录。

在条件表达式中使用 NOT 运算符时,会选取出查询条件为假的记录(反过来为真)。

虽然表 4 中的真值表只是使用一个逻辑运算符时得到的结果,但即使使用两个以上的逻辑运算符连接三个以上的查询条件,通过反复进行逻辑运算求出真值,不论多复杂的条件也可以得到相应的结果。

表 5 就是根据之前例子中的查询条件“商品种类为办公用品”,并且“登记日期是 2009 年 9 月 11 日 或者 2009 年 9 月 20 日”(product_type = '办公用品' AND (regist_date = '2009-09-11' OR regist_date = '2009-09-20'))做成的真值表。

表 5 查询条件为 P AND(Q OR R)的真值表

P AND (Q OR R)

P

Q

R

Q OR R

P AND (Q OR R)

P:商品种类为办公用品

Q:登记日期是 2009 年 9 月 11 日

R:登记日期是 2009 年 9 月 20 日

Q OR R:登记日期是 2009 年 9 月 11 日 或者 2009 年 9 月 20 日

P AND (Q OR R):商品种类为办公用品,并且,登记日期是 2009 年 9 月 11 日 或者 2009 年 9 月 20 日

代码清单 36 中的 SELECT 语句,查询出了唯一满足 P AND(Q OR R) 为真的记录“打孔器”。

法则 14

通过创建真值表,无论多复杂的条件,都会更容易理解。

专栏

逻辑积与逻辑和

将表 4 的真值表中的真变为 1、假变为 0,意外地得到了下述规则。

表 A 真为 1、假为 0 的真值表

AND(逻辑积)

PQ积P AND Q111×11101×00010×10000×00

OR(逻辑和)

PQ和P OR Q111+11101+01010+11000+00

NOT

P反转NOT P11 → 0000 → 11

NOT 运算符并没有什么特别的改变,但是 AND 运算的结果与乘法运算(积),OR 运算的结果与加法运算(和)的结果却是一样的。

严格来说,此处的 1+1=1 与通常的整数运算并不相同。只是因为真值中只存在 0 和 1 两种情况,所以才有了这样的结果。

因此,使用 AND 运算符进行的逻辑运算称为逻辑积,使用 OR 运算符进行的逻辑运算称为逻辑和。

3.5 含有 NULL 时的真值

上一节我们介绍了查询 NULL 时不能使用比较运算符(= 或者 <>),需要使用 IS NULL 运算符或者 IS NOT NULL 运算符。实际上,使用逻辑运算符时也需要特别对待 NULL。

我们来看一下 Product(商品)表,商品“叉子”和“圆珠笔”的进货单价(purchase_price)为 NULL。

那么,对这两条记录使用查询条件 purchase_price = 2800(进货单价为 2800 元)会得到什么样的真值呢?如果结果为真,则通过该条件表达式就可以选取出“叉子”和“圆珠笔”这两条记录。

但是在之前介绍“不能对 NULL 使用比较运算符”(第 2 节)时,我们就知道结果并不是这样的,也就是说结果不为真。

那结果会为假吗?实际上结果也不是假。

如果结果为假,那么对其进行否定的条件 NOT purchase_price = 2800(进货单价不是 2800 元)的结果应该为真,也就能选取出这两条记录了(因为假的对立面为真),但实际结果却并不是这样。

既不是真也不是假,那结果到底是什么呢?其实这是 SQL 中特有的情况。这时真值是除真假之外的第三种值——不确定(UNKNOWN)。一般的逻辑运算并不存在这第三种值。

SQL 之外的语言也基本上只使用真和假这两种真值。与通常的逻辑运算被称为二值逻辑相对,只有 SQL 中的逻辑运算被称为三值逻辑。

因此,表 4 中的真值表并不完整,完整的真值表应该像表 6 这样包含“不确定”这个值。

表 6 三值逻辑中的 AND 和 OR 真值表

AND

P

Q

P AND Q

不确定

不确定

不确定

不确定

不确定

不确定

不确定

不确定

不确定

OR

P

Q

P OR Q

不确定

不确定

不确定

不确定

不确定

不确定

不确定

不确定

不确定

专栏

Product 表中设置 NOT NULL 约束的原因

原本只有 4 行的真值表,如果要考虑 NULL 的话就会像表 6 那样增加为 3×3=9 行,看起来也变得更加繁琐,考虑 NULL 时的条件判断也会变得异常复杂,这与我们希望的结果大相径庭。

因此,数据库领域的有识之士们达成了“尽量不使用 NULL”的共识。

这就是为什么在创建 Product 表时要给某些列设置 NOT NULL 约束(禁止录入 NULL)的缘故。

原文链接:

(完)


  1. 结果的显示方式根据 RDBMS 的客户端的不同略有不同(数据的内容都是相同的)。 ↩︎
  2. 行的顺序也可能存在与上述执行结果不同的情况。如果用户不设定 SELECT 语句执行结果中行的顺序,就可能会发生上述情况。行的排序方法将在 SQL 如何对表进行聚合和分组查询并对查询结果进行排序 中进行学习。 ↩︎
  3. 使用双引号可以设定包含空格(空白)的别名。但是如果忘记使用双引号就可能出错,因此并不推荐。大家可以像 product_list 这样使用下划线(_)来代替空白。 ↩︎
  4. 在 SQL 语句中使用字符串或者日期常数时,必须使用单引号 (') 将其括起来。 ↩︎
  5. 这和 Excel 中根据过滤条件对行进行过滤的功能是相同的。 ↩︎
  6. MySQL 中需要在“–”之后加入半角空格(如果不加的话就不会被认为是注释)。 ↩︎
  7. 有很多 RDBMS 可以使用比较运算符“!=”来实现不等于功能。但这是限于不被标准 SQL 所承认的特定 SQL,出于安全的考虑,最好不要使用。 ↩︎
  8. 该规则对定长字符串和可变长字符串都适用。 ↩︎
  9. SQL 不识别“= NULL”和“<> NULL”的理由将会在下一节(包含 NULL 情况下的真值)中进行说明。 ↩︎
  10. 判定的结果相等。 ↩︎
  11. 需要注意的是,并不是只有一个条件成立时整个查询条件才成立,两个条件都成立时整个查询条件也同样成立。这与“到场的客人可以选择钥匙链或者迷你包作为礼品 ( 任选其一 )”中的“或者”有所不同。 ↩︎
  12. 但是在 SQL 中还存在“不确定”(UNKNOWN)这样的值。接下来会进行详细说明。 ↩︎
  13. 算术运算符返回的结果是数字。除了返回结果的类型不同之外,和比较运算符一样都会返回运算结果。 ↩︎

相关推荐