《SQL必知必会》(第4版)学习笔记

《Sams Teach Yourself SQL in 10 Minutes (4th Edition)》 —— SQL 入门书

闲来无事,翻一翻,确实作为入门书籍来说还是不错的。

引言

SQL 是使用最广泛的数据库语言。

数据库设计、规范化到关系数据库理论以及管理问题等,一大堆,甚是繁琐,但这些往往不是我们需要的。

从最简单的数据检索(select)入手,逐步过渡一些较为复杂的内容,例如:联结、子查询、存储过程、游标、触发器以及表约束等,循序渐进。

第1课 了解SQL

数据库基础、数据库设计、规范化、关系数据库理论

  • 数据库:一个以某种有组织的方式存储的数据集合(文件柜、database)。

  • 数据库软件:数据库管理系统(DBMS),数据库是通过 DBMS 创建和操纵的容器。

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

    • table 表名唯一。
    • 存储在表中的数据是同一种类型的数据或清单。
  • 模式:关于数据库和表的布局及特性的信息(schema)。

  • :(column)字段

    • 表由列组成。
    • 分解数据
    • 数据类型(date type) —— 数值、字符、文本、日期等
  • :(row) 也称为数据库记录 (record)。

  • 主键:唯一标识 (primary key),主键不允许为空值 NULL

    应该总是定义主键

  • 复合主键

  • 外键

  • SQL:Structured Query Language 结构化查询语言

  • 标准SQL:ANSI SQL

动手实践 —— The Most Important!!!

第2课 检索数据

使用 select 语句从表中检索一个或多个数据列。

关键字(keyword):作为 SQL 组成部分的保留字,英语单词。

  • 检索单个列
select prod_name from Products;
select * from table_name;

未排序数据

过滤 → 子集

多条 SQL 语句必须以分号(;)分隔

SQL 语句不区分大小写

忽略空格

  • 检索多个列

多个列,用逗号(;)分隔

select prod_id,
       prod_name,
       prod_price
from Products;

数据格式化

  • 检索所有列

使用星号(*)通配符可以检索所有列

select * from Products;

通配符 *

检索不需要的列通常会降低检索和应用程序的性能,因此需要确保真的需要所有的列,才使用通配符进行检索。

检索未知列

  • 检索不同的值 distinct

distinct 去重 具有唯一性

select distinct vend_id from Products;

作用于所有的列 只返回不同的值

select distinct a,b from table_name;

不能部分使用 distinct

  • 限制结果 limit

只返回一定数量的行

1.SQL Server & Access  -- TOP
  select top 5 prod_name from table_name;
2.DB2 -- FETCH FIRST 5 ROWS ONLY
  select prod_name from table_name fetch first 5 rows only;
3.Oracle -- 基于rownum(行计数器)计算行
  select prod_name from table_name where rownum <= 5;
4.Mysql & MaraiDB & PostgreSQL & SQLite -- limit
  select * from table_name limit 5;

或者

select * from table_name limit 5 offset 5; -- MySQL 等 DBMS 返回从第5行开始的5行数据

从第5行开始后面的3行数据

select * from table_name limit 3 offset 4; --  MySQL返回从第5行起的3行数据
select * from table_name limit 3,4; -- MySQL返回从第4行开始的4行数据

limit m,n

​ 其中,m是指记录开始的 index,从0开始,表示第一条记录

​ n是指从第m+1条开始,取n条数据

​ 初始记录行的偏移量是0,不是1

​ limit 95,-1 96-last所有 —— 测试过,不行!

​ limit 5 等同于 limit 0,5

  • 注释

希望包括不进行处理和执行的文本,例如描述性的注释。

井号#、--、/* */ 等3种方式。

第3课 排序检索数据

使用 select 语句的 order by 子句,根据需要排序检索出的数据。

如果不排序,数据一般将以它在底层表中出现的顺序显示。

子句(clause):一个子句通常由一个关键字加上所提供的数据组成。

select *
from table_name
order by prod_name;

order by 子句应该放在最后面。

使用非检索列进行排序数据也是完全合法的。

按多个列进行排序时,用逗号隔开即可。

select *
from table_name
order by prod_name, prod_id;

按列位置排序

select prod_id, prod_name, prod_price
from Products
order by 2,3; -- 先按第2列进行排序,后按第3列进行排序

不用重新输入列名,却可能导致不明确。

降序desc

-- desc descending 缩写

-- asc ascending 缩写

select *
from table_name
order by prod_name desc; -- asc 升序

多个列

select *
from table_name
order by prod_name desc, prod_id;

desc 关键字只应用到直接位于其前面的列名。

因此如果想对多个列都进行降序,必须针对每一列加上 desc 关键字。

a 和 A 的排序方式则取决于数据库的设置方式。

第4课 过滤数据

使用 select 语句的 where 子句指定搜索条件。

一般只会根据特定操作或者特定需求提取表数据的子集。

需要指定搜索条件(search criteria)、过滤条件(filter condition)。

select prod_name, prod_price
from Products
where prod_price = 3.49;

数据类型

同时使用 order bywhere 子句时,应该让 order by 位于 where 之后。

  • where 子句操作符
操作符 说明
= 等于
<> 不等于
!= 不等于
< 小于
<= 小于等于
!< 不小于
> 大于
>= 大于等于
!> 不大于
between 在指定的2个值之间
is null 为 NULL 值

部分操作符冗余,可相互替换。

select vend_id, prod_name
from Products
where vend_id <> 'DDDDD';

单引号用来限定字符串,字符类型需要使用单引号。

数值无需单引号。

  • 范围值检查 between

范围的开始值和结束值

select * from Products
where prod_price between 5 and 10; -- 包括指定的开始值和结束值

  • 空值检查

列可否为空值,在创建表初期可指定。

NULL -- 空值(no value),与0、空字符串、空格等不同。

select * from Products
where prod_price is null;

过滤数据时,一定要验证被过滤列中含 NULL 的行是否出现在返回的数据中。

第5课 高级数据过滤

组合 where 子句建立高级搜索条件,使用 not 和 in 操作符。

  • 组合 where 子句

通过 and 子句或者 or 子句进行组合使用。

操作符(operator):用来联结或改变 where 子句中的关键字,也称为逻辑操作符(logical operator)。

select * from Products
where vend_id = 'DDDD' and prod_price <= 4; -- and 同时满足

多个条件之间都要使用 and 关键字。

order by 子句放在 where 子句之后。

select * from Products
where vend_id = 'DDDD' or prod_price <= 4; -- or 任一满足

DBMS 检索匹配任一条件的行,许多 DBMS 在 or where 子句的第一个条件满足情况下,就不再计算第二个条件。

and 和 or 两者可以结合以进行复杂、高级的过滤。

注意优先级。

select * from Products
where (vend_id = 'DDDD' or vend_id = 'FFFF')
      and prod_price = 10;

不要过分依赖系统默认的计算顺序,多使用圆括号。

  • in 操作符

in 操作符用来指定条件范围,范围内的每个条件都可以匹配。

select * from Products
where vend_id in ('DDD', 'FFFF')
order by prod_name;

类似于 or 的功能。

in 操作符更加清晰、直观。

in 性能高于 or 操作符。

in 可以包含其他 select 语句,能够更加动态地建立 where 子句。

  • not 操作符

否定其后所跟的任何条件。

select * from Products
where not vend_id = 'DDDD'; -- 也可以使用 <> 操作符完成

在复杂的语句中,使用 not 才有优势,并且与 in 操作符联合使用的时候,更显优势。

MariaDB 支持使用 not 否定 in、between 和 exists 子句。

总结:and、or、not、in、求值顺序。

第6课 用通配符进行过滤

使用通配符 * 、like 操作符进行通配搜索,以便对数据进行复杂过滤。

  • like 操作符

利用通配符,可以创建模糊搜索模式。

通配符(wildcard):用来匹配值的一部分特殊字符。

搜索模式(search pattern):由字面值 + 通配符或两者组合构成的搜索条件。

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

  • 百分号(%)通配符

表示任何字符出现任意次数。

select prod_id, prod_name
from Products
where prod_name like 'Fish%'; -- 以 Fish 开头的产品

Access 通配符:使用 * 而不是 %

不同的 DBMS 区分大小写,具体看配置。

select prod_id, prod_name
from Products
where prod_name like '%bean%'; -- 字符两端

字符中间也可以。

在匹配邮件地址的时候,很有用。例如:where email like 'b%@qq.com'

% 还能匹配 0 个字符

% 代表搜索模式中给定位置的 0 个、1 个或多个字符。

注意:空格的影响,是否匹配不到数据?

注意:NULL 无法匹配

  • 下划线(_)通配符

只匹配单个字符,而不是多个字符。

DB2 不支持通配符 _

Access 需要使用?而不是 _

select prod_id, prod_name
from Products
where prod_name like '_inch'; -- 单个字符 注意空格

  • 方括号([ ])通配符

指定字符集,必须匹配指定位置(通配符的位置)的一个字符。

Access 和 SQL Server 支持集合

select cust_contact
from Customers
where cust_contact like '[JM]%'
order by cust_contact;  -- 以J或M开头的名字

  • 前缀字符 ^ 表否定
select cust_contact
from Customers
where cust_contact like '[^JM]%'
order by cust_contact;  -- 不以J或M开头的名字

Access 中使用 '[!JM]' 进行检索

使用 not 操作符同样可以得到结果。not like

通配符的搜索将耗费更多的时间

注意:

  1. 不要过度的使用通配符;
  2. 尽量不要用在搜索模式的开始处,因为无法用到索引;
  3. 注意通配符的位置;

第7课 创建计算字段

  • 计算字段

关联、and、upper、sum、avg 等计算字段。

从数据库中检索出转换、计算或格式化过的数据。

字段(field):意思同列(column)

在数据库服务器上完成转换和格式化比在客户端中完成快得多。

  • 拼接字段

拼接(concatenate):将值联结在一起。

加号(+)或者 两个竖杆(||)表示。

Mysql 和 MariaDB 使用特殊的函数。

  1. Access 和 SQL Server 使用 + 号。
  2. DB2、Oracle、PostgreSQL 使用 || 。
  3. MySQL、MariaDB 使用 concat 函数。
select vend_name + '(' + vend_country + ')'
from Vendors
order by vend_name; -- 字符拼接 使用||同理

-- MySQL --
select concat(vend_name, '(', vend_country, ')')
from Vendors
order by vend_name;

  • 去空格 rtrim 函数
select rtrim(vend_name) + '(' + rtrim(vend_country) + ')'
from Vendors
order by vend_name;

rtrim 函数 -- 去掉值右边的所有空格

ltrim 函数 -- 去掉值左边的所有空格

trim 函数 -- 去掉值左右两边的所有空格

  • 使用别名 as

别名(alias)是一个字段或值的替换名。

别名使用 as 关键字赋予。

select rtrim(vend_name) + '(' + rtrim(vend_country) + ')' as vend_title
from Vendors
order by vend_name;

as 关键字是可选的,不过最好使用它。

导出列(derived column)

  • 执行算术计算
select prod_id,
       quantity,
       item_price,
       quantity * item_price as expanded_price
from OrderItems
where order_num = 20008;

基本算术操作符:

+-*/ 加减乘除、圆括号区分优先级

select 3 * 2; -- 测试数据

省略 from 子句之后就是简单的访问和处理表达式。

select now(); -- 返回当前时间

第8课 使用数据处理函数

不同的 DBMS 支持不同的函数,如何使用函数。

  • 函数

利用函数处理数据,例如:rtrim() 函数可以去掉字符串空格。

  1. 取字符串 mid() substring() sbustr()
  2. 数据类型转换 convert() cast()
  3. 取当前日期 curdate() sysydate() now() current_date() getdate() date()

SQL 函数不是可移植的。

可移植(portable):所编写的代码可以在多个系统上运行。

使用函数,应该做好代码注释。

  • 函数类别
  1. 文本字符串:删除、填充、转换大小写
  2. 算术运算:绝对值、代数运算
  3. 日期、时间:时间差、时间有效性
  4. 系统函数:登录用户等
  • 文本处理函数

rtrim() 函数:去除多余空格

upper() 函数:转换为大写

select vend_name,
       upper(vend_name) as vend_name_upcase
from Vendors
order by vend_name; -- 将文本转换为大写

常用文本处理函数:

left() 函数:返回字符串左边字符 -- Oracle 不支持

length() 函数:返回字符串长度 -- 同 len()datalength() 函数

upper() 函数:返回字符串大写 -- Access 使用 ucase() 函数

lower() 函数:返回字符串小写 -- Access 使用 lcase() 函数

ltrim()函数:去掉字符串左边空格

right()函数:返回字符串右边字符

rtrim() 函数:去掉字符串右边空格

soundex() 函数:返回字符串的 soundex 值

soundex 是一个将任何文本串转换为描述其语音表示的字母数字模式的算法。考虑其类似发音字符、音节,使其能对字符串进行发音比较而不是字母比较。

Microsoft Access 和 PostgreSQL 不支持 soundex( ) 函数。

SQLite 必须指定 SQLite_soundex 编译选项才支持。

select soundex('Michael Green'),
       soundex('Michelle Green')
from dual;  -- 发音模糊过滤
-- 1	M242	M242

Oracle 中没有 leftright 的写法,但可以使用 substr 函数进行字符截取处理。例如:

select substr('abcdefg', 1,5) from dual;
-- abcde

  • 日期和时间处理函数

日期和时间采用相应的数据类型存储在表中。

不同的 DBMS 采用不同的时间日期处理函数,可移植性最差。

-- SQL Server
select order_num from Orders
where datepart(yy, order_date) = 2012;
-- Access
select order_num from Orders
where datepart('yyyy', order_date) = 2012;
-- PostgreSQL
select order_num from Orders
where date_part('year', order_date) = 2012;
-- Oracle
select order_num from Orders
where to_number(to_char(order_date, 'yyyy')) = 2012; -- 提取日期 转换为数值 进行对比
-- Oracle between
select order_num from Orders
where order_date between to_date('01-01-2012')
and to_date('12-31-2012');
-- MySQL MariaDB
select order_num from Orders
where year(order_date) = 2012;
-- SQLite
select order_num from Orders
where strftime('%Y', order_date) = '2012';

datepart() 函数:返回日期的某一部分

大多数 DBMS 具有比较日期、执行基于日期的运算、选择日期格式等函数。

  • 数值处理函数

数值处理函数仅处理数值数据。

代数、三角或几何运算。

在各 DBMS 中,数值函数反而是最一致、最统一的函数。

常用数值处理函数:

abs() 返回绝对值

cos() 返回余弦值

sin() 返回正弦值

tan() 返回正切值

exp() 返回指数值

pi() 返回圆周率

sqrt() 返回平方根

第9课 汇总数据

利用 SQL 聚集函数汇总表的数据。

汇总表中的数据,而不需要实际数据本身。

聚集函数(aggregate function):对某些行运行的函数,计算并返回一个值。

avg() 平均值

count() 计数

max() 最大值

min() 最小值

sum() 求和

avg() 函数只能用于单个列,确定特定数值列的平均值。多个列必须使用多个 avg() 函数。

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

使用 count(*) 对表中行进行计数,不管列中是否为空值(NULL)还是非空值。

使用 count(column) 对特定列进行计数,忽略 NULL 值。

对非数值数据使用 max() 函数,返回排序后最后一行。忽略 NULL 行。

对非数值数据使用 min() 函数,返回排序后最前面一行。忽略 NULL 行。

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; -- 组合聚集函数

  • 聚集不同值

对所有行执行计算,指定 ALL 参数或不指定参数(因为 ALL 是默认行为)。

只包含不同的值,指定 distinct 参数。

Access 中不支持 distinct,必须使用子查询把 distinct 结果返回到外部查询。

select avg(distinct prod_price) as avg_price
from Products
where vend_id = 'AAAA';

distinct() 必须指定列名

有的 DBMS 支持其他参数,例如:top()top percent() 等。

别名应该采用新的名称,而不该使用已有的列名。

聚集函数很高效,比客户端计算快得多。

第10课 分组数据

分组数据,汇总表内容的子集。

select 语句子句:group by 子句和 having 子句。

使用 SQL 聚集函数可以汇总数据。

使用分组可以将数据分为多个逻辑组,对每个组进行聚集计算。

  • 分组
select vend_id,
       count(*) as num_prods
from Products
group by vend_id;

group by 子句指示 DBMS 分组数据,然后对每个组而不是整个结果集进行聚集。

  1. group by 子句可以包含任意数目的列,可以对分组进行嵌套,更细致的进行数据分组。
  2. 如果分组列中包含 NULL 值的行,则 NULL 将作为一个分组返回。
  3. group by 子句必须出现在 where 子句之后,order by 子句之前。

SQL Server 支持可选的 ALL 子句,返回所有的分组。

也可以通过相应位置指定列,例如: group by 2,1

  • 过滤分组

除了能用 group by 分组数据外,SQL 还允许过滤分组。

where 没有分组的概念。

必须使用 having 子句,类似于 where ,唯一的区别是 where 过滤行,而 having 过滤分组。

select cust_id,
       count(*) as orders
from Orders
group by cust_id
having count(*) >= 2; -- 过滤

where 子句在这里不起作用,因为过滤是基于分组聚集值,而不是特定行的值。

where 在数据分组前进行过滤,而 having 在数据分组后进行过滤。

select vend_id,
       count(*) as num_prods
from Products
where prdo_price >= 4
group by vend_id
having count(*) >= 2;

使用 having 时应该结合 group by 子句。而 where 子句用于标准的行级过滤。

  • 分组和排序

order by 对产生的输出排序,任意列都可以使用。

group by 对行分组,但输出可能不是分组的顺序。

使用 order by 排序保证数据正确排序的唯一方法。

select order_num,
       count(*) as items
from OrderItems
group by order_num
having count(*) >= 3
order by items, order_num; -- 先分组 后过滤 再排序

Access 不允许按别名排序,需要用实际的字段替代,例如:order by count(*), order_num。

  • select 子句顺序

在使用 select 子句时必须遵循的次序。

子句 说明 是否必须使用
select 要返回的列或表达式
from 从中检索数据的表 仅在从表选择数据时使用
where 行级过滤
group by 分组说明 仅在按组计算聚集时使用
having 组级过滤
order by 输出排序顺序

第11课 使用子查询

子查询,如何使用?

查询(query):一般指 select 语句。

SQL 子查询(subquery),即嵌套在其他查询中的查询。

MySQL 早期版本不支持子查询,从4.1版本开始引入。

将一条 select 语句返回的结果用于另一条 select 语句的 where 子句。

select cust_id
from Orders
where order_num in (select order_num
                    from OrderItems
                   where prod_id = 'AAAA'); -- 子查询

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

手动操作,写 SQL 称之为:硬编码。

格式化 SQL:把子查询分解为多行并进行适当的缩进,能极大地简化子查询的使用。

颜色编码:代码高亮。

多层嵌套查询:

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 = 'AAAA')); -- 子查询

可以无限嵌套查询。

作为子查询的 select 语句只能查询单个列。

效率很低。

  • 作为计算字段使用子查询

创建计算字段,作为子查询。

select cust_name,
       cust_state,
       (select count(*)
        from Orders
        where Orders.cust_id = Customers.cust_id) as orders
from Customers
order by cust_name;

where 子句完全限定列名,它指定表名和列名。

用1个句点分隔表名和列名。

必须限制有歧义的列。

出现冲突列名而引起的歧义性,会引起 DBMS 抛出错误信息。

完全限定列名

这种写法并非最有效的,后续的 join 语法效率更高!

第12课 联结表

联结的 select 语句。

SQL 能够在数据查询的执行中联结(join)表。

关系表、关系数据库设计基础知识。

  • 关系表

供应商表、产品信息表,分开存放。

数据一致性

数据库范式

关系数据库设计的基础、分解信息

唯一标识,主键(primary key),任何唯一值都可以。

不重复,不浪费存储空间。

如果需要变动,只需要更改一次。

可伸缩(scale)性强。

  • 联结

使用联结可以关联不同的表,返回一组输出。

联结并非物理实体,在实际数据库中并不存在。

DBMS 根据需要建立联结,在查询执行期间一直存在。

引用完整性,强制实施数据完整性规则。例如:外键。

select vend_name,
       prod_name,
       prod_price
from Vendors a,
     Products b
where a.vend_id = b.vend_id; -- 联结

使用 where 子句进行正确的联结。

完全限定列名。

联结的实际操作是将2个表的每一行都进行匹配,而 where 子句则是过滤满足条件的行。

笛卡尔积:(cartesian product)检索的数目为2个表行数相乘。

select vend_name,
       prod_name,
       prod_price
from Vendors a,
     Products b; -- 产生 N 行

因此,不要忘了 where 子句。

返回笛卡尔积的联结,也称为叉联结(cross join)。

  • 内联结

等值联结(equijoin),基于两个表之间的相等条件。

也称为内联结(inner join)。

select vend_name,
       prod_name,
       prod_price
from Vendors a
inner join Products b
on a.vend_id = b.vend_id;

传递给 on 的实际条件与传递给 where 相同。

ANSI SQL 规范首选 inner join 语法。

  • 联结多个表

SQL 不限制可以联结表的数目,在实际生产中,PostgreSQL 能关联10个表以上。

select prod_name,
       vend_name,
       prod_price,
       quantity
from OrderItems a, 
     Products b,
     Vendors c
where b.vend_id = c.vend_id
and a.prod_id = b.prod_id
and order_num = 20007; -- 多表关联

where 子句定义联结条件,过滤条件。

性能问题:联结很消耗资源,联结越多,性能越低。

第11课中的子查询写法可以使用联结写法替代。

性能可能会受操作类型、所使用的的 DBMS、表中数据量、是否存在索引或键等条件的影响。

第13课 创建高级联结

其他联结,表别名,对联结的表使用聚集函数等。

  • 使用表别名

缩短 SQL 语句

允许在一条 select 语句中多次使用相同的表

使用 as 作为别名

表别名不返回至客户端,不像列别名

  • 不同类型的联结

self-join 自联结

natural join 自然联结

outer join 外联结

  • 自联结

使用表别名可以在一条 select 中多次使用同一个表。

select cust_id,
       cust_name,
       cust_contact
from Customers
where cust_name = (select cust_name
                  from Customers
                  where cust_contact = 'Jim Jones');

通过子查询查找同公司下的用户。也可以使用自联结:

select a.cust_id,
       a.cust_name,
       a.cust_contact
from Customer a,
     Customer b
where a.cust_name = b.cust_name
and a.cust_contact = 'Jim Jones'; -- 简洁

使用表别名。联结后过滤。

  • 自然联结

标准的联结返回所有数据,相同的列甚至多次出现。自然联结排除多次出现,去重。

select a.*
       b.order_num,
       b.order_date,
       c.prod_id,
       c.quantity,
       c.item_price
from Customers as a,
     Orders as b,
     OrderItems as c
where a.cust_id = b.cust_id
and c.order_num = b.order_num
and prod_id = 'RGAN01';

第一个表使用通配符,其他表数据指定具体列。

  • 外联结

包含那些在相关表中没有关联行的行,这种联结成为外联结。

select a.cust_id,
       b.order_num
from Customers a
left outer join Orders b  -- 左连接
on a.cust_id = b.cust_id;

在使用 outer join 语法时,必须使用 right 或者 left 关键字指定包括其他行的表。

分别表示包含左、右数据表的所有数据行。

SQLite 支持 left outer join,但不支持 right outer join。

  • 全外联结(full outer join)

检索两个表中的所有行并关联那些可以关联的行。

select a.cust_id,
       b.order_num
from Customers a
full outer join Orders b
on a.cust_id = b.cust_id;

另外一种 cross jon 连接。

  • 使用带聚集函数的联结

聚集函数用来汇总数据。

select a.cust_id,
       count(b.order_num) as num_ord
from Customers a
inner join Orders b
on a.cust_id = b.cust_id
group by a.cust_id;

也可以与其他联结一起使用。

注意

  1. 切记检查联结的结果,记得测试后使用!!!
  2. 添加联结条件,否则会出现笛卡尔积。
  3. 减少多个表联结,减少故障。

第14课 组合查询

利用 union 操作符将多条 select 语句组合成一个结果集。

组合查询通常被称为并(union)或者复合查询(compound query)。

select cust_name,
       cust_contact,
       cust_email
from Customers
where cust_state in ('A', 'B', 'C')
union
select cust_name,
       cust_contact,
       cust_email
from Customers
where cust_name = 'xxxx';

union 指示 DBMS 执行这两条 select 语句,并把输出组合成一个查询结果集。

使用 union 组合 select 语句的数目具体没有限制。

注意考虑一下性能问题。

  • union 规则
  1. union 必须由两条或以上语句组成;
  2. 每个查询必须包含相同的列、表达式、聚集函数;
  3. 列数据类型必须兼容,例如:不同的数值类型;

union 从查询结果集中自动去除了重复的行。

  • union all

返回所有匹配行,不去重。

select cust_name,
       cust_contact,
       cust_email
from Customers
where cust_state in ('A', 'B', 'C')
union all
select cust_name,
       cust_contact,
       cust_email
from Customers
where cust_name = 'xxxx'; -- 不去重

使用 union 组合查询时,只能使用一条 order by 子句,且必须位于最后一条 select 语句之后。

select xxx from xxx
union all
select xxx from xxx
order by xxx; -- 排序所有结果

某些 DBMS 还支持另外两种 union:

  • except (minus) -- 只存在1表,不存在2表
  • intersect -- 都存在 取交集

使用 union 还可以组合不同表查询,结合别名使用。

第15课 插入数据

使用 insert 语句将数据插入表中。

  • 插入完整的行
  • 插入行的一部分
  • 插入某些查询的结果

使用 insert 语句进行插入需要特定的操作权限。

语法

insert into Customers
values('100','Toy','123',NULl,100);

必须给每一列提供一个值。如果某列没有值,必须使用 NULL 值填充,而且顺序应该依次出现。

into 关键字可选?某些 DBMS 支持。

这种语法适用于测试,单独插入某条数据时使用。不适用于大批量更新。

insert into Customers (cust_id, cust_name, cust_address, cust_zip, cust_email)
values('100','Toy','123',NULl,100);

明确指出列名,因此无须按顺序出现。

即使表结构发生变化,也不影响。

values 数目必须正确,才能成功插入。

  • 插入部分行

只给某些列提供值,给其他列不提供值。

insert into Customers(cust_id, cust_name)
values('100', 'Toy'); -- 插入部分行

省略的列将自动插入空值 NULL。

列必须允许为空。

  • 插入检索出的数据

insert select 语法也是合法的。

insert into Customers(cust_id, cust_name,xxxx,xxx,xxx)
select cust_id,
       cust_name,
       xxxx,
       xxx,
       xxx
from CustNew;

亲测:Oracle可使用!

insert select 中可以包含 where 子句,以过滤插入的数据。

可以插入多行。

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

创建新的表,可以使用 select into 语句。

表不存在,新建。

insert select -- 导出数据
select into -- 导入数据

select * into CustCopy
from Customers;

MariaDB、MySQL、Oracle、PostgreSQL 和 SQLite 使用以下语法:

create table temp_xxx as
select * from xxx;

可以结合 where 和 group by 进行使用,可以联结多个表插入数据,但只能插入一个表。

第16课 更新和删除数据

  • 更新数据

使用 update 语句更新数据。

不要省略 where 子句。

更新需要特定的安全权限。

update Customers
set cust_email = 'xxxxxx'
where cust_id = '100';

set 关键字用于将新值赋给被更新的列。

没有 where 将更新所有的行。

update Customers
set cust_email = 'xxxxxx',
    cust_contact = 'Sam'
where cust_id = '100';

利用键-值对更新多个值。

在 update 语句中还可以使用子查询。

使用 from 子句,用一个表的数据更新另一个表的行。

要删除某个列的值,可设置为 NULL。

update Customers
set cust_email = NULL
where cust_id = '200';

NULL 与空字符串不同。

  • 删除数据

使用 delete 语句删除数据。

不要省略 where 语句。

先通过 select 测试子集之后再修改为 delete 语句进行数据行的删除。

delete from Customers
where cust_id = '100';

where 进行数据过滤。

友好的外键:可以使用外键来严格实施关系,实施引用完整性。防止 delete 语句删除必要的数据。

delete 是删除整行数据,因此不需要列名或者通配符。

删除指定列可以通过 update 进行。

delete 不删除表本身。

  • 删除所有行

delete 删除部分行

更快的清空所有行可以使用 truncate table 语句,速度更快,因为不记录数据的变动。

  • 原则
  1. 记得带 where 子句;
  2. 主键;
  3. 先 select 测试,后 delete 删除;
  4. 使用强制实施引用完整性的数据库;
  5. 确认之后再提交,commit;

第17课 创建和操纵表

创建、更改和删除表。

  • 创建表

SQL 还可以用来执行数据库和表的所有操作。

通过 create table 语句。

通过交互式界面工具创建表会自动生成并执行相应的 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
); -- 数据类型及兼容性

不同 DBMS ,必须书写不同的语句。

语句格式化:多行、缩进。

每个表列要么是 NULL 列,要么是 not NULL 列。NULL 为默认设置。

只有不允许 NULL 值的列可作为主键、唯一标识。

注意NULL 值 && 空字符串''的区别。

指定默认值:使用关键字 default 指定。

create table temp_cccc
(
	order_num integer not null,
    quantity integer not null default 1
);

默认值经常用于日期或时间戳列。引用系统日期的函数或变量,将系统日期用作默认日期。

-- 建表
create table temp_cccccc
(
  id integer not null,
  money varchar(10),
  date2 date default sysdate  -- 系统时间 默认值
);
-- 插入数据
insert into temp_cccccc(id) values(2)
-- 查询
select * from temp_cccccc
-- 1	2		2020/1/17 16:56:53

-- 默认获取系统日期
default current_date() -- MySQL
default sysdate -- Oracle
default getdate() -- SQL Server
default now() -- Access

使用 default 而不是 NULL 值,有利于计算和数据分组。

  • 更新表

更新表定义,可以使用 alter table 语句。

不要在表中包含数据时对其进行更新。

对表增加列的数据类型(以及 NULL 和 default 的使用)有所限制。

重命名表中的列。

修改表结构:表名,列。

-- 新增一个列
alter table temp_cccccc
add data_desc varchar2(10);

更改或删除列、增加约束或增减键,使用类似语法。

-- 删除一个列
alter table temp_cccccc
drop column money;

以下操作为 Oracle 中常用的修改表结构用法,注意部分操作需要字段内容为空时方可操作。

------------ Oracle 更新表结构 ------------
-- 1.添加字段
alter table temp_xxx add col_name varchar2(10);
-- 2.删除字段
alter table temp_xxx drop column col_name;
-- 3.修改字段类型
alter table temp_xxx modify col_name integer;
-- 4.添加备注
comment on column 库名.表名.字段名 is '输入的备注';

复杂的表结构修改一般需要手动删除,重新创建表。

再根据需要,重新创建触发器、存储过程、索引和外键。

alter table 进行前备份表,防止意外。

  • 删除表

使用 drop table 语句删除表。

drop table temp_cccc;

永久删除改表。

drop table temp_ccccc purge; -- 删除并清空回收站

启用关系规则防止意外删除有用的表。

  • 重命名表

使用 rename 语句进行重命名表名。

rename temp_cccccc to temp_xxx; -- 重命名
alter table temp_xxx rename to temp_yyy; -- 也可以

create table temp_zzz as
select * from temp_yyy;
drop table temp_yyy; -- 备份再删除

切记:备份!!备份!!备份!!

第18课 使用视图

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

利用视图可以简化某些 SQL 操作。

Microsoft Access 不支持视图。

任何需要明细数据的人不可能都要详细的去了解表结构,如何关联和对表进行联结。

作为视图,包含的只是查询

  • 重用 SQL 语句
  • 简化复杂的 SQL 操作
  • 使用表的一部分而不是整个表
  • 保护数据
  • 更改数据格式和表示

可以对视图执行 select 操作,过滤和排序数据,将视图联结其他视图或表,甚至添加和更新数据。

性能问题:每次使用视图都必须处理查询执行时需要的所有检索。

视图必须唯一命名。

创建视图,必须具有足够的访问权限。

视图可以嵌套。

许多 DBMS 禁止在视图查询中使用 order by 子句。 -- 测试一番? Oracle 可以排序。

如果列是计算字段,需要使用别名,例如:count(*) as cnt。

视图不能索引、也不能有关联的触发器或默认值。

  • 创建视图

使用 create view 语句创建视图。

  • 删除视图

使用 drop view view_name 删除视图。

利用视图简化复杂的联结。

create view ProductCustomers as
select cust_name,
       cust_contact,
       prod_id
from Customers a,
     Orders b,
     OrderItems c
where a.cust_id = b.cust_id
and b.order_num = c.order_num;  -- 创建视图

使用查询。

select * from ProductCustomers
where prod_id = 'xxx';

但 DBMS 处理查询时,将指定的 where 子句添加到视图查询中已有的 where 子句中,正确的过滤数据。

利用视图,可以一次性编写基础的 SQL,然后根据需要多次使用。

视图需要进行维护。

另一用途是重新格式化检索的数据。

create view VendorLocations as
select rtrim(vend_name) || '(' || rtrim(vend_country) || ')' as vend_title
from Vendors;

创建视图之后,通过 where 过滤数据。

视图内与传递给视图的 where 子句将自动组合。

使用视图与计算字段。

create view OrderItemsExpanded as
select order_num,
       prod_id,
       quantity,
       item_price,
       quantity * item_price as expanded_price
from OrderItems;

正确使用视图,可以极大地简化复杂数据的处理。

视图为虚拟的表。

重新格式化或保护基础数据。

第19课 使用存储过程

存储过程是什么?为甚么要使用存储过程?如何使用?创建和使用存储过程的基本语法。

  • 存储过程

存储过程(Stored Procedure) 是在大型数据库系统中,一组为了完成特定功能的 SQL 语句集,存储在数据库中,经过第一次编译后再次调用不需要再次编译,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来调用存储过程。

针对许多表的多条 SQL 语句,多种判断,有条件的执行其他语句。

可以视为批文件。

Microsoft Access 和 SQLite 不支持存储过程。

  • 为什么要使用存储过程
  1. 通过把处理封装在一个单元中,可以简化复杂的操作。
  2. 保证数据的一致性,防止错误。
  3. 修改方便,表名变动只需修改一次。安全性。
  4. 存储过程通常是以编译过的形式存储,所以 DBMS 处理命令所需工作量少,提高性能。
  5. 灵活使用。

简单、安全、高性能!!!

缺陷:不同 DBMS 语法不同,可移植性差!

  • 执行存储过程

通过 execute 执行存储过程,execute 接受存储过程名和需要传递的任何参数。

execute procedure_name('a','b','c','d');

执行存储过程,插入数据。ID 自动生成。

  1. 参数可选,具有不提供参数时的默认值;
  2. 不安次序给出参数,以“参数=值”的方式给出参数值;
  3. 输出参数;
  4. 用 select 检索数据;
  5. 返回代码;
  • 创建存储过程
-- Oracle -- 执行不通过 待修改
create procedure MailListCount (
	ListCount out integer  -- 返回一个值
)
is 
v_rows integer;
begin
	select count(*) into v_rows
	from Customers
	where cust_email is not null;
	ListCount := v_rows;
end;

in -- 传递值给存储过程

out -- 从存储过程返回值

inout -- 既输入又输出

begin、end -- 存储过程代码

执行存储过程。

-- Oracle --
call MailListCount();
-- 或者 --
var ReturnValue number
exec MailListCount(:ReturnValue);
select ReturnValue;

declare 语句声明变量

@开头 为局部变量

Return @cnt 返回调用程序

-- SQL Server --
create procedure MailingListCount
as
declare @cnt integer
select @cnt = count(*) 
from Customers
where cust_email is not null;
return @cnt;
-- 调用 --
declare @ReturnValue int
execute @ReturnValue = MailingListCount;
select @ReturnValue;

另外的例子,在数据库表中插入数据。

-- SQL Server --
create procedure NewOrder @cust_id char(10)
as
declare @order_num integer
select @order_max = max(order_num)
from Orders
select @order_num = @order_num + 1 -- 自动生成
insert into Orders(order_num,
                   order_date,
                   cust_id)
       values(@order_num,
              getdate(),
              @cust_id)
return @order_num;

局部变量保存最大订单号,和系统日期。

多加注释。

-- SQL Server --
create procedure NewOrder @cust_id char(10)
as
insert into Orders(cust_id)
values(@cust_id)
select order_num = @@identity;

自动增量的列为标识字段(identity field),或者自动编号(auto number)或序列(sequence)。

结合 DBMS 特性进行编写存储过程。

第20课 管理事务处理

利用 commit 和 rollback 语句管理事务处理。

  • 事务处理

使用事务处理(transaction processing),通过确保成批的 SQL 操作要么完全执行,要么完全不执行,来维护数据库的完整性。

设计良好的数据库模式,通过主键的唯一ID互相关联。

假设由于某种数据库故障(超出磁盘空间、安全限制、表锁等),插入数据过程无法完成,将该如何?

—— 事务处理,是一种机制。

如果没有错误发生,整组语句提交给数据库表;

如果发生错误,则进行回退(撤销),将数据库恢复到某个已知且安全的状态;

  1. 事务(transaction)指一组 SQL 语句;
  2. 回退(rollbak)指撤销指定 SQL 语句的过程;
  3. 提交(commit)指将未存储的 SQL 语句结果写入数据库表;
  4. 保留点(savepoint)指事务处理中设置的临时占位符(placeholder),可以对它发布回退;

事务处理用来管理 insert、update 和 delete 语句。

不能回退 select 语句,也没必要。

不能回退 create 和 drop 操作。

  • 控制事务处理

管理事务的关键在于将 SQL 语分解为逻辑块,并明确规定数据何时该回退?何时不应该回退?

-- SQL Server --
begin transaction
...
commit transaction

中间的 SQL 必须完全执行或者完全不执行。

-- MariaDB or MySQL --
start transaction
...

-- Oracle --
set transaction
...
-- PostgreSQL --
begin
...

事务一直存在,没有明确标识事务结束,直到被中断。

通常,commit 用于保存更改,rollback 用于撤销。

select * from temp_cwh_test;
delete from temp_cwh_test;
rollback; -- 回滚

一般的 SQL 语句都是针对数据库表直接执行和编写的,这就是所谓的隐式提交(implicit commit),即提交操作是自动进行的。

-- SQL Server --
begin transaction
delete from OrderItems where order_num = 12345
delete from Orders where order_num = 12345
commit transaction -- 保证2条语句都执行成功时才提交
-- Oracle --
set transaction
...
...
commit;

  • 使用保留点

复杂的事务可能需要部分提交或回退。

要支持回退部分事务,必须在事务处理块中的合适位置放置占位符。

在 SQL 中,这些占位符称为保留点。

创建占位符,可以使用 savepoint 语句。

-- MariaDB/MySQL/Oracle --
savepoint deletel;
-- SQL Server --
save transaction deletel;

每个保留点都要有能够标识的唯一名字。

-- SQL Server --
rollback transaction deletel;
-- Oracel --
rollback to deletel;

完整的例子:

begin transaction
insert into Customers(cust_id, cust_name)
values('100','Toy');
save transaction StartOrder;
insert into Orders(order_num, order_date, cust_id)
values(200,'2020/12/1','100');
if @@error <> 0 rollback transaction StartOrder;
insert into OrderItems(order_num, order_item, prod_id, quantity, item_price)
values(100,1,'ds',100,5,49);
if @@error <> 0 rollbak transaction StartOrder;
insert into OrderItems(order_num, order_item, prod_id, quantity, item_price)
values(100,2,'xx',100,10.99);
if @@error <> 0 rollback transaction StartOrder;
commit transaction

定义保留点,如果操作失败则事务处理能够回滚。

保留点越多越能灵活处理。

事务是必须完整执行的 SQL 语句块。利用 commit 和 rollback 语句进行管理。保留点。

第21课 使用游标

如何使用游标?

结果集(result set):SQL 查询所检索出的结果。

游标(cursor)是一个存储在 DBMS 服务器上的数据库查询,不是一条 select 语句,而是被该语句检索出来的结果集。

有了游标之后,可以根据需要滚动或浏览其中的数据。

能够标记游标只读,使数据能读取,不能更新和删除。

能够控制定向操作(向前、向后、第一、最后、绝对位置、相对位置等)。

Microsoft Access 不支持游标。

SQLite 支持的游标称为步骤(step)。

  • 使用游标

声明游标,定义 select 语句和游标选项。

打开游标以供使用。

根据需要检索各行。

结束游标使用后,关闭游标,释放游标。

  • 创建游标

使用 declare 语句创建游标。

-- MySQL --
declare CustCursor cursor
for 
select * from Customers
where cust_email is null;

-- Oracle --
declare cursor CustCursor
is
select * from Customers
where cust_email is null;

使用 open cursor 语句打开游标。

open cursor CustCursor;

执行查询,存储检索出的数据以供浏览和滚动。

利用 fetch 语句访问游标数据。

-- Oracle --
declare type CustCursor is ref cursor
	return Customers%ROWTYPE;
declare CustRecord Customers%ROWTYPE
begin
	open CustCursor;
	fetch CustCursor into CustRecord;
	close CustCursor;
end; -- fetch 检索当前行,放到声明的变量中,不做任何处理

