《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 by
和 where
子句时,应该让 order by
位于 where
之后。
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
通配符的搜索将耗费更多的时间
不要过度的使用通配符;
尽量不要用在搜索模式的开始处,因为无法用到索引;
注意通配符的位置;
第7课 创建计算字段
关联、and、upper、sum、avg 等计算字段。
从数据库中检索出转换、计算或格式化过的数据。
字段(field):意思同列(column)
在数据库服务器上完成转换和格式化比在客户端中完成快得多。
拼接(concatenate):将值联结在一起。
加号(+)或者 两个竖杆(||)表示。
Mysql 和 MariaDB 使用特殊的函数。
Access 和 SQL Server 使用 + 号。
DB2、Oracle、PostgreSQL 使用 || 。
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()
函数可以去掉字符串空格。
取字符串 mid()
substring()
sbustr()
数据类型转换 convert()
cast()
取当前日期 curdate()
sysydate()
now()
current_date()
getdate()
date()
SQL 函数不是可移植的。
可移植(portable):所编写的代码可以在多个系统上运行。
使用函数,应该做好代码注释。
文本字符串:删除、填充、转换大小写
算术运算:绝对值、代数运算
日期、时间:时间差、时间有效性
系统函数:登录用户等
文本处理函数
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
中没有 left
和 right
的写法,但可以使用 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 分组数据,然后对每个组而不是整个结果集进行聚集。
group by
子句可以包含任意数目的列,可以对分组进行嵌套,更细致的进行数据分组。
如果分组列中包含 NULL 值的行,则 NULL 将作为一个分组返回。
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
子句时必须遵循的次序。
是否必须使用
完全限定列名。
联结的实际操作是将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;
也可以与其他联结一起使用。
切记检查联结的结果,记得测试后使用!!!
添加联结条件,否则会出现笛卡尔积。
减少多个表联结,减少故障。
第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 规则
union 必须由两条或以上语句组成;
每个查询必须包含相同的列、表达式、聚集函数;
列数据类型必须兼容,例如:不同的数值类型;
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,
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 语句,速度更快,因为不记录数据的变动。
记得带 where 子句;
先 select 测试,后 delete 删除;
使用强制实施引用完整性的数据库;
确认之后再提交,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 不支持存储过程。
为什么要使用存储过程
通过把处理封装在一个单元中,可以简化复杂的操作。
保证数据的一致性,防止错误。
修改方便,表名变动只需修改一次。安全性。
存储过程通常是以编译过的形式存储,所以 DBMS 处理命令所需工作量少,提高性能。
灵活使用。
简单、安全、高性能!!!
缺陷:不同 DBMS 语法不同,可移植性差!
执行存储过程
通过 execute 执行存储过程,execute 接受存储过程名和需要传递的任何参数。
execute procedure_name('a','b','c','d');
执行存储过程,插入数据。ID 自动生成。
参数可选,具有不提供参数时的默认值;
不安次序给出参数,以“参数=值”的方式给出参数值;
输出参数;
用 select 检索数据;
返回代码;
创建存储过程
-- Oracle -- 执行不通过 待修改
create procedure MailListCount (
ListCount out integer -- 返回一个值
v_rows integer;
begin
select count(*) into v_rows
from Customers
where cust_email is not null;
ListCount := v_rows;
in -- 传递值给存储过程
out -- 从存储过程返回值
inout -- 既输入又输出
begin、end -- 存储过程代码
执行存储过程。
-- Oracle --
call MailListCount();
-- 或者 --
var ReturnValue number
exec MailListCount(:ReturnValue);
select ReturnValue;
declare
语句声明变量
@开头
为局部变量
Return @cnt
返回调用程序
-- SQL Server --
create procedure MailingListCount
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)
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)
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互相关联。
假设由于某种数据库故障(超出磁盘空间、安全限制、表锁等),插入数据过程无法完成,将该如何?
—— 事务处理,是一种机制。
如果没有错误发生,整组语句提交给数据库表;
如果发生错误,则进行回退(撤销),将数据库恢复到某个已知且安全的状态;
事务(transaction)指一组 SQL 语句;
回退(rollbak)指撤销指定 SQL 语句的过程;
提交(commit)指将未存储的 SQL 语句结果写入数据库表;
保留点(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
select * from Customers
where cust_email is null;
-- Oracle --
declare cursor CustCursor
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;
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
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 保存内容为一个排序的列表。
索引改善检索操作的性能,但降低了数据插入、修改和删除的性能;执行这些操作时,DBMS 必须动态地更新索引;
索引数据可能要占用大量的存储空间;
不是所有数据都适合做索引;
索引用于数据过滤和数据排序;
可以定义多个列,组合索引;
索引使用 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
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
约束比触发器更快。
数据库安全
用户访问权限
用户授权和身份确认
grant -- 授权
revoke -- 回收权限
总结:约束、索引、触发器、授权。
基本数据库设计
表中存放的信息
表与表的关联
行中数据分解
玩具经销商使用的订单录入系统的组成部分:
管理供应商
管理产品目录
管理顾客列表
录入顾客订单
Vendors表——销售产品的供应商
所有的表都应该有主键
Products表——产品目录
为实施引用完整性,应该定义外键,关联到Vendors表
Customers表——顾客信息
Orders表——顾客订单
OrderItems表——订单的实际物品
使用关系图 E-R图说明数据库表的关联方式。
配置 ODBC 数据源
使用 ODBC 进行数据库集成。
ODBC 是一个标准,能使客户端应用与不同的后端数据库或基础数据库引擎交互。
create.txt 创建5个数据库表(定义所有的主键和外键约束)
populate.txt 填充数据库表的insert语句