SQL(Structure Query Language,结构化查询语言)语言是国际标准化组织(ISO)采纳的标准数据库语言。
数据库就是一幢大楼,我们要先盖楼,然后再招住户(住户当然就是数据库对象,)。我们盖得大楼的基本格局设计师们已经为我们设计好,我们在创建数据库过程中,系统(设计师)就会我们把格局设计好。我们住户住进去后只需根据自己的需要小改就可以了。它就是一个保存有组织的数据的容器。
表: 数据库中存储某种特定类型的数据,是一种结构化清单。
主键: 一列(或者一组列),其值能够唯一标识表中的每一行。
表中的任何列都可以作为主键,只要满足以下条件:
SQL语言共分为四大类:数据查询语言DQL,数据操纵语言DML, 数据定义语言DDL,数据控制语言DCL。
数据查询语言DQL用于检索数据库
基本结构是由SELECT子句,FROM子句,WHERE子句组成的查询块:
Select <字段名表>
From <表或视图名>
Where <查询条件>
(SELECT,DELETE,UPDATE,INSERT) 数据操纵语言DML用于改变数据库数据
主要有三种形式:
1) 插入:Insert
2) 更新:Update
3) 删除:Delete
(CREATE,ALTER,DROP,DECLARE)
数据定义语言DDL用于建立,修改,删除数据库中的各种对象-----表、视图、
索引、同义词、聚簇等如:
CREATE TABLE/VIEW/INDEX/SYN/CLUSTER
(GRANT,REVOKE,COMMIT,ROLLBACK)
数据控制语言DCL用来授予或回收访问数据库的某种特权,并控制
数据库操纵事务发生的时间及效果,对数据库实行监视等。
包含两条命令:
1) Grant:授权。
2) Revoke:撤回。
select prod_name
from Products; --从Products表中检索一个名为prod_name的列;
select prod_name, prod_id, prod_price
from Products; --从Products表中检索名为prod_name,prod_id,prod_price的列;
select *
from Products; --通配符(*)代表返回表中的所有列;
如前所示,select语句返回所有匹配的行。但是如果不希望每个值都出现一次,重复出现的可以忽落掉,这是应该使用关键字distinct
select distinct vend_id
from Products; --此时只返回不同的vend_id行;
如果想返回第一行或者一定数量的行,应该使用limit和offset关键字
select prod_name
from Products
limit 5 offset 3; --返回从第3行起的5行数据,第一个被检索的行是第0行,因此,是从第4行开始检索;
上述语句具有简化版:
limit 3,5; --使用这个语法,逗号之前的值对应offset,逗号之后的值对应limit;
使用order by 对检索出的数据进行排序
select prod_name
from Products
order by prod_name; --在指定一条order by子句时,应该保证它是select语句中的最后一条子句;
select prod_id, prod_price, prod_name
from Products
order by prod_price, prod_name; --在这种情况下首先按价格排序,然后按名称排序;
select prod_id, prod_price, prod_name
from Products
order by 2,3;
--这和上述语句输出的结果一样,order by 2,3表示按select清单中的第2个列prod_price进行排序,再按第3个列prod_name进行排序;
使用关键字desc进行降序排序:
select prod_id, prod_price, prod_name
from Products
order by prod_price desc; --此时会根据价格进行降序排序;
select prod_id, prod_price, prod_name
from Products
order by prod_price desc, prod_name; --desc关键字只应用于位于前面的列名,因此只对prod_price进行降序排列;
数据库表一般包含大量的数据,通常只会根据特定操作或者报告的需要提取表数据的子集。只检索所需数据需要指定搜索条件,搜索条件也称为过滤条件,在select语句中根据where子句中指定的搜索条件进行过滤。例如:
select prod_name, prod_price
from Products
where prod_price = 3.49;
--这条语句从products表中检索两个列,但不返回所有行,只返回prod_price值为3.49的行;
SQL支持的条件操作符:
= ;<> 不等于;!=; <;<=;!<;>;>=;!>;between…and…在指定的两个值之间;is null为null值;
上述介绍的所有的where子句在过滤数据是都是使用单一的条件。为了进行更强的过滤控制,SQL允许给出多个where子句,这些字句有两种方式,以and子句或or子句的方式使用。例如:
select prod_id, prod_price, prod_name
from Products
where vend_id = 'DLL01' and prod_price <= 4;
这个例子只包含一个and子句,因此最多有两个过滤条件。可以增加多个过滤条件,每个条件之间都要用and关键字。而or操作符与and操作符刚好相反,只要满足其中的一个条件就返回数据。
IN操作符用来指定条件范围,范围中的每个条件都可以进行匹配。例如:
select prod_name, prod_price
from Products
where vend_id in ('DLL01','BRS01')
order by prod_name;
--等价于
select prod_name, prod_price
from Products
where vend_id ='DLL01' or vend_id ='BRS01'
order by prod_name;
in操作符与or操作符具有相同的功能。为什么还要使用in操作符?其优点如下:
NOT操作符有且只有一个功能,那就是否定其后所跟的任何条件。NOT总是与其它操作符一起使用。
select prod_name
from Products
where not vend_id = 'DLL01'
order by prod_name;
--等价于
select prod_name
from Products
where vend_id <> 'DLL01'
order by prod_name;
这段代码的意思是返回DLL01之外的所有东西。在这种简单地where字句中,NOT没有什么优势,但是在更复杂的字句中,NOT是非常有用的。例如,在与in操作符联合使用时,NOT可以非常简单的找出与条件列表不匹配的行。
通配符: 用来匹配值的一部分的特殊字符。
搜索模式: 由字面值、通配符或两者组合构成的搜索条件。
like操作符: 为在搜索字句中使用通配符,必须使用like操作符。like指示后跟的搜索模式利用通配符匹配而不是简单的相等匹配进行比较。
最常使用的通配符是百分号。在搜索中,%表示任何字符出现的任意次数。例如,为了找出所有以词fish起头的产品:
select prod_id, prod_name
from Products
where prod_name like 'fish%'; --这条语句将检索任意以fish起头的词,不论fish之后是何字符,有多少字符;
通配符可以再搜索模式中的任意位置使用,并且可以使用多个通配符。
有个下划线(_)通配符,用途和%一样,但是它只匹配单个字符,而不是多个字符。
方括号通配符用来指定一个字符集,它必须匹配指定位置(通配符的位置)的一个字符,例如,找出所有名字以J或M起头的联系人:
select cust_contact
from Customers
where cust_contact like '[JM]%'
order by cust_contact;
--此通配符可以使用前缀字符^(脱字号)来否定;
select cust_contact
from Customers
where cust_contact like '[^JM]%'
order by cust_contact;
--得到的结果与上一个正好相反;
这一搜索模式是用来两个不同的通配符。[JM]匹配方括号中的任意一个字符,它也只能匹配单个字符。因此,任何一个字符的名字都不匹配。[JM]之后的%通配符匹配第一个字符之后的任意数目的字符,返回所需结果。
使用通配符的技巧:
字段: 基本上与列的意思相同,经常互换使用,不过数据库列一般称为列,而术语字段通常与计算字段一起使用。计算字段并不实际存在于数据库表中,它是运行时在select语句内创建的。
拼接: 将值联结到一起构成单个值。
select name + ' (' + country + ')'
from test.form
order by name;
--如果没有+好输出的结果本来应该是两列,一列name,一列country,现在输出的结果是name和country在同一列输出;
上面的select语句拼接以下元素:
再看一下上述语句的输出。结合成一个计算字段的两个列用空格填充。如果需要去掉这些空格,可以使用rtrim()函数来完成
select rtrim(name) + ' (' + rtrim(country) + ')'
from test.form
order by name;
从前面的输出可以看出,select语句可以很好地拼接地址字段。但是新得到的这个列没有名字。为了解决这个问题,SQL支持列别名。别名是一个字段或者值的替换名,别名用as关键字赋予。
select rtrim(name) + ' (' + rtrim(country) + ')' as vend_title
from test.form
order by name;
计算字段的另一个常见用途是对检索出的数据进行算术计算。
select prod_id, quantity, item_price, quantity*item_price as expanded_price
from OrderItems
where order_num = 2008;
--这里的expanded_price列是一个计算字段,此计算为quantity*item_price;
函数一般是在数据上执行的,为数据的转换和处理提供了方便。
多数的SQL实现支持以下类型的函数:
常用的文本处理函数
函数 | 说明 |
---|---|
left(或使用子字符串函数) | 返回字符串左边的字符 |
length(也使用datalength()或len()) | 返回字符串的长度 |
lower | 将字符串转换为小写 |
ltrim | 去掉字符串左边的空格 |
right(或使用子字符串函数) | 返回字符串右边的字符 |
rtrim | 去掉字符串右边的空格 |
soundex | 返回字符串的soundex值 |
upper | 将字符串转换为大写 |
日期和时间处理函数
在SQL Server中用datepart():
select order_num
from Orders
where datepart(yy, order_date) = 2012;
在MySQL中具有各种日期处理的函数,但没有datepar(),可以使用名为 year() 的函数:
select order_num
from Orders
where year(order_date) = 2012;
数值处理函数
数值处理函数仅处理数值数据。
函数 | 说明 |
---|---|
ABS() | 返回一个数的绝对值 |
COS() | 返回一个角度的余弦 |
EXP() | 返回一个数的指数值 |
PI() | 返回圆周率 |
SIN() | 返回一个角度的正弦 |
SQRT() | 返回一个数的平方根 |
TAN() | 返回一个角度的正切 |
我们经常需要汇总数据而不需要把他们实际检索出来,为此SQL提供了专门的函数。
聚集函数:对某些行运行的函数,计算并返回一个值。
函数 | 说明 |
---|---|
avg() | 返回某列的平均值 |
count() | 返回某列的行数 |
max() | 返回某列的最大值 |
min() | 返回某列的最小值 |
sum() | 返回某列值之和 |
avg()函数
select avg(price) as avg_price
from products
where id = 'DLL01';
--此句返回了id为DLL01的产品的平均价格;
注意:avg()只能用来确定特定数值列的平均值,而且列明必须作为函数参数给出。为了获得多个列的平均值,必须使用多个avg()函数,avg函数忽略值为null的行。
count()函数
max()函数和min()函数
上述5个聚集函数都可以如下使用:
下面的例子使用了avg()函数。它与上面的select语句相同,但是使用了distinct参数,因此平均值只考虑各个不同的价格:
select avg(distinct price) as avg_price
from products
where id = 'DLL01';
--此例子中的平均价格和上述的平均价格不同,因为有多个物品具有相同的价格,排除它们输出了不同的价格;
目前为止所有的聚集函数例子都只是涉及单个函数。但实际上,select语句可根据需要包含多个聚集函数。例如:
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;
--这里用单条语句执行了4个聚集计算,返回四个值,物品的清单,产品价格的最低值,产品价格的最高值,产品价格的平均值;
使用group by子句和having子句可以进行分组数据,以便汇总表内容的子集。
目前为止的所有计算都是在表的所有数据或匹配特定的where子句的数据上进行的。如果要返回每个供应商提供的产品数目就应该使用分组了。使用分组可以将数据分为多个逻辑组,对每个组进行聚集计算。
分组是使用select语句的group by子句建立的。例如:
select vend_id, count(*) as num_prods
from Products
group by vend_id;
--group by子句指示DBMS按照vend_id排序并分组数据,这就会对给每个vend_id计算num_prods一次,这时候就不需要指定要计算和估值的每个组了,系统会自动完成;
使用group by子句的注意事项:
SQL除了能够分组数据外,还允许过滤分组,规定哪些分组,排序哪些分组。这时候就要用到having子句,having子句非常类似与where,以上所有where类型的子句都可以用having来代替。唯一的差别是,where过滤行,而having过滤分组。
select cust_id, count(*) as orders
from Orders
group by cust_id
having count(*) >=2;
--这条语句和上面的类似,最后一行增加了having子句,它过滤了count(*)>=2的那些分组;
说明:having和where的差别
这里有另一种理解方法,where在数据分组之前进行过滤,having在数据分组之后进行过滤。where排除的行不包括在分组中,这可能会改变计算值,从而影响having子句中基于这些值过滤掉的分组。
group by和order by经常完成相同的工作,但它们非常非常不同,如下表所示:
order by | group by |
---|---|
对产生的输出排序 | 对行分组,但输出可能不是分组的顺序 |
任意列都可以使用(甚至非选择的列也可以使用) | 只可能使用选择列或表达式列,而且必须使用每个选择列表达式 |
不一定需要 | 如果与聚集函数一起使用列(或表达式),则必须使用 |
提示: 一般在使用group by子句时,应该也给出order by子句。这是保证数据正确排序的唯一方法,不要仅仅依赖group by 排序数据。
select子句及其顺序
子句 | 说明 | 是否必须使用 |
---|---|---|
select | 要返回的列或表达式 | 是 |
from | 从中检索数据的表 | 仅在从表选择数据时使用 |
where | 行级过滤 | 否 |
group by | 分组说明 | 仅在按组计算聚集时使用 |
having | 组级过滤 | 否 |
order by | 输出排序顺序 | 否 |
查询: 任何SQL语句都是查询,但此术语一般指select语句
子查询: 嵌套在其它查询中的查询。
现在假如需要列出订购物品RGAN01的所有顾客,应该怎么样检索?列出具体步骤:
--输出了包含此物品的所有订单编号:
select order_num
from OrderItems
where prod_id = 'RGAN01';
--输出与订单编号相关的客户ID;
select cust_id
from Orders
where order_num in (20007,20008);
--输出顾客ID的所有顾客信息;
select cust_name, cust_contact
from Customers
where cust_id in ('100000004','100000005');
--上述步骤都是单独最为一个查询来执行。我们可以把select语句返回的结果用于另一条select语句的where子句;
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 = 'RGAN01'));
注意: 作为子查询的select语句只能查询单个列,企图检索多个列将返回错误。
使用子查询的另一个方法就是创建字段。假如需要显示Customer表中每个顾客的订单总数:
select cuse_name,
cust_state,
(select count(*)
from Orders
where Orders.cust_id = Customers.cust_id) as orders
from Customers
order by cust_name;
--在这里我们用到了完全限定列名:where Orders.cust_id = Customers.cust_id) as orders,因为没有具体指定就会返回错误结果。有时候由于出现冲突列名而导致歧义性就会引起DBMS抛出错误信息;
有一个包含产品目录的数据库表,其中每类物品占一行。对于每一种物品,要存储的信息包括产品描述、价格,以及生产该产品的供应商。
现在有同一供应商生产的多种物品,那么在何处存储供应商名、地址、联系方法等供应商信息呢?将这些数据与产品信息分开存储的理由是:
1、同一供应商生产的每个产品,其供应商信息都是相同的,对每个产品重复此信息既浪费时间又浪费存储空间;
2、如果供应商信息发生变化,例如供应商迁址或电话号码变动,只需修改一次即可;
3、如果有重复数据(即每种产品都存储供应商信息),则很难保证每次输入该数据的方式都相同。不一致的数据在报表中就很难利用。
关键是,相同的数据出现多次决不是一件好事,这是关系数据库设计的基础。关系表的设计就是要把信息分解成多个表,一类数据一个表。各表通过某些共同的值互相关联(所以才叫关系数据库)。
在这个例子中可建立两个表:一个存储供应商信息,另一个存储产品信息。Vendors表包含所有供应商信息,每个供应商占一行,具有唯一的标识。此标识称为主键(primary key),可以是供应商ID或任何其他唯一值。
Products表只存储产品信息,除了存储供应商ID(Vendors表的主键)外,它不存储其他有关供应商的信息。Vendors表的主键将Vendors表与Products表关联,利用供应商ID能从Vendors表中找出相应供应商的详细信息。
这样做的好处是:
1、供应商信息不重复,不会浪费时间和空间;
2、如果供应商信息变动,可以只更新Vendors表中的单个记录,相关表中的数据不用改动;
3、由于数据不重复,数据显然是一致的,使得处理数据和生成报表更简单。
总之,关系数据可以有效地存储,方便地处理。因此,关系数据库的可伸缩性远比非关系数据库要好。
可伸缩(scale): 能够适应不断增加的工作量而不失败。设计良好的数据库或应用程序称为可伸缩性好(scale well)。
使用联结表的好处:
如前所述,将数据分解为多个表能更有效地存储,更方便地处理,并且可伸缩性更好。但这些好处是有代价的。
如果数据存储在多个表中,怎样用一条SELECT语句就检索出数据呢?
答案是使用联结。简单说,联结是一种机制,用来在一条SELECT 语句中关联表,因此称为联结。使用特殊的语法,可以联结多个表返回一组输出,联结在运行时关联表中正确的行。
创建联结非常简单,指定要联结的所有表以及关联它们的方式即可。请看下面的例子:
输入▼
select vend_name, prod_name, prod_price
from Vendors, Products
where Vendors.vend_id = Products.vend_id;
输出▼
vend_name prod_name prod_price
-------------------- -------------------- ----------
Doll House Inc. Fish bean bag toy 3.4900
Doll House Inc. Bird bean bag toy 3.4900
Doll House Inc. Rabbit bean bag toy 3.4900
Bears R Us 8 inch teddy bear 5.9900
Bears R Us 12 inch teddy bear 8.9900
Bears R Us 18 inch teddy bear 11.9900
Doll House Inc. Raggedy Ann 4.9900
Fun and Games King doll 9.4900
Fun and Games Queen doll 9.4900
分析▼
SELECT语句与前面所有语句一样指定要检索的列。这里最大的差别是所指定的两列(prod_name和prod_price)在一个表中,而第三列(vend_name)在另一个表中。
现在来看from子句,与以前的select语句不一样,这条语句的FROM子句列出了两个表:Vendors和Products。它们就是这条SELECT语句联结的两个表的名字。这两个表用WHERE子句正确地联结,WHERE子句指示DBMS将Vendors表中的vend_id与Products表中的vend_id匹配起来。
可以看到,要匹配的两列指定为Vendors. vend_id和Products. vend_id。这里需要这种完全限定列名,如果只给出vend_id,DBMS就不知道指的是哪一个(每个表中有一个)。从前面的输出可以看到,一条SELECT 语句返回了两个不同表中的数据。
警告:完全限定列名
就像上述提到的,在引用的列可能出现歧义时,必须使用完全限定列名(用一个点分隔表名和列名)。如果引用一个没有用表名限制的具有歧义的列名,大多数DBMS会返回错误。
WHERE子句的重要性
使用WHERE子句建立联结关系似乎有点奇怪,但实际上是有个很充分的理由的。要记住,在一条SELECT 语句中联结几个表时,相应的关系是在运行中构造的。在数据库表的定义中没有指示DBMS如何对表进行联结的内容。你必须自己做这件事情。在联结两个表时,实际要做的是将第一个表中的每一行与第二个表中的每一行配对。WHERE子句作为过滤条件,只包含那些匹配给定条件(这里是联结条件)的行。没有WHERE子句,第一个表中的每一行将与第二个表中的每一行配对,而不管它们逻辑上是否能配在一起。
笛卡儿积(cartesian product)
由没有联结条件的表关系返回的结果为笛卡儿积。检索出的行的数目将是第一个表中的行数乘以第二个表中的行数。
警告:不要忘了WHERE子句
要保证所有联结都有WHERE子句,否则DBMS将返回比想要的数据多得多的数据。同理,要保证WHERE子句的正确性。不正确的过滤条件
会导致DBMS返回不正确的数据。
内联结
目前为止使用的联结称为等值联结(equijoin),它基于两个表之间的相等测试。这种联结也称为内联结(inner join)。其实,可以对这种联结使用稍微不同的语法,明确指定联结的类型。下面的SELECT语句返回与前面例子完全相同的数据:
输入▼
select vend_name, prod_name, prod_price
from Vendors INNER join Products
on Vendors.vend_id = Products.vend_id;
分析▼
此语句中的SELECT 与前面的SELECT 语句相同,但FROM 子句不同。这里,两个表之间的关系是以INNER JOIN指定的部分FROM子句。在使用这种语法时,联结条件用特定的ON子句而不是WHERE子句给出。传递给ON的实际条件与传递给WHERE的相同。
联结多个表
SQL不限制一条SELECT语句中可以联结的表的数目。创建联结的基本规则也相同。首先列出所有表,然后定义表之间的关系。例如:
输入▼
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;
输出▼
prod_name vend_name prod_price quantity
--------------- ------------- ---------- --------
18 inch teddy bear Bears R Us 11.9900 50
Fish bean bag toy Doll House Inc. 3.4900 100
Bird bean bag toy Doll House Inc. 3.4900 100
Rabbit bean bag toy Doll House Inc. 3.4900 100
Raggedy Ann Doll House Inc. 4.9900 50
分析▼
这个例子显示订单20007中的物品。订单物品存储在OrderItems表中。每个产品按其产品ID存储,它引用Products表中的产品。这些产品通过供应商ID联结到Vendors表中相应的供应商,供应商ID存储在每个产品的记录中。这里的FROM子句列出三个表,WHERE子句定义这两个联结条件,而第三个联结条件用来过滤出订单20007中的物品。
警告:性能考虑
DBMS在运行时关联指定的每个表,以处理联结。这种处理可能非常耗费资源,因此应该注意,不要联结不必要的表。联结的表越多,性能下降越厉害。
警告:联结中表的最大数目
虽然SQL本身不限制每个联结约束中表的数目,但实际上许多DBMS都有限制。请参阅具体的DBMS文档以了解其限制。
现在回顾一下9.1中的例子,如下的SELECT语句返回订购产品RGAN01的顾客列表:
输入▼
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 = 'RGAN01'));
-- 子查询并不总是执行复杂SELECT操作的最有效方法,下面是使用联结的相同查询:
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';
--这个查询中的返回数据需要使用3个表。但在这里,我们没有在嵌套子查询中使用它们,而是使用了两个联结来连接表。这里有三个WHERE子句条件。前两个关联联结中的表,后一个过滤产品RGAN01的数据。
本课讲解另外一些联结(包括它们的含义和使用方法),介绍如何是以表别名,如何对被联结的表使用聚集函数。
SQL除了可以对列名和计算字段使用别名,还允许给表名起别名。这样做有两个主要理由:
请看下面的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';12345
注意:Oracle中没有AS
Oracle不支持AS关键字。要在Oracle中使用别名,可以不用AS,简单地指定列名即可(因此,应该是Customers C,而不是Customers AS C)。
需要注意,表列名只在查询执行中使用。与列别名不一样,表别名不返回到客户端。
联结类型:内联结、自联结、自然联结、外联结
查询要求:首先找出Jim Jones工作的公司,然后找出在该公司工作的顾客。
--通过子查询的方式
SELECT cust_id, cust_name, cust_contact
FROM Customers
WHERE cust_name = ( SELECT cust_name
FROM Customers
WHERE cust_contact = 'Jim Jones');
现在来看使用联结的相同查询:
SELECT c1.cust_id, c1.cust_name, c1.cust_contact
FROM Customers AS c1, Customers AS c2
WHERE c1.cust_name = c2.cust_name
AND c2.cust_contact = 'Jim Jones';
此查询中需要的两个表实际上是相同的表,因此Customers表在FROM子句中出现了两次。虽然这是完全合法的,但对Customers的引用具有歧义性,因为DBMS不知道你引用的是哪个Customers表。解决此问题,需要使用别名表。
提示:用自联结而不用子查询
自联结通常作为外部语句,用来替代从相同的表中检索数据的使用子查询语句。虽然最终的结果是相同的,但许多DBMS处理联结远比处理子查询快得多。
标准的联结(内联结)返回所有数据,相同的列甚至多次出现。自然联结排除多次出现,使每一列只返回一次。怎样完成这项工作呢? 自然联结要求你只能选择那些唯一的列,一般通过对一个表使用通配符(SELECT *),而对其他表的列使用明确的子集来完成。
SELECT C.*, O.order_num, O.order_date,
OI.prod_id, OI.quantity, OI.item_price
FROM Customers AS C, Order AS O, OrderItems AS OI
WHERE C.cust_id = O.cust_id
AND OI.order_num = O.order_num
AND prod_id = 'RGAN01';123456
事实上,我们迄今为止建立的每个内联结都是自然联结,很可能永远都不会用到不是自然联结的内联结。
4.外联结
许多联结将一个表中的行与另一个表中的行相关联,但有时候需要包含没有关联行的那些行。例如,可能需要使用联结完成以下工作:
在上述例子中,联结包含了那些在相关表中没有关联的行。这种联结称为外联结。
下面的SELECT语句给出一个简单的内联结。它检索所有顾客及其订单:
SELECT Customers.cust_id, Orders.order_num
FROM Customers INNER JOIN Orders
ON Customers.cust_id = Orders.cust_id;
外联结语法类似。要检索包括没有订单顾客在内的所有顾客,可如下进行:
SELECT Customers.cust_id, Orders.order_num
FROM Customers LEFT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id;
类似提到的内联结,这条SELECT语句使用关键字out join来指定联结类型(而不是在WHERE子句中指定)。
但是,与内联结关联两个表中的行不同的是,外联结还包括没有关联行的行。在使用OUTER JOIN语法时,必须使用RIGHT或LEFT关键字指定包括其所有行的表(RIGHT指出的是OUTER JOIN右边的表,而LEFT指出的是OUTER JOIN左边的表。)
上面的例子使用LEFT OUTER JOIN从FROM子句左边的表(Customers)中选择所有行。为了从右边的表中选择所有行,需要使用RIGHT OUTER JOIN,如下所示:
SELECT Customers.cust_id, Orders.order_num
FROM Customers RIGHT OUTER JOIN Orders
ON Orders.cust_id = Customers.cust_id;
注意:SQLite外联结
SQLite支持LEFT OUTER JOIN,但不支持RIGHT OUTER JOIN。
提示:外联结的类型
要记住,总是有两种基本的外联结形式:左外联结和右外联结。它们之间的唯一差别是所关联表的顺序。换句话说,调整FROM或WHERE子句中表的顺序,左外联结可以转换为右外联结。因此,这两种外联结可以互换,哪个方便就用哪个。
还存在另一种外联结,就是全外联结,它检索两个表中的所有行并关联那些可以关联的行。与左外联结或右外联结包含一个表的不关联的行不同,全外联结包含两个表的不关联的行。全外联结的语法如下:
SELECT Customers.cust_id, Orders.order_num
FROM Orders FULL OUTER JOIN Customers
ON Orders.cust_id = Customers.cust_id;
要检索所有顾客及每个顾客所下的订单数,下面代码使用COUNT()函数完成此工作:
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;
cust_id num_ord
10000001 2
10000003 1
10000004 1
10000005 1
聚集函数也可以方便地与其他联结一起使用。请看下面的例子:
SELECT Customers.cust_id,
COUNT(Orders.order_num) AS num_ord
FROM Customers LEFT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id
GROUP BY Customers.cust_id;
cust_id num_ord
10000001 2
10000002 0
10000003 1
10000004 1
10000005 1
这个例子使用左外部联结来包含所有顾客,甚至包含那些没有任何订单的顾客。结果中也包含了顾客10000002,他有0各订单。
汇总一下联结及其使用的要点:
数据插入: insert用来将行插入(或添加)到数据库表。
插入有几种方式:
注意:插入及系统安全
把数据插入表中的最简单方法是使用基本的INSERT语法,指定表名和插入到新行中的值。例如:
INSERT INTO Customers VALUES (
'10000006', 'Toy Land', '123 Any Street', 'New York', 'NY', '11111', 'USA', NULL, NULL
)
123
注意info关键字: 在某些SQL实现中,跟在INSERT之后的INTO关键字是可选的。
上面的SQL语句高度依赖于表中列的定义次序,还依赖于其容易获得的次序信息。即使可以得到这种次序信息,也不能保证各列再下一次表结构变动后保持完全相同的次序。因此编写依赖于特定列次序的SQL语句是很不安全的。
编写INSERT语句的更安全(不过更烦琐)的方法如下:
INSERT INTO Customers (cust_id,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country,
cust_contact,
cust_email)
VALUES ( '100000006',
'Fe Cow',
'QIQIHER',
'USA',
'NY',
'161041',
'USA',
NULL,
NULL);
上述代码在表名后的括号里明确给出了列名。在插入行时,DBMS将用VALUES列表中的相应值填入列表中的对应项。VALUES中的第一个值对应于第一个指定列名,第二个值对应第二个指定列名。
优点:即使表的结构改变,这条INSERT语句仍然能正确的工作。
提示:总是使用列的列表,不要使用没有明确给出列的INSERT语句。给出列能是SQL代码继续发挥作用,即使表的结构发生变化。
注意:小心使用VALUES
使用INSERT的推荐方法是明确给出表的列名。这种语法,还可以省略列,这表示可以只给某些列提供值,给其他列不提供值。
INSERT INTO Customers(cust_id,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country )
VALUES('1000000006',
'Toy Land',
'123 Any Street',
'New York',
'NY',
'11111',
'USA');
在前面的例子中,没有给出cust_contact和cust_email这两列提供值。这表示没有必要在INSEERT语句中包含它们,因此这里省略了这两列及其对应的值。
注意:省略列如果表的定义允许,则可以再INSERT操作用中省略某些列。省略的列必须满足以下某个条件。
如果对表中不允许NULL值且没有默认值的列不给出值,DBMS将产生错误信息,并且相应的行插入不成功。
INSERT一般用来给表插入具有指定列值的行。INSERT还存在另一种形式,可以利用它将SELECT语句的结果插入表中,这就是所谓的INSERT SELECT。
假如把另一个表中的顾客列合并到Customers表中:
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;
例子使用INSERT SELECT从CustNew中将所有数据导入Customers,SELECT语句从CustNew检索出要插入的值,而不是列出它们,SELECT中列出的每一列对应于Customers表名后所跟的每一列。
注意:INSERT SELECT中的列名
在INSERT和SELECT语句中使用了相同的列名。但是,不一定要求列名匹配;
它使用的是列的位置,因此SELECT中的第一列(不管其列名)将用来填充表列中指定的第一列,第二列将用来填充表列中指定的第二列,如此等等;
INSERT SELECT中SELECT语句可以包含WHERE子句,以过滤插入的数据。
注意:插入多行
有一种数据插入不使用INSERT语句。要将一个表的内容复制到一个全新的表(运行中创建的表),可以使用SELECT INTO语句。例如:
select *
info CustCopy
from Customers;
--创建了一个名为CustCopy的新表并把Customers表中的数据复制到新表中,要想只复制部分的列,可以明确给出列名,而不是使用*通配符;
在MySQL、Oracle中使用的语法稍有不同:
CREATE TABLE CustCopy AS
SELECT * FROM Customers;
--将Customers表中的数据,复制到了CustCopy表中。
在使用select info时需要注意事项:
更新(修改)表中的数据,可以使用 UPDATE 语句,有两种使用 UPDATE 的方式:
注意:
在使用 update 时一定要细心,因为稍不注意就会更新表中的所有行,在客户端/服务器的 DBMS 中,使用 UPDATE 语句可能需要特殊的安全权限。
基本的 UPDATE语句由三部分组成,分别是:
例子,客户 1000000005 现在有了电子邮箱地址,记录需要更新,其语句如下:
UPDATE Customers --要更新的表名;
SET cust_email = '[email protected]' --设置一个新值然后赋给被更新的列;
WHERE cust_id = '1000000005'; --确定更新的是哪一行
UPDATE 语句总是以要更新的表名开始,SET 命令用来将新值赋给被更新的列,如果没有WHERE 子句,这个电子邮箱地址将更新表中的所有行。
更新多个列的语法稍有不同:
UPDATE Customers
SET cust_contact = 'Sam Roberts', cust_email = '[email protected]'
WHERE cust_id = '1000000006';
在更新多个列时,只需要使用一条SET 命令,每个“列=值”对之间用逗号分隔(最后一列不需要用逗号)
在 UPDATE 语句中使用子查询 : UPDATE语句中可以使用子查询,使得能用 SELECT 语句检索出的数据更新列数据。
要删除某个列的值,可以设置它为 NULL
(假如表定义允许 NULL
值)
如下进行:
UPDATE Customers
SET cust_email = NULL
WHERE cust_id = '1000000006';
NULL
用来去除 cust_email 列中的值,这与保存空字符串不同(空字符串用 ‘ ’ 表示,是一个值),NULL 表示没有值。
从一个表中删除(去掉)数据,使用 DELETE 语句,有两种使用 DELETE 的方式:
注意:
在使用 DELETE 时一定要细心,因为稍不注意,就会错误地删除表中的所有行,在客户端/服务器的 DBMS 中,使用 DELETE 语句可能需要特殊的安全权限。
如下代码,从 Customers 表中删除一行:
DELETE FROM Customers
WHERE cust_id = '1000000007';
--DELETE FROM 要求指定从中删除数据的表名,WHERE 子句过滤要删除的行
在上述例子中,如果省略 WHERE 子句,它将删除表中的每一个顾客
友好的外键:
存在外键时,DBMS 使用它们实施引用完整性,使用外键确保引用完整性的一个好处是,DBMS通常可以防止删除某个关系需要用到的行。例如要从 Products 表中删除一个产品,而这个产品用在 OrderItems 的已有订单中,那么 DELETE 语句将抛出错误并终止。这是总要定义外间的另一个理由。
删除表的内容而不是表: DELETE 语句从表中删除行,甚至是删除表中所有行,但是,DELETE 不删除表本身
更快的删除:
如果想从表中删除所有的行,不要使用 DELETE,可以使用 TRUNCATE TABLE 语句,它可以完成相同的工作,而速度更快(因为不记录数据的变动)。TRUNCATE 实际是删除原来的表并重新创建一个表,而不是逐行删除表中的数据
在 UPDATE 和 DELETE 语句中,如果省略了 WHERE 子句,则 UPDATE 和 DELETE 将被应用到表中的所有行,所有在使用这两条语句时,需要注意一些事项:
SQL 不仅用于表数据操纵,还用来执行数据库和表的所有操作,包括表本身的创建和处理
一般有两种创建表的方法:
用程序创建表,可以使用 SQL 的 CREATE TABLE 语句;需要注意的是,使用交互式工具时实际上就是使用 SQL 语句,这些语句不是用户编写的,而是界面工具自动生成并执行相应的 SQL 语句。
表创建基础
利用 CREATE TABLE创建表,必须给出以下信息:
在 SQL 中的代码示例如下:
CREATE TABLE ProductsCopy
(
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 TEXT(1000) NULL
);
提示:
在创建新表时,指定的表名必须不存在,否则会出错。防止意外覆盖已有的表,SQL要求首先手工删除该表,然后再重建它,而不是简单的用创建表语句覆盖它。
使用 NULL 值
NULL 值就是没有值或者缺值,允许 NULL
值的列也允许在插入行时不给出该列的值,不允许 NULL
值的列不接受没有列值的行,即在插入或者更新行时,该列必须有值。每个表列要么是 NULL 列,要么是 NOT NULL 列,这种状态在创建时由表的定义规定,对于大部分的 DBMS,NULL 为默认值,如果不指定 NOT NULL ,就认为指定的是 NULL。
**主键和null值:**主键是其值唯一标识表中每一行的列,只有不允许 NULL
值的列可以作为主键,允许 NULL
值的列是不能作为唯一标识的。NULL 值是没有值,不是空字符串,如果指定 ''
(两个单引号,中间没有字符),这在 NOT NULL 中是允许的,空字符串是一个有效值,而不是无值。
指定默认值
SQL 允许指定默认值,在插入行时如果不给出值,DBMS将自动采取默认值,默认值在 CREATE TABLE 语句的列定义中用关键字 DEFAULT 指定。如下例子:quantity 列为订单中每个物品的数量,在这一列的描述中,增加了 DEFAULT 1,指示 DBMS,如果不给出数量则使用数量1
CREATE TABLE OrderItemsCopy
(
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
);
--在 MySQL 中不允许使用函数作为默认值,只支持常量
默认值经常应用于日期和时间戳列。例如通过指定引用系统日期的函数或变量,将系统日期用作默认日期。MYSQL用户指定default current_date()。这条获得系统系统日期的命令在不同的DBMS中几乎都是不一样的,下表列出了这条命令在某些DBMS中的语法。
DBMS | 函数/变量 |
---|---|
access | now() |
DB2 | current_date |
MySQL | current_date() |
Oracle | sysdate |
SQL Server | getdate() |
SQLite | date(‘now’) |
更新表定义,可以使用 ALTER TABLE 语句。但是,在理想状态下,当表中存储数据以后,该表就不应该再被更新
在表的设计过程中,需要花费大量时间来考虑,以便后期不对该表进行大的改动
使用 ALTER TABLE 时需要考虑的事情:
使用 ALTER TABLE 更改表结构时,必须给出以下信息:
如下示例代码,可以给 Vendors 表增加一个名为 vend_phone 的列,其数据类型为 CHAR
ALTER TABLE Vendors
ADD vend_phone CHAR(20);
删除刚刚添加的列,可以如下:
ALTER TABLE vendors
DROP COLUMN vend_phone;
ALTER TABLE的一种常见用途是定义外键,为了对单个表进行多个更改,可以使用单条 ALTER TABLE 语句,每个更改用逗号分隔。
复杂的表结构更改一般需要手动删除过程,它涉及以下步骤:
使用 ALTER TABLE
要极为小心,应该在进行改动前做完整的备份(表结构备份和数据的备份), 数据库的更改不能撤销,如果增加了不需要的列,也许无法删除;同样,如果删除了不应该删除的列,则可能会丢失该列中的所有数据。
删除表(删除整个表而不是其内容)非常简单,使用 DROP TABLE 即可
如下代码示例,该语句删除了 ProductsCopy 表,删除表没有确认,也不能撤销,一旦执行,将永久删除该表
DROP TABLE ProductsCopy;
使用关系规则防止意外删除:
许多 DBMS 允许强制实施有关规则,防止删除与其他表相关联的表
在实施这些规则时,如果对某个表发布一条 DROP TABLE 语句,且该表是某个关系的组成部分,则 DBMS 将阻止这条语句的执行,直到该关系被删除为止。
每个 DBMS 对表重命名的支持有所不同,在 MySQL 中可以通过使用 RENAME 语句
RENAME TABLE customers2 TO customers1;--将customers2重命名为customers1;
如果要对多个表进行重命名
RENAME TABLE backup_customes TO customers,
backup_vendors TO vendors,
backup_products TO products;
视图是虚拟的表,与包含数据的表不同,视图只包含使用时动态检索数据的查询
如下代码,该查询用来检索订购了某种产品的顾客,任何需要这个数据的人都必须理解相关表的结构,知道如何创建查询和对表进行联结,检索其他产品(或多个产品)的相同数据,必须修改最后的 WHERE 子句
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 = 'ANV01';
现在,假如可以把整个查询包装成一个名为 ProductCustomers
的虚拟表,则可以如下轻松地检索出相同的数据
SELECT cust_name, cust_contact
FROM ProductCustomers
WHERE prod_id = 'ANV01';
这就是视图的作用,ProductCustomers 是一个视图,作为视图,它不包括任何列或数据,包含的是一个查询(与上面用以正确连接表的相同的查询)。
为什么使用视图
上述代码是使用视图的一个例子,但是视图还有其他的作用
创建视图之后,可以用与表基本相同的方式使用它们。可以对视图执行 SELECT 操作,过滤和排序数据,将视图联结到其他视图或表,甚至添加和更新数据(存在某些限制)。
视图仅仅是用来查看存储在别处数据的一种设施,视图本身并不包含数据,因此返回的数据是从其他的表中检索出来的,在添加或更改这些表中数据时,视图将会返回改变过的数据。
性能问题:
因为视图不包含数据,所以每次使用视图时,都必须处理查询执行时需要的所有检索;如果使用了多个联结和过滤创建了复杂的视图或者嵌套了视图,性能可能会下降得很厉害。
视图的规则和限制
视图创建和使用的一些常见规则和限制:
视图可以使用 CREATE VIEW 语句来创建,只能创建不存在的视图,使用 SHOW CREATE VIEW viewname; 可以查看创建视图的语句,使用 DROP 删除视图,DROP VIEW viewname,覆盖(或者更新)视图,必须先删除它,然后再重新创建。
一个最常见的视图应用就是隐藏复杂的 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;
这个语句创建一个名为 ProductCustomers 的视图,它联结三个表,返回已经订购了任意产品的所有顾客的列表
如果执行 SELECT * FROM ProductCustomers
, 将列出订购了任意产品的顾客,也可以检索订购了特定产品 ANV01 的顾客
代码如下:
SELECT cust_name, cust_contact
FROM ProductCustomers
WHERE prod_id = 'ANV01';
这条语句通过 WHERE
子句从视图中检索特定数据,当 DBMS 处理此查询时,它将指定的 WHERE 子句添加到视图查询中已有的 WHERE 子句中,以便正确过滤数据。可以看出,视图可以极大地简化复杂大的 SQL 语句的使用,利用视图,可以一次性编写基础的 SQL 语句,然后根据需要多次使用。
创建可重用的视图:
创建不绑定特定数据的视图是一个好方法
上述例子中的视图返回订购所有产品而不仅仅是 ANV01 的顾客
扩展视图的范围不仅使得它能够被重用,而且可能更有用,这样可以避免创建和维护多个类似的视图
视图的另一种常见用途是重新格式化检索出的数据
如下代码示例,使用 SELECT
语句在单个组合 计算列中返回供应商的名字和位置
SELECT rtrim(vend_name) + ' ( ' + rtrim(vend_country) + ')'
AS vend_title
FROM Vendors
ORDER BY vend_name;
现在,假设经常需要这个格式的结果,我们不必在每次需要时执行这种拼接,而是创建一个视图,使用它即可
将上述语句转化为视图,如下代码所示:
CREATE VIEW VendorLocations AS
SELECT rtrim(vend_name) + ' ( ' + rtrim(vend_country) + ')'
AS vend_title
FROM Vendors;
使用上述视图,如下所示:
SELECT * FROM VendorLocations;
视图对于应用普通的 WHERE 子句也很有用
例如,可以定义 Customer Email List 视图,过滤没有电子邮件地址的顾客
CREATE VIEW CustomerEmailList AS
SELECT cust_id, cust_name, cust_email
FROM Customers
WHERE cust_email IS NOT NULL;
--这里的where子句过滤了cust_email列中具有null值的那些行,使他们不被检索出来。
SELECT * FROM CustomerEmailList;
从视图检索数据时,如果使用了一条 WHERE 子句,则两组子句(一组在视图中,一组传递给视图的)将自动结合
在简化计算字段的使用上,视图也特别有用
如下代码示例,是 计算字段中使用过的,检索某个订单中的物品,计算每种物品的总价格
SELECT prod_id, quantity, item_price,
quantity*item_price AS expanded_price
FROM OrderItems
WHERE order_num = 20008;
要将其转化为一个视图
CREATE VIEW OrderItemsExpanded AS
SELECT order_num, prod_id, quantity, item_price,
quantity*item_price AS expanded_price
FROM OrderItems;
使用视图
SELECT * FROM OrderItemsExpanded
WHERE order_num = 20008;
总结:
SELECT
语句的层次,可以用来简化数据处理,重新格式化或保护基础数据之前讲述的大多数 SQL 语句都是针对一个或多个表的单条语句;但是并非所有的操作都这么简单,经常会有一些复杂的操作需要多条语句才能完成
例如以下情形:
显然,为了解决上述四个问题,需要针对许多表的多条 SQL 语句;并且需要执行的具体 SQL 语句及其次序也不是固定的,它们可能会根据是否在库存中而发生变化
编写代码的选择:
存储过程的优点:
简要概括一下就是存储过程主要有三个优点:简单、安全、高性能
存储过程的缺点:
存储过程的执行远比编写要频繁得多,但是执行存储过程的语句很简单,即execute。execute接受存储过程名和需要传递给它的任何参数。例如:
execute AddNewProduct('JTS01'
'Stuffed Eiffel Tower',
'6.49',
'Plush stuffed toy with the text LaTour Eiffel in red white and blue');
这里执行一个名为的AddNewProduct存储过程,将一个新产品添加到Products表中。AddNewProduct有四个参数:供应商ID(Vendors表的主键)、产品名,价格和描述。这四个参数匹配存储过程中4个预期变量,此存储过程将新行添加到Products表,并将传入的属性赋给相应的列。
以下是存储过程所完成的工作:
以上就是存储过程执行的基本形式。
下面是MySQL的存储过程的执行:
MySQL 称存储过程的执行为调用,因此,MySQL 执行存储过程的语句为 call。call 接受存储过程的名字和需要传递给它的任何参数
CALL productpricing(@pricelow, @pricehigh, @priceaverage);
这里是执行名为productpricing的存储过程,它计算并返回产品的最低、最高和平均价格。
存储过程可以显示结果,也可以不显示结果
如下示例创建存储过程的代码:
create procedure productpricing()
begin
select Avg(prod_price) as priceaverage
from products;
end;
此存储过程名为 productpricing,用create procedure productpricing()语句定义,如果存储过程接受参数,它们将在()中列举出来。此存储过程没有参数,但后面仍然需要跟();begin和 end 语句用来限定存储过程体,过程体本身仅是一个简单的 select 语句,没有返回数据,因为这段代码并未调用存储过程,这里只是为以后使用而创建它。
mysql 命令行客户机的分隔符:
默认的 MySQL 语句分隔符为 ;,mysql 命令行实用程序也使用 ; 作为语句分隔符,如果命令行实用程序要解释存储过程自身内的 ; 字符,则它们最终不会成为存储过程的成分,反而出现语法错误
解决办法:
临时更改命令行实用程序的语句分隔符
delimiter // 告诉命令行实用程序使用 // 作为新的语句结束分隔符,可以看到标志存储过程结束的 END 定义为 END // 而不是 END;存储过程体内的 ; 仍然保持不动,并且正确地传递给数据库引擎,为恢复为原来的语句分隔符,可以使用 delimiter ;,除 \ 符号外,任何字符都可以用作语句分隔符
mysql> delimiter //
mysql> create procedure productpricing()
-> begin
-> select Avg(prod_price) as priceaverage
-> from products;
-> end //
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER ;
使用存储过程
CALL productpricing();
存储过程实际上是一种函数,所以存储过程名后需要有()符号(即使不传递参数也需要)
删除存储过程
存储过程在创建后,被保存在服务器上以供使用,直至被删除。删除命令从服务器中删除存储过程
drop procedure productpricing;
上述删除是仅当存在时删除,如果指定的过程不存在,则 drop procedure 将产生一个错误,当过程存在想删除时(如果过程不存在也不产生错误)可以使用 drop procedure if exists。
productpricing 只是一个简单的存储过程,它简单地显示 SELECT 语句的结果
一般,存储过程并不显示结果,而是把结果返回给指定的变量
变量(variable): 内存中一个特定的位置,用来临时存储数据
示例:(如果还是在 mysql 命令行中,需要和刚才同样操作)
CREATE PROCEDURE productpricing(
OUT pl DECIMAL(8,2),
OUT ph DECIMAL(8,2),
OUT pa DECIMAL(8,2))
BEGIN
SELECT Min(prod_price) INTO pl FROM products;
SELECT Max(prod_price) INTO ph FROM products;
SELECT Avg(prod_price) INTO pa FROM products;
END;
此存储过程接受3个参数: pl 存储产品最低价格, ph 存储产品最高价格, pa 存储产品平均价格。每个参数必须具有指定的类型,这里使用十进制值
关键字 OUT 指出相应的参数用来从存储过程传出一个值(返回给调用者)。 MySQL 支持 IN(传递给存储过程)、 OUT(从存储过程传出,如这里所用)和 INOUT(对存储过程传入和传出)类型的参数
存储过程的代码位于 BEGIN 和 END 语句内,如前所见,它们是一系列 SELECT 语句,用来检索值,然后保存到相应的变量(通过指定 INTO 关键字)
参数的数据类型:
存储过程的参数允许的数据类型与表中使用的数据类型相同
注意:
记录集不是允许的类型,因此,不能通过一个参数返回多个行和列
为调用此存储过程,必须指定3个变量名
CALL productpricing(@pricelow,
@pricehigh,
@priceaverage);
由于此存储过程要求 3 个参数,因此,必须正好传递 3 个变量,它们是存储过程将保存结果的 3 个变量的名字
所有 MySQL 变量都必须以 @ 开始
在调用时,这条语句不显示任何数据,它返回以后可以显示(或在其他处理中使用)的变量
SELECT @priceaverage;//
为了获得 3 个值,可以使用以下语句:
SELECT @pricehigh, @pricelow, @priceaverage;//
下面时使用 IN和 OUT 参数的例子
ordertotal 接受订单号并返回订单的合计
mysql> CREATE PROCEDURE ordertotal(
-> IN onumber INT,
-> OUT ototal DECIMAL(8,2))
-> BEGIN
-> SELECT Sum(item_price*quantity)
-> FROM orderitems
-> WHERE order_num = onumber
-> INTO ototal;
-> END;//
onumber 定义为 IN,因为订单号被传入存储过程
ototal 定义为 OUT,因为要从存储过程返回合计
SELECT 语句使用这两个参数,WHERE 子句使用 onumber 选择正确的行, INTO使用 ototal 存储计算出来的合计
为调用这个新存储过程,可使用以下语句:
mysql> CALL ordertotal(20005, @total);//
必须给 ordertotal 传递两个参数
第一个参数为订单号,第二个参数为包含计算出来的合计的变量名
为了显示此合计,可如下进行:
mysql> SELECT @total;//
@total已由 ordertotal 的 CALL 语句填写,SELECT 显示它包含的值
为了得到另一个订单的合计显示,需要再次调用存储过程,然后重新显示变量:
mysql> CALL ordertotal(20009, @total);//
mysql> SELECT @total;//
建立智能存储过程
迄今为止使用的所有存储过程基本上都是封装 MySQL 简单的 SELECT 语句
虽然它们全都是有效的存储过程例子,但它们所能完成的工作,直接用这些被封装的语句就能完成
只有在存储过程内包含业务规则和智能处理时,它们的威力才真正显现出来
使用事务处理(transaction processing),通过确保成批的 SQL 操作要么完全执行,要么完全不执行,来维护数据库的完整性。关系数据库把数据存储在多个表中,使数据更容易操作、维护和重用。在某种程度上说,设计良好的数据库模式都是关联的
以 Orders 表为例,订单存储在 Orders 和 OrderItems 两个表中:Orders 存储实际的订单,OrderItems 存储订购的各项物品。这两个表使用称为主键的唯一 ID 互相关联,又与包含客户和产品信息的其他表相关联。
给系统添加订单的过程如下:
假设由于某种数据库故障(如超出磁盘空间、安全限制、表锁等),这个过程无法完成,数据库中的数据会出现什么情况呢?
如果故障发生在添加顾客之后,添加 Orders 表之前,则不会出现什么问题;某些顾客没有订单是完全合法的;重新执行此过程时,所插入的顾客记录将被检索和利用,可以有效地从出现故障的地方开始执行此过程。如果故障发生在插入 Orders 行之后,添加 OrderItems 行前,则数据库中将出现一个空订单。更糟糕的是,如果系统在添加 OrderItems 行之时出现故障,结果是数据库中存在不完整的订单,并且不知道。
为了结果这些问题,就需要使用事务处理
事务处理是一种机制,用来管理必须成批执行的 SQL 操作,保证数据库不包含不完整的操作结果
利用事务处理,可以保证一组操作不会中途停止,它们要么完全执行,要么完全不执行(除非有明确指示)
如果没有错误发生,整组语句提交给(写到)数据表;如果发生错误,则进行回退(撤销),将数据库恢复到某个已知且安全的状态
事务处理的相关术语:
事务(transaction): 指一组 SQL 语句
回退(rollback): 指撤销指定 SQL 语句的过程
提交(commit): 指将未存储的 SQL 语句写入数据库表
保留点(savepoint): 指事务处理中设置的临时占位符(placeholder),可以对它发布回退(与回退整个事务处理不同)
可以回退的语句:
事务处理用来管理insert、update和 delete 语句
不能回退 select语句(实际上,回退也没有必要);也不能回退 create 或 drop 操作
事物处理中可以使用这些语句,但是进行回退时,这些操作不可撤销
还是上述例子,其过程为:
管理事务的关键在于将 SQL 语句组分解为逻辑块,并明确规定数据何时应该回退,何时不应该回退
有的 DBMS 要求明确标识事务处理块的开始和结束,不同的 DBMS 中相关语句不相同,在 MySQL 中的语句为:
START TRANSACTION
...
从上述语句中,可以发现实现并没有明确标识事务处理在何处结束
事务一直存在,直到被中断;通常,COMMIT 用于保存更改,ROLLBACK 用于撤销,具体如下
使用 ROLLBACK
SQL 的ROLLBACK 命令用来回退(撤销)SQL 语句
DELETE FROM Orders;
ROLLBACK;
在上述代码中,执行 DELETE 操作,然后用 ROLLBACK 语句撤销;这说明,在事务处理中,DELETE (INSERT、UPDATE一样)操作并不是最终的结果
使用 COMMIT
一般的 SQL 语句都是针对数据库表直接执行和编写的,也就是所谓的隐式提交(implicit commit),即提交(写或保存)操作是自动进行的
在事务处理块中,提交不会隐式进行
进行明确的提交,使用 COMMIT 语句
使用保留点
使用简单的 ROLLBACK 和 COMMIT 语句,就可以写入或撤销整个事务。但是,只是对于简单的事务才能这样做,复杂的事务可能需要部分提交或回退
例如,之前描述的添加订单的过程就是一个事务;如果发生错误,只需要返回到添加 Orders 行之前即可;不需要回退到 Customers 表(如果存在的话)
要支持回退部分事务,必须在事务处理块中的合适位置放置占位符;当需要回退时,就可以回退到某个占位符
在 SQL 中,这些占位符称为保留点,在 MySQL 中可以使用 SAVEPOINT 语句创建占位符
SAVEPOINT delete1;
每个保留点都要取能够标识它的唯一名字,以便在回退时,DBMS 知道回退到何处,回退到上述代码中保留点的语句为:
ROLLBACK TO delete1;
注意:
可以在 SQL 代码中设置任意多的保留点,越多越好,越能灵活地进行回退
SQL 检索操作返回一组称为结果集的行,这组返回的行都是与 SQL 语句相匹配的行(零行或者多行)
结果集(result set):SQL 查询所检索出的结果
有时,需要在检索出来的行中前进或者后退一行或多行,这就是游标的作用所在
游标(cursor):
是一个存储在 DBMS 服务器上的数据库查询,它不是一条 SELECT 语句,而是被该语句检索出来的结果集
在存储了游标后,应用程序可以根据需要滚动或浏览其中的数据,主要用于交互式应用
不同的 DBMS 支持不同的游标选项和特性,一些常见的选项和特性如下:
1、能够标识游标为只读,使数据能够被读取,但是不能更新和删除
2、能控制可以执行的定向操作(向前、向后、第一、最后、绝对位置、相对位置等)
3、能标识某些列为可编辑的,某些列为不可编辑的
4、规定范围,使游标对创建它的特定请求(如存储过程)或对所有请求可访问
5、指示 DBMS 对检索出的数据(而不是指出表中活动数据)进行复制,使数据在游标打开和访问期间不变化
使用游标涉及以下几个明确的步骤:
声明游标后,可以根据需要频繁地打开和关闭游标;在游标打开时,可以根据需要频繁地执行取操作
创建游标
使用 DECLARE 语句创建游标
DECLARE 命名游标,并定义相应的 SELECT 语句,根据需要带 WHERE 和其他子句
下面的例子,创建一个游标来检索没有电子邮件地址的所有顾客,作为应用程序的组成部分,帮助操作人员找出空缺的电子邮件地址
DECLARE CustCursor CURSOR
FOR
SELECT * FROM Customers
WHERE cust_email IS NULL
使用游标
定义游标后,就可以打开它了
使用 OPEN CURSOR 语句打开游标
OPEN CURSOR CustCursor
在处理 OPEN CURSOR 语句时,执行查询,存储检索出的数据以供浏览和滚动
现在可以使用 FETCH 语句访问游标数据
FETCH 指出要检索那些行,从何处检索它们以及将它们放于何处(如变量名)
关闭游标
游标在使用完毕时,需要关闭
一旦关闭游标,如果不再打开,将不能使用;第二次使用时,不需要声明,只需用 OPEN 打开它即可
关系数据库存储分解为多个表的数据,每个表存储相应的数据
利用键来建立从一个表到另一表的引用(由此产生了术语引用完整性(referential integrity))
正确地进行关系数据库涉及,需要一种方法保证只在表中插入合法数据
例如,如果 Orders 表存储订单信息,OrderItems 表存储订单详细内容,应该保证 OrderItems 中引用的任何订单 ID 都存在于 Orders 中;类似地,在 Orders 表中引用的任意顾客必须存在于 Customers 表中
虽然可以在插入新行时进行检查(在另一个表上执行 SELECT ,以保证所有值合法并存在),但最好不要这样做,因为:
1、如果在客户端曾main上实施数据库完整性规则,则每个客户端都要被迫实施这些规则,一定会有一些客户端不实施这些规则
2、在执行 UPDATE 和 DELETE 操作时,也必须实施这些规则
3、执行客户端检查非常耗时,而 DBMS 执行这些检查会相对高效
管理如何插入或处理数据库数据的规则
DBMS 通过在数据库表上施加约束实施引用完整性
大多数约束是在表定义中定义的,用 CREATE TABLE 和 ALTER 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
);
ALTER TABLE Vendors
ADD CONSTRAINT PRIMARY KEY (vend_id);
外键
外键是表中的一列,其值必须列在另一表的主键中
外键是保证引用完整性的极其重要的部分
举例说明,Orders 表将录入到系统的每个订单作为一行包含其中;
顾客信息存储在 Customers 表中;
Orders 表中的订单通过顾客 ID 于 Customers 表中的特定行相关联;
顾客 ID 为 Customers 表的主键,每个顾客都有唯一的 ID;
订单号为 Orders 表中的主键,每个订单都有唯一的订单号
Orders 表中顾客 ID 列的值不一定是唯一的;如果某个顾客有多个订单,则多行具有相同的顾客 ID(虽然订单号不同)
同时,Orders 表中顾客 ID 列的合法值为 Customers 表中顾客的 ID
在 Orders 的顾客 ID 列上定义了一个外键,因此该列只能接受 Customers 表的主键值
定义外键的方法:
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 关键字,它表示 cust_id 中的任何值都必须是 Customers 表的 cust_id 中的值
相同的工作,也可以在 ALTER TABLE 语句中使用 CONSTRAINT 完成
ALTER TABLE Orders
ADD CONSTRAINT
FOREIGN KEY (cust_id) REFERENCES Customers (cust_id)
外键有助于防止意外删除:在定义了外键后,DBMS 不允许删除在另一个表中具有关联行的行
唯一约束
唯一约束用来保证一列(或一组列)中的数据是唯一的,它们类似于主键,但是存在以下重要区别:
检查约束
检查约束用来保证一列(或者一组列)中的数据满足一组指定的条件
检查约束的常见用途有:
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
);
12345678
利用这个约束,任何插入或者更新的行都会被检查,保证 quantity 的值大于 0
用户定义数据类型:
有的 DBMS 允许用户定义自己的数据类型,它们是定义检查约束(或者其他约束)的基本简单数据类型
索引用来排序数据以加快搜索和排序操作的速度
主键数据总是排序的,这是 DBMS 的工作,因此,按主键检索特定行总是一种快速有效的操作
但是,搜索其他列中的值通常效率不高,解决办法就是使用索引
可以在一个或多个列上定义索引,使 DBMS 保存其内容的一个排过序的列表
定义了索引后,DBMS 以使用书中的索引类似的方式使用它,搜索排过序的索引,找出匹配的位置,然后检索
在开始创建索引前,应该了解:
不同的 DBMS 中创建索引的方式不同
CREATE INDEX prod_name_ind
ON Products (prod_name);
12
索引必须唯一命名
检查索引:
索引的效率随表数据的增加或改变而变化;最好定期检查索引,并根据需要进行调整
触发器时特殊的存储过程,它在特定的数据库活动发生时自动执行
触发器可以与特定表上的 INSERT、UPDATE 和 DELETE 操作(组合)相关联
与存储过程不一样(存储过程只是简单的存储 SQL 语句),触发器与单个的表相关联
与 Orders 表上的 INSERT 操作相关联的触发器只在 Orders 表中插入行时执行
触发器内的代码具有以下数据的访问权:
根据所使用的 DBMS 的不同,触发器可在特定操作执行之前或者之后执行
触发器的常见用途:
一般来说,约束的处理比触发器块,在可能的时候,尽量使用约束
任何安全系统的基础都是用户授权和身份确认
这是一种处理,通过这种处理对用户进行确认,保证他是有权用户,允许执行他要执行的操作
一般来说,需要保护的操作有:
数据库文件:逻辑结构和物理结构
逻辑存储结构:文件的性质:数据文件和日志文件。
数据文件:
首要数据文件:一个数据库必须有一个首要数据文件。(.mdf)
次要数据文件:可以有多个次要数据文件,也可没有。(.ndf)
日志文件:(.ldf)。安装文件、配置文件、错误信息文件。
物理存储结构:以文件的形式存储在磁盘上。
数据库文件组:也分主文件组,次文件组,
日志文件不属于文件组一部分,文件在文件组中不是共享的,即一个文件只属于一个文件组。文件缺省时被分在主文件组中。
创建方式:企业管理器、 T-SQL 语句
1、空间大小修改
2、重命名
3、数据库结构的更改
1、T-SQL 语言;Alert database databasename
2、企业管理器:右键删除
3、键盘 delete
备份设备
企业管理器:添加备份或删除备份设备。
数据库就是按照数据结构来组织、存储和管理数据的仓库,简而言之就是对数据的处理(增、删、改、查)。
1、什么是数据库约束
2、创建、删除和修改约束
1、主键约束(英文)【添加、删除、修改操作方法、参数说明】
2、唯一性约束()
3、检查约束()
4、缺省约束()
5、外键约束()
约束
check约束
primary key 约束
unique约束
default约束
foreign key 约束
(操作方法,两种)
(索引概念、索引作用)
创建索引(方法)
查看、修改、删除索引
约束是在表中定义的用于维护数据库完整性的一些规则通过为表中的列定义,约束可以防止将错误的数据插入表中,也可以保持表之间数据的一致性
数据库完整性控制的基本概念与方法。
1、实体完整性
2、参照完整性
3、用户自定义完整性
4、完整性约束命名子句
5、域中的完整性限制
6、触发器(定义、激活、删除)
为维护数据库的完整性,DBMS必须:
1、提供定义完整性约束条件的机制(DDL)
实体完整性、参照完整性、用户自定义完整性
2、提供完整性检查的方法
检查是否违背了完整性约束 update delete insert
3、违约处理
采取的处理方式 noaction cascade
数据库的完整性是为了保证数据库中储存的数据是正确的
RDBMS关系数据库管理系统(猛击此处)(Relational Database Management System)完整性实现的机制
1、询工具的使用
2、查询语句的语法结构(select语句是T-SQL的核心)
3、子查询(exists子查询、From子句)
4、连接(内连接、外连接、交叉连接)
5、数据汇总(sum、avg)(max、min)(count)(having子句)
6、排序
1、into子句
2、union子句
查询为了方便用户找到对应的数据
创建、修改、删除、使用视图
创建视图
创建视图需要考虑的准则
必须获得创建视图的权限,并且如果使用架构绑定创建视图,必须对视图定义中所引用的表或视图具有适当的权限
使用SSMS创建视图
使用T-SQL创建视图
1、视图的列可以来自不同的表,是表的抽象和再逻辑意义上建立的新关系
2、视图是由基本表(实表)产生的表(虚表)
3、视图的建立和删除不影响表
4、对视图内容的更新(添加、删除和修改)直接影响基本表
5、 视图来自多个基本表时,不允许添加和删除数据
1、视图能简化用户的操作
2、安全性
3、逻辑数据独立性
1、修改
2、删除
3、使用
4、 检索
1、添加记录(Insert Into + Values)
2、修改记录(Update + Set +Where)
3、 删除记录( Delete From + Where )
视图(view)是在基本表之上建立的表,它的结构(即所定义的列)和内容(即所有数据行)都来自基本表,它依据基本表存在而存在。一个视图可以对应一个基本表,也可以对应多个基本表。视图是基本表的抽象和在逻辑意义上建立的新关系
1、视图是已经编译好的sql语句。而表不是
2、视图没有实际的物理记录。而表有。
3、表是内容,视图是窗口
4、表只用物理空间而视图不占用物理空间,视图只是逻辑概念的存在,表可以及时四对它进行修改,但视图只能有创建的语句来修改
5、表是内模式,视图是外模式
6、视图是查看数据表的一种方法,可以查询数据表中某些字段构成的数据,只是一些SQL语句的集合。从安全的角度说,视图可以不给用户接触数据表,从而不知道表结构。
7、表属于全局模式中的表,是实表;视图属于局部模式的表,是虚表。
8、 视图的建立和删除只影响视图本身,不影响对应的基本表。
视图的创建、使用、修改、和删除操作;其中视图的创建、修改和删除操作都讲解了两种实现方法;视图的使用中介绍使用视图进行查询和使用视图操作基本表的方法。
数据库就是对数据进行增删改查。对数据库进行备份、恢复。通过建立视图、创建表、索引等进行操作)其中会有权限限制(进行角色处理、管理等)通过后再对数据库进行自己想要的操作