-- Oracle --
declare type CustCursor is ref cursor
	return Customers%ROWTYPE;
declare CustRecord Customers%ROWTYPE
begin
	open CustCursor;
	loop
		fetch CustCursor into CustRecord;
		exit when CustCursor%NOTFOUND;
		...
	end loop;
	close CustCursor;
end; -- 循环所有行

-- SQL Server --
declare @cust_id char(10),
        @cust_name char(50),
        @cust_address char(50),
        @cust_city char(50),
        @cust_state char(50),
        @cust_zip char(10),
        @cust_email char(255)
open CustCursor
fetch next from CustCursor
into @cust_id,@cust_name,@cust_address,@cust_city,@cust_state,@cust_zip,@cust_email
while @@fetch_status = 0
begin
	fetch next from CustCursor
		into @cust_id,@cust_name,@cust_address,@cust_city,@cust_state,@cust_zip,@cust_email
end
close CustCursor

声明变量,fetch 语句检索一行并保存。

while 循环处理下一行。

  • 关闭游标

释放游标所占用的资源。

-- Oracle --
close CustCursor;
-- SQL Server --
close CustCursor
deallocate cursor CustCursor;

先声明,后打开,再使用,记得关闭!

第22课 高级 SQL 特性

高级数据处理特性:约束、索引和触发器。

  • 约束

关联表和引用完整性(referential integrity)。

关系数据库设计

数据库完整性规则

约束(constraint):管理如何插入或处理数据库数据的规则。

DBMS 通过在数据库表上施加约束来实施引用完整性。

通过使用 create table 或者 alter table 语句。

  • 主键

特殊的约束,保证一列中的值是唯一的,而且永不改动。

唯一标识每一行。

任意两行的主键值都不相同。

主键值唯一,且不为 NULL 值。

主键值从不修改或更新。

主键值不能重用。

create table Vendors
(
  vend_id char(10) not null primary key,
  vend_name char(50) not null,
  vend_state char(50) null,
  vend_country char(50) null
);

添加关键字 primary key ,使其成为主键。

alter table Vendors
add constraint primary key (vend_id); -- 添加约束

SQL Lite 不允许使用 alter table 定义键,要求初始的 create table 语句中定义。

  • 外键

外键是表中的一列,其值必须在另一表的主键中。

外键是保证引用完整性的重要部分。

create table Orders
(
	order_num integer not null primary key,
    order_date datetime not null,
    cust_id char(10) not null references Customers(cust_id)
)

使用 references 关键字,建立依赖。或者:

alter table Orders
add constraint
foreign key (cust_id) references Customers (cust_id);

外键有助于防止意外删除。

有的 DBMS 支持级联删除(cascading delete)的特性。

  • 唯一约束

用来保证一列中的数据是唯一的,类似于主键。但存在区别:

表可以多个唯一约束,但只允许一个主键。

可包含 NULL 值。

可修改或更新。

可重复使用。

不能用来定义外键。

可以通过在列上定义 unique 约束做到,也可以单独使用 constraint 定义。

  • 检查约束

检查约束保证一列中的数据满足一组指定的条件。

检查最小、最大值。

指定范围。

只允许特定的值。

DBMS 会拒绝任何无效的数据。

create table OrderItems
(
	order_num integer not null,
    order_item integer not null,
    quantity integer not null check (quantity >0)
);

或者

add constraint check(gender like '[MF]')

用户定义数据类型。

  • 索引

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

数据库的索引。类似于书籍的目录。

主键数据总是排序的,因此按主键检索特定行总是一种快速有效的操作。

可以在一个或多个列上定义索引,使 DBMS 保存内容为一个排序的列表。

注意:

  1. 索引改善检索操作的性能,但降低了数据插入、修改和删除的性能;执行这些操作时,DBMS 必须动态地更新索引;
  2. 索引数据可能要占用大量的存储空间;
  3. 不是所有数据都适合做索引;
  4. 索引用于数据过滤和数据排序;
  5. 可以定义多个列,组合索引;

索引使用 create index 语句创建。

create index prod_name_ind
on Products(prod_name);

索引必须唯一命名。

定期检查索引,优化调整索引。

  • 触发器

触发器是特殊的存储过程,它在特定的数据库活动发生时自动执行。

触发器可以与特定表上的 insert、update 和 delete 操作(或组合)相关联。

触发器与单个表相关联。

触发器可在特定操作执行之前或之后执行。

保证数据一致。

基于某个表的变动在其他表上执行活动。

进行额外的验证并根据需要回退数据。

-- SQL Server --
create trigger customer_state
on Customers
for insert, update
as
update Customers
set cust_state = upper(cust_state)
where Customers.cust_id = inserted.cust_id;

-- Oracle --
create trigger customer_state
after insert or update
for each row
begin
update Customers
set cust_state = upper(cust_state)
where Customers.cust_id = :OLD.cust_id
end;

约束比触发器更快。

  • 数据库安全

数据安全

用户访问权限

用户授权和身份确认

grant -- 授权
revoke -- 回收权限

总结:约束、索引、触发器、授权。

附录

基本数据库设计

表中存放的信息

表与表的关联

行中数据分解

玩具经销商使用的订单录入系统的组成部分:

  • 管理供应商
  • 管理产品目录
  • 管理顾客列表
  • 录入顾客订单

5个表

  1. Vendors表——销售产品的供应商

    所有的表都应该有主键

  2. Products表——产品目录

    为实施引用完整性,应该定义外键,关联到Vendors表

  3. Customers表——顾客信息

  4. Orders表——顾客订单

  5. OrderItems表——订单的实际物品

使用关系图 E-R图说明数据库表的关联方式。

  • 配置 ODBC 数据源

使用 ODBC 进行数据库集成。

ODBC 是一个标准,能使客户端应用与不同的后端数据库或基础数据库引擎交互。

数据:

  • create.txt 创建5个数据库表(定义所有的主键和外键约束)
  • populate.txt 填充数据库表的insert语句
posted @ 2020-01-21 11:37  Hider1214  阅读(622)  评论(0编辑  收藏  举报