回顾了一遍SQL基础,集中总结一下,记录了一些平时容易忽略或者容易忘的问题。本篇博客中所有的范例都已在 MySql 5.7.20 中验证。
1. 关于查询基础
SELECT语句是SQL最基础也是最重要的语句,这里列一些自己以前没有注意到的东西。
首先我们先建立一张表作为示例,并随便插入一些值:
CREATE TABLE SubjectInfo
(
id INTEGER NOT NULL ,
name VARCHAR(20) NOT NULL ,
age INTEGER NOT NULL ,
gender CHAR(6) NOT NULL ,
height INTEGER NOT NULL ,
weight INTEGER NOT NULL ,
PRIMARY KEY (id)
);
1.1 常数的查询
SELECT子句中不仅可以书写列名,还可以书写常数,比如:
SELECT name, age, lab AS 'ISMC' FROM SubjectInfo;
这样显示的结果除了选择的 name 和 age 列之外,还会增加一个 lab 列,结果中所有记录的值都会是我们指定的 ISMC 。
1.2 从结果中删除重复的行
在SELECT语句中使用 DISTINCT 可以删除重复行,但要注意的是:
- NULL 也被视为一类数据,存在多条 NULL 数据行时,也会结合为一条 NULL 数据。
- 可以在多列之前使用 DISTINCT :
- 此时,此时多个列的数据进行组合,全部重复的会被删去
- 注意 DISTINCT 关键字只能用在第一个列名之前
1.3 表达式中需要注意NULL
算术运算符:
- 查询时,我们可以在子句中用上表达式,此时需要特别注意含有 NULL 的运算,比如:
上式的计算结果都是 NULL,实际上所有包含 NULL 的计算,结果肯定是 NULL。4 / NULL; NULL / 0;
比较运算符:
- 此外,不能对 NULL 使用比较运算符。在 WHERE 子句中我们利用
>, <, <>
筛选出来记录都不包含该条件列中为 NULL 的记录, 在SQL中提供了专门用来判断是否为 NULL 的运算符IS NULL
和IS NOT NULL
逻辑运算符:
- 真值通常是只有真(TRUE)和(FALSE)的,运算符在比较成立的时候返回真,不成立时返回假。但是,在SQL中还存在另外一个特定的真值--不确定(UNKNOWN)。也就是说,与通常的逻辑运算为二值逻辑相对,SQL中的逻辑运算是包含对真、假和不确定进行运算的三值逻辑。
- 上述也是我们在前面提到的,为何用
>, <, <>
不能筛选出含有 NULL 值的记录的原因,因为此时的真值表不是只有2*2的4行,而是3*3的9行!因此,数据库领域的有识之士们达成了“尽量不使用 NULL ”的共识,这也是为什么我们在创建表时,会经常加上NOT NULL
的字段限制条件。
2. 聚合、分组与排序
通过SQL对数据进行某种操作或者计算时需要使用函数,常用的函数包括COUNT, SUM, AVG, MAX, MIN
,这些用于合计的函数称为聚合函数或者集合函数,实际上,所有的聚合函数都是一样的:输入多行输出一行。
2.1 聚合函数
COUNT函数:
- 通常,聚合函数会对 NULL 以外的对象进行合计。但是只有 COUNT 函数例外,使用
COUNT(*)
可以查出包含 NULL 在内的全部数据行数。 - 但是呢,将包含 NULL 的列作为参数时,
COUNT(*)
和COUNT(<列名>)
的结果并不相同。 - 还有就是除了 COUNT 函数之外的其他函数并不能将 星号 作为函数的参数。
SUM、AVG函数:
- 要注意在使用
SUM(<列名>)
和AVG(<列名>)
的时候,运算过程中是不包含该字段中值为 NULL 的记录的。
MAX、MIN函数:
- SUM、AVG函数只能对数值类型的列使用,而MAX、MIN函数原则上适用于任何数据类型的类,也就是说,如果是能够排序的数据,就肯定有最大值和最小值,也就能够使用这两个函数。
2.2 分组
通过使用聚合函数和GROUP BY子句,可以将表分割后再进行聚合。在GROUP BY子句中指定的列称为聚合键或者分组列,聚合键可以是用逗号隔开的多个字段。当聚合键中包含 NULL 时,在结果中会以“不确定”行(空行)的形式表现出来。
2.2.1 使用GROUP BY子句时,SELECT子句中不能出现聚合键之外的列名
在使用 COUNT 这样的聚合函数时,SELECT子句中的元素有严格的限制,实际上,此时SELECT子句中只能存在以下三种元素:
- 常数
- 聚合函数
- GROUP BY 子句中指定的列名(也就是聚合键)
这里常见的错误就是把聚合键之外的列名书写在SELECT子句中。这里的问题是聚合键和该字段并不是一一对应的。但是呢,在MySQL中这样的语法也得到了认同,执行时并不会发生错误,它会在多列候补中随机抽出一个满足条件的值。
2.2.2 不要再WHERE子句中使用聚合函数
考虑到WHERE子句是对记录的筛选,在其中使用聚合函数就不是对一行一行的筛选了,所以会引发错误。实际上,只有SELECT、HAVING 和 ORDER BY 中能够使用 COUNT 等函数。
2.2.3 WHERE 和 HAVING 的比较
两者都是指定条件用的,但是具体来说:WHERE 子句用来指定数据行的条件,HAVING 子句用来指定分组的条件。
HAVING子句和包含GROUP BY子句时的SELECT子句一样,能够使用的要素有一定的限制。限制的内容也完全一样,HAVING子句中能够使用的3种要素同样是:
- 常数
- 聚合函数
- GROUP BY 子句中指定的列名(也就是聚合键)
在思考HAVING子句的使用方法时,把一次聚合后的结果作为HAVING子句起始点的话更容易把握。但此时就有一个疑问,因为我们发现聚合键所对应的条件此时既能在HAVIGN子句中使用,也可以在WHERE子句中使用。但是,我们认为聚合键所对应的条件还是应该放在WHERE子句中。理由有二:
- 根本原因是WHERE子句和HAVING子句的作用不同:HAVING子句用来指定“组”的条件,而“行”所对应的条件应该写在WHERE子句中,这样一来,书写出来的SELECT子句中各部分各司其职,更易理解
- 其实,关于DBMS的内部实现:通常情况下,为了得到相同的结果,将条件写在WHERE子句中要比写在HAVING子句中的处理速度更快,返回结果所需时间更短。
- 使用COUNT函数等对表中数据进行聚合时,DBMS内部就会进行排序处理。排序处理会大大增加机器的负担,因此,只有尽可能减少排序的行数才能增加处理速度,而通过WHERE子句指定条件时,由于排序之前就对数据进行了过滤,所以能够减少排序的数据量。
- WHERE子句更具速度优势的另一个理由是,可以对WHERE子句指定条件所对应的列创建索引,这样也可以大幅提高处理速度。
2.3 排序
不管何种情况,ORDER BY子句都需要写在SELECT BY子句都需要写在SELECT语句的末尾。这是因为对数据行进行排序的操作必须在结果即将返回时执行。ORDER BY子句中书写的列名称为排序键。在键后面可以使用DESC、ASC来指定降序、升序。排序键中包含NULL时,会在开头或末尾进行汇总,具体位置看具体DBMS。
此外,ORDER BY:子句中可以使用存在于表中,但并不包含在SELECT子句中的列,还可以使用聚合函数,比如说:
SELECT age, count(*)
FROM subjectinfo
GROUP BY age
ORDER BY count(*);
3. 数据插入、删除和更新
在介绍这部分之前,我们先来了解清单的概念,比如:
INSERT INTO SubjectInfo
(id, name, age, gender, height, weight)
VALUES
(1, 'Tristan', 23, 'male', 177, 71);
这种将列名和值用逗号隔开,分别写在()内的形式称为清单。
3.1 数据的插入(INSERT)
插入NULL时,需要在值清单中明确写出NULL,如果在列清单和VALUES中省略设定了默认值的列的话,该列的值就会被设定为NULL。插入默认值时,写DEFAULT,同时在定义表时,在对应的字段后面应该有``DEFAULT XXX`的设置。
省略列清单和多行INSERT: 对表中所有列进行INSERT操作时可以省略表明后的列清单,多行插入的时候可以将VALUES子句通过逗号进行分隔排列:
INSERT INTO SubjectInfo VALUES
(1, 'Tristan', 23, 'male', 177, 71),
(2, 'May', 23, 'female', 165, 52);
使用INSERT从其他表中复制数据: INSERT ... SELECT 语句可以在需要进行数据备份时使用。
INSERT INTO SubjectInfo
(id, name, age, gender, height, weight)
SELECT test_id, name, age, sex, h, w FROM AnotherTable;
3.2 数据的删除(DELETE)
标准SQL中用来从表中删除数据的只有DELETE语句。但是,很多数据库产品还存在一种被称为TRUNCATE的语句。这些产品主要包括Oracle、SQL Server、PostgreSQL 和 MySQL,而DB2中并没有TRUNCATE语句。
TRUNCATE <表名>
与DELECT不同的是,TRUNCATE只能删除表中的全部数据,而不能通过WHERE子句指定条件来删除部分数据。也正是因为它不能具体地控制删除对象,所以其处理速度比DELECT要快的多。实际上,DELETE语句在DML语句中也属于处理时间比较长的,因此要删除全部数据行时,使用TRUNCATE可以缩短执行时间。
3.3 数据的更新(UPDATE)
暂时没啥可说的
3.4 事务
在RDBMS中,事务代表了对表中数据进行更新的单位,简单来讲,事务就是需要在同一个处理单元中执行的一系列更新处理的集合。示例如下:
-- MySQL style
START TRANSACTION; -- 事务开始
UPDATE ...;
COMMIT; -- 事务结束
其中,事务的终止指令包括 COMMIT(提交处理)和 ROLLBACK(取消处理)两种。
事务处理从何时开始:
事务并没有标准的开始指令存在,根据DBMS的不同而不同。实际上,几乎所有的数据库产品的事务都无需开始指令,这是因为大部分情况下,事务在数据库连接建立时就已经悄悄开始了,并不需要用户再明确发出开始指令。
那像这样不使用指令而悄悄开始事务的情况下,应该如何区分各个事务呢?通常有如下两种情况:
- 每条SQL语句就是一个事务(自动提交模式)
- 直到用户执行COMMIT或者ROLLBACK为止算作一个事务
通常的DBMS都可以选择其中任意一种模式。默认使用自动提交模式的DBMS有 SQL Server、Postgre SQL 和 MySQL等。该模式下,每一条语句都包括在事务的开始语句和结束语句之中。
ACID特性: DBMS的事务都遵循四种标准规格的约定。将这四种特性的首字母结合起来统称为ACID特性。这些约定是所有DBMS都必须遵守的规则。
- 原子性(Atomicity):指在事务结束时,其中所包含的更新处理要么全部执行,要么完全不执行。也就是要么占有一切要么一无所有。
- 一致性(Consistency):指的是事务中包含的处理,要满足数据库提前设置的约束,如主键约束或者 NOT NULL 约束等,一致性也称为完整性。
- 隔离性(Isolation):指保证不同事务之间互不干扰的特性。该特性保证了事务之间不会互相嵌套。此外,在某个事务中进行的更改,在该事务结束之前,对其他事务而言是不可见的。
- 持久性(Durability):指的是事务(不论是提交还是回滚)一旦结束,DBMS会保证该时点的数据状态得以保存的特性。即使由于系统故障导致数据丢失,数据库也一定能通过某种手段进行恢复。保证持久性的方法根据实现的不同而不同,其中最常见的就是将事务的执行记录(日志)保存在硬盘中,当发生故障时,可以通过日志恢复到故障发生前的状态。
其他方面
关于注释:
一行注释用--
,多行注释用/* */
,注意在MySql中单行注释--
之后需要加入半角空格,否则不认为是注释。
逻辑运算符的优先顺序:
AND 运算符的优先级高于 OR 运算符,尽量用上括号以避免错误。
各子句的书写顺序和执行顺序:
书写顺序: SELECT -> FROM -> WHERE -> GROUP BY -> HAVING -> ORDER BY
执行顺序: FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY