SQL必知必会

先来几张图

前言

@author lgj
在读电子版<<SQL必知必会>> 第4版时,做了下笔记。供以后自己或者其他学习者参考。
电子版<<SQL必知必会>>和书中使用的数据库和表的源代码,
请参看地址:https://github.com/thinkingfioa/Notes/tree/master/sql/SQL%E5%BF%85%E7%9F%A5%E5%BF%85%E4%BC%9A4
评价 本书适合入门,对数据库查询部分讲解的比较好,之后的增删改以及数据库的其他特性讲解的并不出众,需要配合其他资料进一步学习。

第2课 检索数据

2.3 检索多个列

select column1, column2, column3 from tableName;

2.4 检索所有的列

select * from products;

注:
1. 缺点:使用通配符(*),除非确实需要查询表的每一列,否则可能会降低检索和应用的性能.
2. 优点:能检索出表的不明确的列。
2.5 检索不同的值

select distinct vend_id from products;

注:
1. distinct关键字作用于作用于其后的所有的列,不仅仅是其后的一列。
2. 如:select distinct vend_id,prod_price from products;那么这两列vend_id, prod_price都同,才会合并。
2.6 限制结果
  • select prod_name from products limit 2; 返回结果只有2行。
  • select prod_name from products limit 2 offset 5; 返6,7这2行数据。
  • select prod_name from products limit 2 offset 5; 可以简写成 select prod_name from products limit 5,2;
注:
1. 第一个被检索的行是第0行,而不是第1行。因此,LIMIT 1 OFFSET 1会检索第2行,而不是第1行.
2. MySQL和MariaDB支持简化版的LIMIT 4 OFFSET 3语句,即LIMIT 3,4。
2.7 使用注释
  1. 单行注释: #
  2. 多行注释: /*. ... */

第3课 排序检索数据

使用Select子句的Order by子句,根据需要检索排序出的数据
3.1 排序数据

使用Order by语句排序Select检索出来的语句, Order by子句取一个或多个列的名字。

select prod_name, prod_price from products order by prod_price;

注:Order by子句, 必须是select的最后一条语句。
3.2 按多个列排序

select prod_name, prod_price, prod_id from products order by prod_price, prod_name;

3.3 按列位置排序

select prod_name, prod_price, prod_id from products order by 2, 3;

注:
1. order by 后面可以接数字,比如:2代表的是prod_price, 3代表的是prod_id。不推荐使用这种方式。
3.4 指定排序方向

ASC 升序(默认),DESC 降序。

  • select prod_name, prod_price, prod_id from products order by prod_price DESC, prod_name;
  • 如果DESC降序,必须对所有需要降序的列都要加上。

第4课 过滤数据

4.2.3 范围值检查
  • select prod_name, prod_price, prod_id from products where prod_price between 4 and 10;
4.2.4 空值检查
  • select * from customers where cust_email is NULL; 空判断过滤数据
  • select * from customers where cust_email is not NULL; 非空判断过滤数据
is null: 当列的值是 null,此运算符返回 true。
is not null: 当列的值不为 null, 运算符返回 true。
<=>: 比较操作符(不同于=运算符),当比较的的两个值为 null 时返回 true。
关于 null 的条件比较运算是比较特殊的。你不能使用 = null 或 != null 在列中查找 null 值 。
在 mysql 中,null 值与任何其它值的比较(即使是 null)永远返回 false,即 null = null 返回false 。

第5课 高级数据过滤

5.1.3 组合where子句
  1. OR操作符和AND操作符。
select prod_name, prod_price, vend_id from products where vend_id = '1001' OR vend_id = '1005' and prod_price >= 10;

结果:

prod_name prod_price vend_id
.5 ton anvil 5.99 1001
1 ton anvil 9.99 1001
2 ton anvil 14.99 1001
JetPack 1000 35.00 1005
JetPack 2000 55.00 1005

分析:

OR操作符和AND操作符优先级不同。AND操作符的优先级 > OR操作符的优先级。所以上面的sql变成了:
select prod_name, prod_price, vend_id from products where vend_id = '1001' OR (vend_id = '1005' and prod_price >= 10 );
5.2 IN操作符
  • select prod_name, prod_price, vend_id from products where vend_id in ('1001', '1003') order by prod_price DESC;

IN操作符的优点

  1. IN操作符一般比一组OR操作符更快
  2. IN最大的优点是:可以包含其他SELECT语句,能够动态的建立WHERE子句,如下:
select a.* from student a join score b on a.s_id=b.s_id where b.c_id in(select c_id from course where t_id =(select t_id from teacher where t_name = '张三'));
5.3 NOT操作符

Not操作符用作否定其后所跟的任何条件。

  • select * from products where Not vend_id = 1001 and prod_name = 'Safe';
prod_id vend_id prod_name prod_price prod_desc
SAFE 1003 Safe 50.00 Safe with combination lock
注:Not在这里只能作用与vend_id.

第6课 使用通配符进行过滤

6.1.1 百分号(%)通配符

在搜索串中,%表示任何字符出现任意次数。代表搜索模式中给定位置0个,1个,多个字符。排除匹配NULL的行。

  • select prod_id, prod_name from products where prod_name like 'Jet%';
6.1.2 下划线(_)通配符

在搜索串中,下划线(_)只匹配单个字符,而不是多个字符。只能是一个字符。

  • select prod_id, prod_name from products where prod_name like 'JetPack _000';
6.1.3 方括号([])通配符

只有微软的Access和SQL server支持。

6.2 使用通配符的技巧
  1. 要知道,使用通配符可能比其他的搜索耗费更长的时间,对数据库的压力更大。
  2. 不要过度使用通配符,在其他操作符能达到效果的情况下,尽量不要使用通配符。
  3. 尽量不要把通配符放在搜索模式的开始处,把通配符置于开始处,搜索起来最慢的。

第7课 创建计算字段

7.1 计算字段

计算字段是运行时在SELECT语句内创建的。不同于数据库中表的列

7.2 拼接字段

将表中的列数据,拼接到一起。将值联结在一起(将一个值附加到另一个值), 构成单个值。如:prod_name(prod_price).

拼接的操作符:
1. Access和SQL Server使用+号
2. DB2、Oracle、PostgreSQL、SQLite和Open Office Base使用||
3. MySQL和MariaDB中,必须使用特殊的函数。eg: concat(prod_name, '(', prod_price, ')')
Trim函数
1. RTRIM(String)函数去掉字符串右边的空格。
2. LTRIM(String)函数去掉字符串左边的空格。
3. TRIM(String)函数去掉字符串左边和右边空格。
As别名。重新为列起一个新的名字。
1.  select concat(prod_name, '(', vend_id, ')') as price_info from products;
7.3 执行算术计算

算术运算符:加,减,乘,除

计算商品共卖出多少钱
1. select prod_id, quantity, item_price, quantity * item_price as expanded_price from orderitems where order_num = 20005;

第8课 使用数据处理函数

8.1 函数

SQL中函数,各个DBMS所支持的都不同,如果使用过多的函数,可能会带来可移植问题。

8.2 使用函数
大部分SQL实现支持以下类型的函数
1. 用于处理文本字符串(如删除,填充值,转换大小写等)的文本函数。
2. 用于在数值数据上进行的算术操作(如返回绝对值,进行代数运算)的数值函数。
3. 用于处理日期和时间值,并从这些值中提取特定成分(如返回两个日期之差,检查日期有效性)的日期和时间函数。
4. 返回DBMS正使用的特殊信息(如返回用户登录信息)的系统函数。
8.2.1 文本处理函数

常见的文本处理函数

函数 说明
LEFT()(或使用子字符串函数) 返回字符串左边的字符
RIGHT()(或使用子字符串函数) 返回字符串右边的字符
LOWER()(Access使用LCASE()) 将字符串转换为小写
UPPER()(Access使用UCASE()) 将字符串转换为大写
LTRIM() 去掉字符串左边的空格
RTRIM() 去掉字符串右边的空格
TRIM()、去掉字符串左边和右边的空格
SOUNDEX() 返回字符串的SOUNDEX值(读音相同)
LENGTH()(也使用DATALENGTH()或LEN()) 返回字符串长度

解释文本处理函数:SOUNDEX()

SOUNDEX()函数是一种和根据读音相同来比较的
1. SELECT cust_name, cust_contact FROM Customers WHERE SOUNDEX(cust_contact) = SOUNDEX('Michael Green');
8.2.2 日期和时间处理函数

非常遗憾的是:日期和时间处理函数在SQL实现中差别很大,可移植性非常差。所以,关于具体DBMS支持的日期-时间处理函数,请参阅相应的文档。

8.2.3 数值处理函数

数值处理函数仅处理数值数据,这些函数一般主要用于代数、三角或几何运算。数值处理函数在SQL差别不大。

函数 说明
ABS() 返回一个数的绝对值
COS() 返回一个角度的余弦
EXP() 返回一个数的指数值
PI() 返回圆周率
SIN() 返回一个角度的正弦
SQRT() 返回一个数的平方根
TAN() 返回一个角度的正切

第9课 汇总数据

9.1 聚集函数
函数 说明
AVG() 返回某列的平均值
COUNT() 返回某列的行数
MAX() 返回某列的最大值
MIN() 返回某列的最小值
SUM() 返回某列值之和
9.1.1 AVG()函数

AVG函数计算表中列的平均值。AVG()只能用来确定特定数值列的平均值.AVG函数忽略列值为NULL的行。

  • select avg(prod_price) as avg_price from products;
9.1.2 COUNT()函数
COUNT函数有两种实现方式:
1. 使用count(*),对表中行的数目进行计数。无论是空值(NULL)或非空值。
2. 使用count(columnName)对表特定列具有值进行计数,忽略NULL值。
3. select count(vend_state) from vendors;语句忽略NULL值。
9.1.3 MAX()函数

MAX()返回指定列中的最大值。

  • select max(prod_price) as max_price from products;
9.1.4 MIN()函数

MIN()的功能正好与MAX()功能相反,它返回指定列的最小值。

9.1.5 SUM()函数

SUM()用来返回指定列值的和(总计)。

  • select sum(item_price) as sum_price from orderitems where order_num = 20005;
  • select sum(item_price*quantity) as total_price from orderitems where order_num = 20005;

注: 利用标准的算术运算符,所有的聚集函数都可以执行多个列上的计算。

9.2 聚集不同的值

使用关键字: DISTINCT关键字,过滤相同的值。

  • select avg(distinct prod_price) as avg_price from products where vend_id = 'DLL01';
9.3 组合聚集函数

select 语句可以根据需要包含多个聚集函数。

  • select count(*) as num_items, min(prod_price) as min_price, max(prod_price) as max_price, sum(prod_price) as sum_price from products;

第10课 分组数据

将数据分组,使用Group by子句和Having子句。

10.1 数据分组

目前为止的所有计算都是在表的所有数据或匹配特定的WHERE子句的数据上进行的。没有对应的分组概念。

  • select count(*) as num_prods from products where vend_id = 'DLL01';

如果查询每个供应商提供的商品数,怎么查?答:需要使用分组,Group by子句和Having子句

10.2 创建分组

分组是使用select语句的Group by实现的。

  • select vend_id, count(*) num_prods from products group by vend_id;

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

使用GROUP BY子句前,需要了解一些重要的规定:
1. GROUP BY子句可以包含任意数目的列,因而可以对分组进行嵌套,更细致的进行分组。
2. GROUP BY子句嵌套了分组,数据将在最后指定的分组上进行汇总。
3. GROUP BY子句中列出的每一列必须是检索列或有效的表达式。如果在SELECT中使用表达式,则必须在GROUP BY子句中指定相同的表达式。不能使用别名。
4. 大多数SQL实现不允许GROUP BY列带有长度可变的数据类型。
5. 除聚集计算语句外,SELECT语句中的每一列都必须在GROUP BY子句中给出。
6. 如果分组列中包含具有NULL值的行,则NULL将作为一个分组返回。如果列中有多行NULL值,它们将分为一组。
7. GROUP BY子句必须出现在WHERE子句之后,Order by子句之前。
10.3 过滤分组

Group by新建分组,使用Having子句过滤分组。如:查询至少有两个订单的顾客。

Where 子句和Having 子句的区别:
1. Where子句过滤的是行,而Having子句过滤的是分组。
2. Having子句可以替代Where子句,但是不建议这样做。
  • select vend_id, count() as num_prods from products where prod_price >= 4 group by vend_id having count() > 2;

分析:首先,where语句先选出价格大于4的商品,然后按照vend_id来进行分组,再对分组进行过滤。

10.4 分组和排序
Order by Group by
对产生的输出排序 对行分组,但输出可能不是分组的顺序
任意列都可以使用 只可能使用选择列或表达式列,而且必须使用每个选择列表达式
不一定需要 如果与聚集函数一起使用列(或表达式),则必须使用
语句的末尾 一般在join和where后面

一般使用GROUP BY子句后,也应该使用ORDER BY子句。

10.5 Select子句的顺序
子句 说明 是否必须使用
SELECT 要返回的列或表达式
FROM 从中检索数据的表 仅在从表选择数据时使用
WHERE 行级过滤
GROUP BY 分组说明 仅在按组计算聚集时使用
HAVING 组级过滤
ORDER BY 输出排序顺序

第11课 使用子查询

11.2 利用子查询进行过滤
假如需要列出订购物品RGAN01的所有顾客,应该怎样检索?
1. 从订单详情表(OrderItems)中查询订购物品GRANO1的所有订单编号。
2. 根据订单编号,从表(Orders)中查询顾客ID
3. 根据顾客ID,从表(Customers)查询顾客信息。

select cust_name, cust_contact from customers
   where cust_id IN (select cust_id from Order
         where order_num IN (select order_num from OrderItems
                              where prod_id = 'GRANO1'));

作为子查询的Select语句只能查询单个列(cust_id、order_num)。企图查询多个列,是错误的。

11.3 作为计算字段使用子查询
假如需要显示Customers表中每个顾客的订单总数,应该怎样写?
1. 从Customers表中检索顾客列表。
2. 对于检索的每个顾客,统计在Orders表中的数目。

select cust_name, cust_state,
    (select count(*) from Orders 
          where Orders.cust_id = Customers.cust_id) AS orders
from Customers order by cust_name;
该子查询检索到每个顾客执行一次。

第12课 联结表

12.1.1 关系表

关系表的设计就是把信息分成多个表,一类数据一个表。各个表通过共同的值进行关联。

12.2 创建联结
当from子句后接多个表时候,表示联结产生了。select子句后面所接的字段,分别来自于两个表中。
1. select vend_name, prod_name, vend_city, vendors.vend_id from vendors, products where vendors.vend_id = products.vend_id;

如果没有Where子句,那么返回结果就是笛卡尔积。
三者是等价的
select a.*,b.c_id,b.s_score from student a inner join score b on a.s_id=b.s_id;
select a.*,b.c_id,b.s_score from student a join score b on a.s_id=b.s_id;
select a.*,b.c_id,b.s_score from student a,score b where a.s_id=b.s_id;
12.2.3 联结多个表

select prod_name, vend_name, prod_price, quantity from OrderItems, Products, Vendors
where Products.vend_id = Vendors.vend_id
And OrderItems.prod_id = Products.prod_id
And order_num = 20007;

第13课 创建高级联结

13.1 使用表别名
使用表别名优点:
1. 缩短SQL语句
2. 允许在一条select语句中多次使用相同的表
select cust_name, cust_contact from Customers AS C, Orders AS O, OrderItems AS OI
where C.cust_id = O.cust_id and OI.order_num = O.order_num and prod_id = 'RGAN01'
13.2 使用不同类型的联结

四种联结: 1. 内联结或等值联结, 2. 自联结(self-join), 3. 自然联结(natural join), 4. 外联结(outer join)。

13.2.1 自联结

问:查询与Y Lee同一公司的所有顾客。

1. 使用子查询
select cust_name, cust_address from customers where cust_name = (select cust_name from customers where cust_contact = 'Y Lee')
2. 使用自联结
select C1.cust_name, C2.cust_address from customers as C1, customers as C2 where C1.cust_name = C2.cust_name and C1.cust_contact = 'Y Lee'
用自联结而不用子查询:
1. 许多DBMS处理联结远比处理子查询快得多。
13.2.2 自然联结
13.2.3 外联结
许多联结将一个表中的行和另一个表中的行相关联,但有时候需要包含没有关联行的那些行。如,以下工作:
1. 对每个顾客订单进行计数,包括至今尚未下订单的顾客。
2. 列出所有产品以及订购数量,包括没人订购的产品。

上述举例,包括了那些相关表中没有关联的行。这种联结称为外联结。
检索所有顾客及其订单
1. 内联结(inner join): 
select Customers.cust_id, Orders.order_num from Customers inner join Orders on Customers.cust_id = Orders.cust_id;
2. 外联结(left outer join):
select Customers.cust_id, Orders.order_num from Customers left outer join Orders
on Customers.cust_id = Orders.cust_id;
外联结的使用的是left outer join是从左边的表(Customers)中选择行,所以如果右表没有对应的id则补充。要注意on后面的表Customers和Orders顺序。
3. 外联结(right outer join)
select Customers.cust_id, Orders.order_num from Customers right outer join Orders on Orders.cust_id = Customers.cust_id;
4. 全外联结(full outer join)
select Customers.cust_id, Orders.order_num from Orders full out join Customers
on Orders.cust_id = Customers.cust_id;
两个表的行的最大集合。

参考 https://www.cnblogs.com/dinglinyong/p/6656315.html

13.3 使用带聚集函数的联结
检索所有顾客及每个顾客所下的订单数
select Customers.cust_id, count(Orders.order_num) AS num_ord
from Customers inner join Orders on Customers.cust_id = Orders.cust_id
group by Customers.cust_id;

13.4 使用联结和联结条件

联结和联结的使用要点:
1. 注意使用的联结类型。有内联结,外联结等
2. 联结语法每个DBMS可能不一样。
3. 保证使用正确的联结条件,否则返回不正确的数据。
4. 应该总是使用联结条件,否则会得到笛卡尔积。
5. 在一个联结中可以包含多个表,甚至可以对每个联结采用不同的联结类型。虽然这样做是合法的,一般也很有用,但应该在一起测试它们 前分别测试每个联结。这会使故障排除更为简单。

第14课 组合查询

利用UNION操作符将多条Select语句合成一个结果集。

14.1 组合查询

SQL 允许执行多条查询语句(多条Select语句),并将结果作为一个查询结果集返回。

主要有两种情况需要使用组合查询:
1. 在一个查询中,从不同的表返回结构数据。
2. 对一个表执行多个查询,按一个查询返回数据。

注:一般多个Where子句的Select语句都可以作为一个组合查询。也就是说将Where子句拆分开来。
14.2 创建组合查询

用操作符UNION操作符组合多条Select语句,将他们的结果组合成一个结果集。

14.2.1 使用UNION操作符
查询Illinois、Indiana和Michigan等美国几个州的所有顾客的报表,和不管位于哪个州的所有的Fun4All
1. 使用UNION操作符查询。
select cust_name, cust_contact, cust_email from Customers
where cust_state in ('Illinois','Indiana','Michigan')
UNION
select cust_name, cust_contact, cust_email from Customers
where cust_name = 'Fun4All';

2. 使用Where子句
select cust_name, cust_contact, cust_email from Customers
where cust_state in ('Illinois','Indiana','Michigan') or cust_name = 'Fun4All'
Union和Where子句比较:
1. 对于较复杂的过滤条件,或者从多个表(而不是一个表)中检索数据的情形,使用UNION可能会使处理更简单。
2. 多数DBMS使用内部查询优化程序,使用Union关键字会在内部组合它们,所以性能几乎无差别。但使用Union操作符也请注意下性能问题。
14.2.2 UNION规则
Union非常好用,但使用组合前请注意下以下规则:
1. UNION必须由两条或两条以上的SELECT语句组成,语句之间用关键字UNION分隔。
2. UNION每次查询必须包含相同的列,表达式,聚集函数。(各个列不需要以相同的次序列出)。
3. 列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含转换的类型。
14.2.3 包含或取消重复的行

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

使用关键字:Union All的会返回所有的匹配行(不进行去除重复的行)。
Union All操作符是Where不能替代的。
14.2.4 对组合查询结果排序

用Union组合查询时,只能使用一条Order by子句,它必须位于最后一条Select语句。

  • select cust_name, cust_contact, cust_id from customers
    where cust_state in ('MI', 'OH')
    union
    select cust_name, cust_contact, cust_id from customers
    where cust_contact = 'E Fudd'
    order by cust_id;

注意:union组合查询中order by子句的列必须在select后面有。且order by必须在语句最后,对整个结果集进行排序。

14.2.5 sql执行顺序总结
sql 不同于与其他编程语言的最明显特征是处理代码的顺序。在大数编程语言中,代码按编码顺序被处理,但是在sql语言中,第一个被处理的子句是from子句,尽管select语句第一个出现,但是几乎总是最后被处理。
每个步骤都会产生一个虚拟表,该虚拟表被用作下一个步骤的输入。这些虚拟表对调用者(客户端应用程序或者外部查询)不可用。只是最后一步生成的表才会返回给调用者。如果没有在查询中指定某一子句,将跳过相应的步骤。下面是对应用于sql server 2000和sql server 2005的各个逻辑步骤的简单描述。 

(8)select (9)distinct  (11)<top num> <select list>
(1)from [left_table]
(3)<join_type> join <right_table>
(2)on <join_condition>
(4)where <where_condition>
(5)group by <group_by_list>
(6)with <cube | rollup>
(7)having <having_condition>
(10)order by <order_by_list>

逻辑查询处理阶段简介:

from:对from子句中的前两个表执行笛卡尔积(cartesian product)(交叉联接),生成虚拟表vt1
join on:对vt1应用on筛选器。只有那些使<join_condition>为真的行才被插入vt2。join分为cross join、inner join、[left join、right join,full join]
outer(join):如果指定了outer join(相对于cross join 或(inner join),保留表(preserved table:左外部联接把左表标记为保留表,右外部联接把右表标记为保留表,完全外部联接把两个表都标记为保留表)中未找到匹配的行将作为外部行添加到 vt2,生成vt3.如果from子句包含两个以上的表,则对上一个联接生成的结果表和下一个表重复执行步骤1到步骤3,直到处理完所有的表为止。
where:对vt3应用where筛选器。只有使<where_condition>为true的行才被插入vt4.和join中的on筛选类似,只是比on筛选器执行的晚.
group by:按group by子句中的列列表对vt4中的行分组,生成vt5.
cube|rollup:把超组(suppergroups)插入vt5,生成vt6.
having:对vt6应用having筛选器。只有使<having_condition>为true的组才会被插入vt7.过程和on、where类似,只能用在group by 之后。
select:处理select列表,产生vt8.
distinct:将重复的行从vt8中移除,产生vt9.
order by:将vt9中的行按order by 子句中的列列表排序,生成游标(vc10).
limit/top:从vc10的开始处选择指定数量或比例的行,生成表vt11,并返回调用者。


标准的 sql 的解析顺序为: 
(0).from 子句, 组装来自不同数据源的数据 
(1).join on 拼接多个表
(2).where 子句, 基于指定的条件对记录进行筛选 
(3).group by 子句, 将数据划分为多个分组 
(4).使用聚合函数进行计算 
(5).使用 having 子句筛选分组 
(6).计算所有的表达式 
(7).使用 order by 对结果集进行排序 

举例说明: 
在学生成绩表中 (暂记为 tb_grade), 把 "考生姓名"内容不为空的记录按照 "考生姓名" 分组, 并且筛选分组结果, 选出 "总成绩" 大于 600 分的. 
标准顺序的 sql 语句为: 
select 考生姓名, max(总成绩) as max总成绩 
from tb_grade 
where 考生姓名 is not null 
group by 考生姓名 
having max(总成绩) > 600 
order by max总成绩 

在上面的示例中 sql 语句的执行顺序如下: 
(1). 首先执行 from 子句, 从 tb_grade 表组装数据源的数据 
(2). 执行 where 子句, 筛选 tb_grade 表中所有数据不为 null 的数据 
(3). 执行 group by 子句, 把 tb_grade 表按 "学生姓名" 列进行分组 
(4). 计算 max() 聚集函数, 按 "总成绩" 求出总成绩中最大的一些数值 
(5). 执行 having 子句, 筛选课程的总成绩大于 600 分的. 
(7). 执行 order by 子句, 把最后的结果按 "max 成绩" 进行排序.

重点注意:

where和having的区别,
join需要注意用on,
like、=、is null的区别 还有null与空格的区别
order by的局限
group by如何在count等命令下分组

第15课 插入数据

利用SQL的INSERT语句将数据插入表中。

15.1.1 插入完整的行
使用insert语句时,请在表名括号内明确给定列名。
1. insert into Customers(cust_id,cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES
('1000000006','Toy Land','123 Any Street','New York','NY','11111','USA',NULL, NULL);
15.1.3 插入检索出的数据

将Select语句的查询结果插入到表中。顾名思义,它是由一条INSERT语句和一条SELECT语句组成的。

insert into 
Customers(cust_id, cust_contact,cust_email,cust_name,cust_address,cust_city,cust_state,cust_zip,cust_country)
select 
cust_id,cust_contact,cust_email,cust_name,cust_address,cust_city,cust_state,cust_zip,cust_country 
from CustNew;
说明:将select语句查询出来的结果,插入到表Customers中. 
注意:
1. insert 和select不一定要求列名匹配。它使用的是列的位置,因此SELECT中的第一列(不管其列名)将用来填充表列中指定的第一列,第二列将用来填充表列中指定的第二列。
2. insert 通常只插入一行。但insert select是个例外,它可以用一条insert插入多行,不管select语句返回对少条语句,都会被插入到表中。
15.2 从一个表复制到另一个表

select into将数据复制到一个新表中。

insert select与select into区别:
1. insert select是导出数据,先select出结果,然后插入到表中。select into是导入数据。
select * into CustCopy from customers;
创建一个名为CustCopy的新表。然后将整个customers表内容复制到表CustCopy中。
Mysql中语法是:
create table CustCopy AS select * from Customers;
使用select into注意点:
1. 任何select选项和子句都可以使用,包括where和order by。
2. 可利用联结从多个表插入数据(Mysql中未测试通过).
3. 不管从多少个表中检索数据,数据都只能插入到一个表中。

第16课 更新和删除数据

16.1 更新数据

update语句更新表中的数据。

  • update Customers set cust_contact = 'Sam Roberts',cust_email = 'sam@toyland.com' where cust_id = '10000006';

注意:update语句可能更新多个值。

16.2 删除数据

delete语句删除表中的数据。

  • delete from Customers where cust_id = '10000006'
删除时外键考虑:
1. 使用delete语句删除数据时,可能会有外键的影响。比如:我要删除Products表中某个商品,但是该商品cust_id在订单表中有记录,作为订单表的外键。那么删除会失败。
16.3 更新和删除的指导原则
  1. 除非确实删除所有行或更新所有行,否则绝不能不使用where语句。
  2. 保证每个表都要有主键,尽可能像where子句那样使用它。
  3. 在update和delete语句使用where子句前,应该先用select进行测试,保证过滤条件是正确无误的。
  4. 使用强制实施引用完整性的数据库,这样DBMS将不允许删除其数据与其他表相关联的行。

第17课 创建和操纵表

该课主要讲解:创建,更改和删除表的基本知识。表的创建是很有学问的,本书中的内容并不丰富还需要查一些其他资料。

17.1.1 表创建基础
利用create table创建表,必须给出下列信息:
1. 新表的名字,在关键字create table后给出.
2. 表列的名字和定义,用逗号隔开。
3. 有的DBMS还要指定表的位置。

CREATE TABLE IF NOT EXISTS `runoob_tbl`(
   `runoob_id` INT UNSIGNED AUTO_INCREMENT,
   `runoob_title` VARCHAR(100) NOT NULL,
   `runoob_author` VARCHAR(40) NOT NULL,
   `submission_date` DATE,
   PRIMARY KEY ( `runoob_id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

实例解析:
如果你不想字段为 NULL 可以设置字段的属性为 NOT NULL, 在操作数据库时如果输入该字段的数据为NULL ,就会报错。
AUTO_INCREMENT定义列为自增的属性,一般用于主键,数值会自动加1。
PRIMARY KEY关键字用于定义列为主键。 您可以使用多列来定义主键,列间以逗号分隔。
ENGINE 设置存储引擎,CHARSET 设置编码。
17.1.2 使用Null

创建表的时候,指定表的列为Not null,则表示,以后执行insert语句,该列必须有值,不接受null。

17.1.3 指定默认值
创建表的时候,可以为表的列提供默认值。

- create table OrderItems
(
order_num INTEGER Not null,
order_item INTEGER Not null,
prod_id CHAR(10) Not null,
quantity INTEGER Not null default 1,
item_price DECIMAL(8,2) not null
);
17.2 更新表
使用alter table时,请考虑下列的事:
1. 理想情况下,不要再表包含数据时对其进行更新。应该在表的设计过程中充分烤炉未来可能。
2. 所有的DBMS都允许给现有的表增加列,不过对所增加列的数据类型(以及NULL和Default的使用)有所限制.
3. 许多DBMS不允许删除或更改表中的列.
  • alter table Vendors add vend_phone CHAR(20);
复杂的表结构更改一般需要手动删除过程,它涉及以下步骤:
1. 用新的列布局创建一个新表;
2. 使用INSERT SELECT语句从旧表复制数据到新表。有必要的话,可以使用转换函数和计算字段;
3. 检验包含所需数据的新表;
4. 重命名旧表(如果确定,可以删除它);
5. 用旧表原来的名字重命名新表;
6. 根据需要,重新创建触发器、存储过程、索引和外键
17.3 删除表
  • drop table Customers;

注意:删除表不可撤销。请慎重

17.4 重命名表

每个DBMS对表重命名的支持有所不同,具体要参考具体的DBMS文档.

第18课 使用视图

18.1 视图

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

理解视图的最好方法是看例子.
select cust_name, cust_contact from Customers, Orders, OrderItems
where Customers.cust_id = Orders.cust_id
and OrderItems.order_num = Orders.order_num
and prod_id = 'RGAN01';
该句sql检索订购了某种商品的顾客。任何需要这个数据的人必须理解这些表与表之间的关系。

假如将整个查询包装成一个名为ProductsCustomers的虚拟表。那么sql可以简化为:
select cust_name, cust_contact from ProductCustomers where prod_id = 'RGANO1';

总结:视图不包含任何的列或数据,包含的是一个查询。
18.1.1 为什么使用视图
视图的常见使用场景:
1. 重用SQL语句。
2. 简化复制的SQL操作。在编写查询后,可以方便的使用它,而不必知道具体的细节。
3. 使用表的一部分,而不是整个表。
4. 保护数据。可以授予用户访问表的特定部分,而不是表的全部权限。
5. 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。
创建视图后,可以用表相同的方式去使用它们。可以对视图执行select操作,过滤和排序数据,将视图联结到其他视图或表等。
再次强调:视图只是包含了动态检索数据的查询。
18.1.2 视图的规则与限制

不同的DBMS中视图的限制和规则可能不同,具体请参考文档。

视图的创建和使用的一些最常见的规则和限制:
1. 与表一样,视图必须唯一命名,名字不能有冲突。
2. 对于可以创建的视图数目没有限制。
3. 创建视图,必须具有足够的访问权限。
4. 视图可以嵌套,即可以利用从其他视图中检索数据的查询来构造视图。所允许的嵌套层数在不同的DBMS中有所不同(嵌套视图可能会严 重降低查询的性能,因此在产品环境中使用之前,应该对其进行全面测试)。
5. 许多DBMS禁止在视图查询中使用ORDER BY子句。 
6. 有些DBMS要求对返回的所有列进行命名,如果列是计算字段,则需要使用别名。
7. 视图不能索引,也不能有关联的触发器或默认值。 
8. 有些DBMS把视图作为只读的查询,这表示可以从视图检索数据,但不能将数据写回底层表。
18.2 创建视图

Create view语句来创建视图。

18.2.1 利用视图简化复杂的联结
视图最常见的应用:隐藏复杂的SQL
create view ProductCustomers AS
select cust_name, cust_contact, prod_id
from Customers, Orders, OrderItems
where Customers.cust_id = Orders.cust_id
AND OrderItems.order_num = Orders.order_num;

然后调用sql查询:
select cust_name, cust_contact from ProductCustomers where prod_id = 'RGANO1';
18.2.2 用视图重新下格式化检索的数据
视图另一个常见的应用: 重新格式化检索出的数据
create view VendorLocations AS
select RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')'
AS vend_title from Vendors;

在单个组合计算列中返回供应商名和位置
18.2.3 用视图过滤不想要的数据
可以定义一个视图:过滤没有电子邮件地址的顾客
create view CustomerEMailList AS
select cust_id, cust_name, cust_email
from Customers where cust_email is not Null;
18.2.4 使用视图与计算字段

在简化计算字段的使用上,视图也特别有用。

create view OrderItemsExpanded AS
select order_num, prod_id, quantity, item_price, quantity*item_price AS expanded_price
from OrderItems
第19课 使用存储过程
19.1 存储过程
1. 通俗的讲,存储过程就是类似于C的一个方法。
2. 简单的说,存储过程就是为以后使用而保存一条或多条SQL语句。可以将其视为批文件,但它的作用不仅限于批处理。
19.2 为什么使用存储过程
  1. 存储过程有3个优点:简单,安全和高性能。
  2. 多数DBMS的存储过程可能不同,而且存储过程也比较复杂,难于编写。
19.3 执行存储过程

使用关键字execute来执行存储过程

EXECUTE AddNewProduct( 'JTS01', 'Stuffed Eiffel Tower', 6.49,'Plush stuffed toy with the text");
AddNewProduct是一个存储过程,将一个新的商品添加到Product表中。但我们发现,最重要的字段prod_id列没有,应为我们想统一化规格化生成对应的prod_id.
所以,该存储过程需要做以下3件事:
1. 验证传递的数据,保证所有4个参数都有值;
2. 生成用作主键的唯一ID; 
3. 将新产品插入Products表,在合适的列中存储生成的主键和传递的数据。
19.4 创建存储过程

创建存储过程,每个DBMS差别很大,具体需要参考DBMS文档。

第20课 管理事务处理

  • 使用事务处理,确保成批的SQL操作要么完全执行,要么完全不执行,来保证数据库的完整性。
  • 事务处理是一种机制,用来管理必须成批执行的SQL操作,保证数据库不可能包括不完整的操作结果。
  • 事务都应该具备ACID特征。所谓ACID是Atomic(原子性),Consistent(一致性),Isolated(隔离性),Durable(持久性)四个词的首字母所写,
在使用事务时,需要理解几个关键字:
1. 事务(transaction)指一组SQL语句.
2. 回退(rollback)指撤销指定SQL语句的过程。
3. 提交(commit)指将未存储的SQL语句结果写入数据库表。
4. 保留点(savepoint)指事务处理中设置的临时占位符(placeholder),可以对它发布回退。
哪些sql语句可以回退?
答:Insert, Delete和Update语句可以回退。Select, Drop和Create语句不能回退。
20.2 控制事务处理

管理事务的关键在于将SQL语句组合成逻辑块,并规定数据何时回退,何时不应该回退。

不同的DBMS实现事务处理的语法不同:
1. SQL Server
Begin Transaction
...
Commit Transaction
在这里Begin Transaction和Commit Transaction之间的SQL必须完全执行,或完全不执行。
2. Mysql
Start Transaction
...
Commit
3. Oracle
Set Transaction
...
Commit
20.2.1 使用Rollback

SQL 使用Rollback来回退(撤销)SQL命令

命令:delete from Orders; Rollback;
使用Delete语句,然后使用Rollback语句撤销。该句能充分说明:在事务处理块中,delete操作(insert操作和update操作)并不是最终的结果。
20.2.2 使用Commit

在事务处理块中,提交必须时显示提交。

删除订单12345,所以需要同时更新两个数据库表Orders表和OrderItems表。
1. Mysql中
Begin Transaction
delete OrderItems where order_num = 12345;
delete Orders where order_num = 12345;
Commit Transaction
2. Oracle中
Set Transaction
delete OrderItems where order_num = 12345;
delete Orders where order_num = 12345;
Commit;

上面的事务处理块中同时更新两个表中的记录,事务保证了操作的一致性,不可能出现部分删除。
20.2.3 使用保留点
保留点(savepoint)作用是:支持回退部分事务。
例如添加一个订单,需要插入顾客信息,插入订单信息,订单详情信息,但当插入订单详情时发生错误,只需要回退到插入订单信息,不需要回退到插入顾客信息。这是就需要回退部分事务。
保留点使用:在编写事务过程中,需要在事务处理块中的合适位置放置占位符。用于后面的回退。
不同的DBMS的保留点设置不同
1. Mysql
Savepoint delete1;
2. SQL Server
Save Transaction delete1;
不同的DBMS的回退保留点的方式不同
1. Mysql
Rollback to delete1;
2. SQL Server
Rollback transaction delete1;
20.3 SQL Server事务举例

不同的DBMS可能不同,但是总体概念和流程都是相同的。

Begin Transaction
insert into Customers(cust_id, cust_name) values('1000000010', 'Toys Emporium');
save transaction StartOrder;
insert into Orders(order_num, order_date, cust_id) values(20100,'2001/12/1','1000000010');
IF @@ERROR <> 0 Rollback Transaction StartOrder;
insert into OrderItems(order_num, order_item, prod_id, quantity, item_price)
values(20100, 1, 'BR01', 100, 5.49);
IF @@ERROR <> 0 Rollback Transaction StartOrder;
insert into OrderItems(order_num, order_item, prod_id, quantity, item_price)
values(20100, 2, 'BR03', 100, 10.99);
IF @@ERROR <> 0 Rollback Transaction StartOrder;
Commit Transaction;

第21课 使用游标

21.1 游标

SQL检索结果是返回一组称为结果集的行。但是无法从结果集中得到第一行,下一行或前10行的数据。但是大多数Web应用开发人员不使用游标,而是根据自己的需要开发相应的功能。如:利用limit1, 10来实现分页查询, 或使用foreach来实现遍历。

游标的用途:
有时,需要在检索出来的行中前进或后退一行或多行,就需要使用游标。
游标(cursor)是一个存储在DBMS服务器上的数据库查询,它不是一条SELECT语句。在存储了游标之后,应用程序可以根据需要滚动或浏览数据集中的数据。
游标在不同的DBMS中的一些共性:
1. 能够标记游标为只读,使数据能读取,但不能更新和删除。
2. 能控制可以执行的定向操作(向前、向后、第一、最后、绝对位置、相对位置等)。
3. 能标记某些列为可编辑的,某些列为不可编辑的。
4. 规定访问范围,使游标对创建它的特定请求(如存储过程)或对所有请求可访问。
5. 指示DBMS对检索出的数据(而不是指出表中活动数据)进行复制,使数据在游标打开和访问期间不变化。
21.2 使用游标
使用游标的几个明确的步骤:
1. 在使用游标前,必须定义它。这个过程实际上没有检索数据,它只是定义要使用的SELECT语句和游标。
2. 一旦定义,就必须打开游标以供使用。这个过程用前面定义的SELECT语句把数据实际检索出来。
3. 对于填有数据的游标,根据需要取检索各行。
4. 在结束游标使用时,必须关闭游标,可能的话,释放游标(有赖于具体的DBMS)。
21.2.1 创建游标

使用Declare语句创建游标。

创建一个游标来检索没有电子邮件地址的所有顾客.
1. Mysql + SQL Server
Declare CustCursor Cursor for
Select * from Customers where cust_email is null;
2. Oracle
Declare Cursor CustCursor is
Select * From Customers where cust_email is null;
21.2.2 使用游标

游标的使用场景并不多,该节笔记比较粗糙。

使用OPEN CURSOR语句打开游标
1. Open Cursor CustCursor;
使用Fetch语句访问游标数据。
Fetch需要指出检索哪些行,从何处检索他们以及将他们存放于何处。
Declare Type CustCursor IS ref Cursor
Return Customers%ROWTYPE;
Declare CustRecord Customers%ROWTYPE
Begin
	Open CustCursor;
	Fetch CustCursor INTO CustRecord;
	Close CustCursor;
End;	
21.2.3 关闭游标
1. Oracle
Close CustCursor;
2. SQL Server
Close CustCursor
Deallocate Cursor CustCursor;

第22课 高级SQL特性

22.1 约束
  1. 约束: 管理如何插入或处理数据库数据的规则。
  2. 引用完整性(referential integrity): 利用键来建立从一个表到另一个表的引用
关系型数据库,需要保证插入数据库的数据和合法性。
例如:如果Orders表存储订单信息,OrderItems表存储订单详细内容,应该保证OrderItems中引用的任何订单ID都存在于Orders中。类似地,在Orders表中引用的任意顾客必须存在于Customers表中。
DBMS通过在数据库表上施加约束来实施引用完整性.
22.1.1 主键

主键是一种特殊的约束,用来某一行的数据是唯一的,而且主键永不改动。

表中的列只要满足以下条件,可用于主键:
1. 任意两行的主键值都不相同。
2. 每行都具有一个主键值(即列中不允许NULL值)。
3. 包含主键值的列从不修改或更新。
4. 主键值不能重用。如果从表中删除某一行,其主键值不分配给新行。
主键的创建:
1. Create Table 
Create Table Vendors
(
vend_id		CHAR(10) NOT NULL Primary Key,
vend_name 	CHAR(50) NOT NULL,
vend_address CHAR(50) NULL,
vend_city    CHAR(50) NULL,
vend_state   CHAR(5)  NULL,
vend_zip     CHAR(10) NULL,
vend_country CHAR(50) NULL
);
Primary Key声明表的主键列
2. Alter Table
Alter Table Vendors 
Add Constraint Primary Key(vend_id);
22.1.2 外键

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

外键是关系数据库描述表和表之间依赖关系
1. 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)
);
2 Alter Table
Alter Table Orders
Add Constraint Foreign key (cust_id) references Customers(cust_id);
外键的作用:
1. 能够帮组保证引用完整性。
2. 防止意外删除记录
由于顾客表Customers表中主见: cust_id,作为订单表Orders中的外键。所以如果想直接删除顾客表中的记录,必须保证其值已经不被订单表中依赖。
3. 级联删除
Mysql支持级联删除特性。解释:如果从顾客表Customers表中删除某个顾客,那么由于顾客表Customers表中的主见cust_id作为了Orders表中的外键依赖,所以订单表Orders中的该顾客的订单也一并删除。
22.1.3 唯一约束
唯一约束用来保证某一列中的数据是唯一的。它和主键有以下区别:
1. 表可包含多个唯一约束,但每个表只允许一个主键。
2. 唯一约束列可包含NULL值。
3. 唯一约束列可修改或更新。
4. 唯一约束列的值可重复使用。
5. 与主键不一样,唯一约束不能用来定义外键。
22.1.4 检查约束
检查约束用来保证一列中的数据满足一组指定的条件。检查约束的常见用途:
1. 检查最小或最大值。例如,防止0个物品的订单(即使0是合法的数)。 
2. 指定范围。例如,保证发货日期大于等于今天的日期,但不超过今天起一年后的日期。 
3. 只允许特定的值。例如,在性别字段中只允许M或F。
对OrderItems表施加了检查约束,保证所有物品的数量大于0:
CREATE TABLE OrderItems
(
	order_num		INTEGER	NOT NULL,
	order_item	INTEGER	not null, 
	prod_id		CHAR(10)	not null,
	quantity		INTEGER	not null check (quantity > 0),
	item_price	MONEY		not null
);

检查名为gender的列只包含M或F
Alter Table OrderItems
Add Constraint Check (gender LIKE '[MF]')
22.2 索引

索引是利用B+等索引机制,加快查询和排序的速度。

使用索引需要记住以下内容:
1. 索引提高检索操作的性能,但降低了数据插入、修改和删除的性能。因为数据变更需要更新索引。 
2. 索引数据可能要占用大量的存储空间。 
3. 并非所有数据都适合做索引。查询量非常大,列的数据非常多,使用使用索引。
4. 索引用于数据过滤和数据排序。如果你经常以某种特定的顺序排序数据,则该数据可能适合做索引。 
5. 可以在索引中定义多个列(例如,州加上城市)。这样的索引仅在以州加城市的顺序排序时有用。
所以必须唯一命名:
Create Index prod_name_index On Products(prod_name);
22.2.1 索引建立的几大原则
组合索引的解释:
如果创建了(state, city, zip)列作为组合索引,索引中的数据行按照state/city/zip次序排列,
这意味着,这个索引可以被用于搜索如下所示的数据列组合:
state, city, zip
state, city
state

但是MySQL不能利用这个索引来搜索没有包含在最左前缀的内容。例如,如果你按照city或zip来搜索,
就不会使用到这个索引。如果你按照state,和zip来搜索,该索引也是不能用于这种组合值的,但是可以利用索引来查找匹配的state从而缩小搜索的范围。

1. 选择唯一性索引
唯一性索引的值是唯一的,能快速的通过该索引来确定某条记录。
2. 为经常需要排序、分组和联合操作的字段建立索引
经常需要Order By、Group By、Distinct和Union等操作的字段,排序操作会浪费很多时间。
3. 为常作为查询条件的字段建立索引
某个字段经常用来做查询条件,该字段的查询速度会影响整个表的查询速度。使用索引,可以极大加快查询速度。
4. 最左前缀匹配原则,非常重要的原则。
mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,
比如state='yes' and city='wuwei' and price > 3 and name = 'ppp' 如果建立(state, city, price, name)顺序的索引,
name的查询是用不到索引的,只能使用部分索引,然后在结果集上面进行排序。如果建立(state, city , ppp, price)的索引则都可以用到,state, city, name的顺序可以任意调整。
5. =和in可以乱序。
比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。
但是如果建立索引(a,b,c),结果where子句是:b =2 and c=3,则索引不起作用。
6. 尽量使用数据量少的索引
如果索引的值很长,那么查询的速度会受到影响。
例如,对一个CHAR(100)类型的字段进行全文检索需要的时间肯定要比对CHAR(10)类型的字段需要的时间要多。
7. 尽量使用前缀来索引
索引字段的值很长,应该采用前缀来索引。
8. 尽量选择区分度高的列作为索引。
数据量区分度越高,索引的比较成本会小很多。
9. 删除不再使用或者很少使用的索引
请定期删除不在使用的索引。
10. 限制索引的数目
使用索引需要付出代价,索引会消耗磁盘空间,对数据库记录的更新和删除产生影响。
11. 索引列不能参与计算。
坚决不能将某个计算函数作为缩影。如:from_unixtime(create_time) = ’2014-05-29’
12. 尽量的扩展索引,不要新建索引。 
比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
22.3 触发器
触发器是特殊的存储过程,可以在特定的数据库活动发生时自动执行。
触发器可以与特定表上的Insert, Update或Delete操作(或组合)想关联。
如:与Orders表上的Insert操作相关联的触发器只在Orders表中插入行时才会执行。
触发器的常见用途:
1. 保证数据一致。例如,在Insert或Update操作中将所有州名转换为大写。
2. 基于某个表的变动在其他表上执行活动。例如,每当更新或删除一行时将审计跟踪记录写入某个日志表。
3. 进行额外的验证并根据需要回退数据。例如,保证某个顾客的可用资金不超限定,如果已经超出,则阻塞插入。
4. 计算计算列的值或更新时间戳。
不同的DBMS的触发器差距很大,请参考具体文档.
如:创建一个触发器,对所有Insert和Update操作,将Customers表中的cust_state列转换为大写。
1. SQL Server
Create Trigger customer_state On Customers
For Insert, Update As
Update Customers Set cust_state = Upper(cust_state)
2. Oracle
Create Trigger customer_state
After Insert Or Uupdate
For Each Row
Begin
Update Customers
Set cust_state = Upper(cust_state)
Where Customers.cust_id = :OLD.cust_id
end;
22.4 数据库安全
一般对数据保护操作有:
1. 对数据库管理功能(创建表、更改或删除已存在的表等)的访问;
2. 对特定数据库或表的访问;
3. 访问的类型(只读、对特定列的访问等);
4. 仅通过视图或存储过程对表进行访问;
5. 创建多层次的安全措施,从而允许多种基于登录的访问和控制;
6. 限制管理用户账号的能力。
posted @ 2018-08-13 16:52  从零开始的程序员生活  阅读(550)  评论(0编辑  收藏  举报