《SQL基础教程》
《SQL基础教程(第2版)》读书笔记
基础知识
管理数据库的计算机系统被称为数据库管理系统(DataBase Management System,DBMS),按数据的保存格式,其主要可分为五类:
- 层次数据库(Hierarchical,HDB):把数据通过层次(树形)结构表现出来
- 关系数据库(Relational,RDB):用行和列组成的二维表来管理数据
- 面向对象数据库(Objec Oriented, OODB):把数据及其操作集合起来以对象为单位进行操作
- XML数据库(XMLDB):处理XML数据
- 键值存储系统(Key-Value Store, KVS):保存查询所用的主键和值的组合
目前应用最广也是数据库为RDB,这里要学习的就是专用用来对RDB数据进行操作的结构化查询语言(Structured Query Language,SQL)。RDBMS常采用客户端/服务器(C/S)结构,当前具有代表性的有:
- Oracle Database
- SQL Server (Microsoft)
- DB2 (IBM)
- PostgreSQL
- MySQL
在RDB中,包含如下概念:
- 表(table):用来管理数据的二维表
- 字段:表的列,表示表中的数据项
- 记录:表的行,表示一条数据
在RDBMS中,必须以行为单位进行数据读写,而用来实现这些操作的一条(ISO定义的)标准SQL语句通常由关键字、表名、列名等组合而成,根据对RDBMS赋予的指令种类,SQL语句大致可分为三类:
- DDL(Data Definition Language,数据定义):对数据库或表对象进行增删改操作
- DML(Date Manipulation Lanugage,数据操纵):对表中的数据进行增删改操作
- DCL(Data Control Lanugage,数据控制):确定、取消数据变更、设定用户权限等操作
实际使用的SQL语句大都为DML,这里重点学习的也是DML。SQL语句不区分大小写,通常习惯将关键字及表名首字母大写,除数字可以直接书,字符串、时间等常数都需要包括在单引号(’)中,各条语句以分号(;)结束。
基本操作语句
表对象操作
对数据库或表对象进行增删改的常用SQL语句有:
1 | CREATE DATABASE <database_name>; -- 创建数据库 |
其中
- INTEGER:整型
- CHAR:定长字符串,以定长存储,长度可自行指定
- VARCHAR:可变字符串,以边长存储,长度可自行指定
- DATE:日期
- NUMERIC:数值,可定义全体及小数位数
约束用以对各字段中存储的数据进行一些限制或追加条件,几种常用的约束有:
- PRIMARY KEY:主键
- NOT NULL:非空
- DEFAULT:默认值
更新表的定义便是表中的字段进行增改,
1 | ADD COLUMN <line_define>; -- 新增列 |
在PostgreSQL中,使用这些语句操作表对象的例子如下:
1 | CREATE DATABASE shop; -- 创建数据库shop |
表的增删改
对表中数据进行增删改的常用SQL语句有:
1 | INSERT INTO <table_name> (<line_name_list>) VALUES (<value_list>); -- 插入数据 |
插入数据时,清单(list)中的名字或值须以逗号(,)分隔,要插入的数据值清单
1 | INSERT INTO Product (product_id, product_name, product_type, sale_price, purchase_price, regist_date) VALUES ('0001', 'T恤' ,'衣服', 1000, 500, '2009-09-20'); |
更新或删除数据时,可通过WHERE子句来指定条件,
以上语句的用例如下所示:
1 | DELETE FROM Product WHERE sale_price >= 4000 AND purchase_price >= 4000; -- 删除符合条件的数据 |
查询语句
基本查询
SELECT是SQL最基本也是最重要的语句,它常用以查询表中的数据,几个基本的SELECT查询语句如下:
1 | SELECT <line_name> FROM <table_name>; -- 简单查询 |
查询过程中,在SELECT子句中列出要查询的字段名
在SELECT子句中使用DISTINCT关键字,可以根据一些字段来删除查询结果中的重复数据,也通过WHERE子句来指定条件,筛选出想要的查询结果。
这些基本查询语句的用例如下:
1 | SELECT product_id, product_name, purchase_price FROM Product; -- 查询其中三列 |
聚合查询
通过SQL对数据进行某种操作或计算时需要使用函数,其中对数据进行一些汇总操作的函数就被称为聚合函数,常用的聚合函数有
- COUNT():计算表中的记录数
- SUM():计算某字段的合计值
- AVG():计算某字段的平均值
- MAX/MIN():求出某字段的最大/最小值
以聚合函数为SELECT子句,结合GROUP BY关键字,可以实现对表中地数据进行分组汇总,其基本地语法如下:
1 | SELECT <line_name> FROM <table_name> GROUP BY <line_name>; -- 分组聚合 |
例如,可使用如下语句来统计Product中各类产品的数量:
1 | SELECT product_type, COUNT(*) FROM Product GROUP BY product_type; -- 分组聚合 |
其中GROUP BY子句中的字段名被称为聚合键,它决定了表的分组方式,SELECT子句中除了聚合函数外不可出现除该聚合键以外的字段名。
句中可引入WHERE语句,以实现依条件进行汇总:
1 | SELECT <line_name> FROM <table_name> WHERE <cond_expr> GROUP BY <line_name>; -- 根据条件进行分组聚合 |
GROUP BY子句一定要写在FROM及WHERE语句之后,且上述语句中各子句的执行顺序如下:
FROM -> WHERE -> GROUP BY -> SELECT
因此,GROUP BY中不可使用SELECT子句中由AS定义的别名作为聚合键。
用WHERE语句中只能对原始记录指定条件,要由分组聚合结果指定条件,即从分组结果中筛选数据,就需要用到HAVING子句,其语法如下:
1 | SELECT <line_name> FROM <table_name> GROUP BY <line_name> HAVING <cond_expr>; -- 从分组结果中依条件选择 |
HAVING子句可由常数、聚合函数或聚合键构造。例如,将Product按商品种类分组后,可使用如下语句取出其中数据行为2的组:
1 | SELECT product_type. COUNT(*) FROM Product GROUP BY product_type HAVING COUNT(*) = 2; |
加入HAVING子句后,整个查询语句的执行顺序如下:
SELECT -> FROM -> WHERE -> GROUP BY -> HAVING
在查询表中数据时,默认情况下的查询结果都是无序的。通过ORDER BY关键字,可查询到的表记录按某个字段进行排序,具体的语法如下:
1 | SELECT <line_name> FROM <table_name> ORDER BY <line_name> DESC/ASC; -- 按某字段进行升/降排序 |
其中通过最后的关键字为DESC或ASC来指定升降序。ORDER BY子句中的字段名被称为排序键,类似GROUP BY,排序键也可以有多个,且越先写的排序键优先级越高。
ORDER BY子句通常要放在SELECT语句的末尾,其中可以使用别名,因为ORDER BY子句总在最后执行,具体顺序如下:
FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY
要注意的是,聚合函数只能在SELECT、HAVING、ORDER BY子句中使用,WHERE子句中不可出现聚合函数。
事务与视图
所谓的事务(transaction),指的是需要在同一个处理单元中执行的一系列更新处理的集合。表的增删改操作常需要用多条SQL语句才能完成,因此可以多条这样的SQL语句封装成一个事务来统一执行。在RDBMS中,事务是对表中数据进行更新的单位。
封装多条用于操作表的数据的SQL语句的方法,及构建事务的语法如下:
1 | 事务开始语句; |
例如下面在PostgreSQL中创建一个事务:
1 | BEGIN TRANSACTION; |
标准SQL中并没有定义事务的开始语句,因此此语句随DBMS的不同而不同,如PostgreSQL中规定的开始语句为“BEGIN TRANSACTION”,MySQL中的则为“START TRANSCTION”。事务的结束语句可为“COMMIT”或“ROLLBACK”,前者是按顺序执行事务中的DML语句并在最终提交,后者则是取消事务中所有对数据库的更新处理而恢复其原始状态。
DBMS中的事务都需要遵循四大特性:
- 原子性(Atomicity):事务结束时,其中包含的更新处理要么全部执行,要么全部不执行
- 一致性(Consistency):事务中包含的处理要满足数据库提前设置的约束
- 隔离性(Isolation):不同事务间互不干扰
- 持久性(Durability):事务结束后,DBMS能保证该时间点的数据被保存,即使发生故障也有恢复手段
取每个特性的首字符,这四大特性又称ACID特性。
事务是将多条对数据库进行增删改的语句封装到一起,如果将一些常用的查询语句封装起来,就有了视图(VIEW)。查询语句执行后的结果是一张表,要保存这张表的话需要一定存储空间,将查询语句封装为视图,需要这张表的时候只需要调用该视图即可,这可以极大地节省存储空间。
创建视图地语法如下:
1 | CREATE VIEW <view_name> (<view_line_name_1>, <view_line_name_2>, ...) |
例如将前面例子中用过的一条查询语句封装为ProductSum视图:
1 | CREATE VIEW ProductSum (product_type, cnt_product) |
视图中将SELECT语句的查询结果中包含的字段为其字段,并为它们设置别名,且视图的使用方法也与普通的表无异:
1 | SELECT product_type, cnt_product FROM ProductSum; |
要注意的是,因为RDB中的记录本身都是无序的,所以不能在视图中使用ORDER BY语句进行排序。虽然对视图的操作与表无异,但无法随意使用更新语句对视图进行数据更新,因为视图的更新过程将对原表中数据产生直接影响。
另外,可以使用如下语句删除某个视图:
1 | DROP VIEW <view_name>; |
子查询
所谓子查询,是将SELECT语句进行多层嵌套,而直接以SELECT语句作为某些关键字的子句。如下面的例子:
1 | SELECT Product_type, cnt_product FROM ( |
其中内层的SELECT语句将首先被执行并得到查询结果,外层的查询将以这些结果为源数据进行二次查询。简单来说,子查询就是一次性视图,在SELECT语句执行结束后便消失,而不是像视图那样永久保存在磁盘上。在上例中,还使用了AS关键字为这个子查询命名。
子查询可以进行无限嵌套,例如下面的SELECT语句中便嵌套了两层的SELECT语句:
1 | SELECT product_type, cnt_product FROM ( |
返回结果为一行一列即单一值的子查询语句被称为标量子查询。不管是SELECT、WHERE子句还是GROUP BY、HAVING、ORDER BY子句,只要是能够用常数或列名的地方,都可以使用标量子查询语句。例如下面的例中WHERE子句的条件表达式:
1 | SELECT product_id, product_name, sale_price FROM Product WHERE sale_price > (SELECT AVG(sale_price) FROM Product); |
有时想要在GOROUP BY细分好的各组内进行一些比较操作,例如根据product_type将Product中的记录分组后,想要分别选出各组中价格高于该平均价格的商品,可能会想到如下语句:
1 | SELECT prodcut_id, product_name, sale_price FROM Product WHERE sale_price > (SELECT AVG(sale_price) FROM Product GROUP BY product_type); |
然而这是一个错误的SQL语句,其中的SELECT子查询语句不为标量,无法参与比较。要解决这类问题,需要用到关联子查询,如下所示:
1 | SELECT product_type, product_name, sale_price FROM Product AS P1 WHERE sale_price > ( |
子查询语句中,会根据product_type返回相应的平均价格,从而筛选中表中满足要求的记录。
集合与联结
SQL语句除了能对RDB中的表进行增删改以及查询操作外,还能在多个表之间进行集合运算。其中用到的关键字如下:
1 | SELECT ... FROM <table_name_1> UNION SELECT ... FROM <table_name_2>; -- 并集 |
其中UINON、INTERSERT、EXCEPT关键字分别代表了对两表进行并集、交集、差集运算。对于两个具有相同字段的表,并集是将这些记录进行合并,交集则选出两表中的公共记录,差集则将把公共记录从第一个表中去除,这些过程都不会保留重复的记录,可在这些关键字后面添加ALL来把重复数据予以保留。
集合运算是以行方向为单位进行操作,而联结是以列为单位对表进行的操作。在联结运算中,应用最为广泛的为内联结,其用到的关键字为INNER JOIN。它是以两表共有的字段为桥梁,从而将两表的字段合并到一起而得到一张表。
内联结语句的语法为:
1 | SELECT <line_name> FROM <table_name_1> INNER JOIN <table_ame_2> ON <expr>; -- 由联结键内联 |
其中关键字ON后面的表达式
1 | CREATE TABLE ShopProduct |
该表与Product有相同的字段product_id,于是可以以此为联结键,将两表的字段进行合并:
1 | SELECT SP.shop_id, SP.shop_name, SP_product_id, P.product_name, P.sale_price |
其中使用了别名以分别从两表中选出想要查询的字段,当ShopProduct中某个记录的product_id与Product中的某个记录一致,Product中该记录的几个字段就会被联结到ShopProduct上。使用这样的联结运算将满足规则的表联结起来时,前面用到的WHERE、GROUP BY HAVING、ORDER BY等语句都能正常使用。
既然有内连接,那必有与之相对的外联结。外联结以OUTER JOIN为关键字,过程中可使用关键字LEFT或RIGHT,将OUTER JOIN左边或右边的表作为主表,联结过程中不管与之联结的表中是否存在对应字段,主表中的记录都会被全部保留。如下面的例子所示:
1 | SELECT SP.shop_id, SP.shop_name, SP_product_id, P.product_name, P.sale_price |
除了内外联之外,还存在一种称为交叉联结的表联结方式,在实际情况中的应用相对较少,具体的语法如下:
1 | SELECT <line_name> FROM <table_name_1> CROSS JOIN <table_ame_2>; -- 交叉联结 |
该联结过程是满足相同规则的表进行笛卡尔积运算,因此不支持ON关键字,要得到与内外联一致的结果,就必须使用WHERE关键字从大量的结果中进行筛选。
函数、谓词、CASE
在聚合查询中是曾了解过几个简单的聚合函数,除此之外,SQL中还存在用以进行数值计算的算术函数、字符串操作的字符串函数、日期操作的日期函数及用于转换数据类型和值的转换函数。在这些函数中常用的如下:
- ABS(*):求绝对值
- MOD(m, n):求余(m/n)
- ROUND(m, n):舍入m(n为保留位数)
- ||/CONCAT():拼接字符串
- LENGTH():求字符串长度
- LOWER/UPPER():对字符串进行大/小写转换
- REPLACE(str, m, n):将字符串str中的m替换为n
- SUBSTRING(str FROM m FOR n):从str的第m位开始截取n个
- CURRENT_DATE:当前日期
- CURRENT_TIME:当前时间
- CURRENT_TIMESTAMP:当前日期、时间
- EXTRACT(m FROM n):从时间n中截取元素m
- CAST(m AS n):m的类型转换为n
- COALESCE(date1, date2, …):参数可变,返回参数中左侧第一个不是NULL的值,常可用在含有NULL的记录项时防止最后的计算结果变为NULL
SQL中还存在一个谓词的概念,所谓的谓词简单来说就是返回结果真值(True、False或Unknown)的函数,它们可以用于SQL语句中的WHERE、HAVING等子句中,参与各种逻辑计算。
LIKE谓词可用来进行模糊查询,从表中筛选出符合某种组合模式的数据。其中可用的通配符有:
- %:0个以上任意字符
- _:1个任意字符
例如,可用如下语句从表中选出符合模式“任意个任意字符+ab+1个任意字符”的字符串:
1 | SELECT * FROM SampleLike WHERE strcol LIKE '%ab_'; |
BETWEEN谓词可用来代替通常的大于、小于号加逻辑运算符的组合,方便地实现范围查询,例如:
1 | SELECT product_name, sale_price FROM Product WHERE sale_price BETWEEN 100 AND 1000; |
IS NULL和IS NOT NULL谓词则用来判断某个记录项是否为NULL。IN及NOT IN谓词可以用来代替OR,查询某个项目是否存在于某个集合中,如下所示:
1 | SELECT product_name, purchase_price FROM Product WHERE purchase_price IN (320, 500, 5000); |
另外还有一个功能强大的谓词EXIST,可用其代替IN/NOT IN,但该谓词的语法较为复杂及难以理解。由表ShopProduct和Product,使用该谓词来选出“东京店在售之外的商品的售价”的语句如下:
1 | SELECT product_name, sale_price FROM Product AS P |
使用SQL进行查询时,还可以使用CASE进行条件分支,其语法如下:
1 | CASE WHEN <value_expr> THEN <expr> |
例如:
1 | SELECT product_name, |
更新历史:
- 2019.11.12 完成初稿