【DB】 数据库概论

数据库概论

  数据库一直是我的一个痛。。因为不是科班出生,很多知识都有待补充。而数据库这一块,一方面没做过什么必须要用到数据库的大项目,另一方面身边很多同事都和我说数据库这东西很好学(毕竟我们不是专业做数据库开发的,只要会用就行了),所以一直没怎么系统学习过。这次趁着培训讲到SQL,终于在自己电脑上装了个MySQL服务,开始尝试自己使用数据库了。。翻开了很久之前买的《数据库概论》这本书,这篇文章就是它的笔记了。

■  绪论

  书的第一章讲了很多理论性较强的东西。首先是数据存储技术的发展历史。首先数据是靠人工维护管理在实体上的(纸带机和汇编时代),后来人们搞出了文件系统来维护数据不过文件系统的数据存储冗余度大独立性差等问题。再后来就是数据库技术的出现。数据库技术具有数据结构化程度高,冗余度低,独立性高,有统一的DBMS(Database Manage System)程序来调度管理数据和对数据的操作。

   回过头来再来看“数据”究竟为何物。在从自然世界到机器世界,数据含有不同的含义。从我们眼中现实世界里活生生的一个实物,到机器中1100的二进制码,数据的抽象化大约经历了三个阶段。这三个阶段的工作分别被称为概念模型,逻辑模型和物理模型。概念模型用于将现实世界抽象成一个较为简洁有序的模型,做这个工作的主要是数据库的设计人员,他们按照用户的观点来对数据和信息建模。物理模型是最底层的抽象,它描述了数据在系统内部是以怎么样的形态和方式存取,具体的物理模型实现是由数据库软件的开发人员编写的DBMS来完成,数据库设计人员需要知道什么样的物理模型最合适他要设计的数据库。逻辑模型处于概念和物理模型之中,包括了层级模型,网状模型,关系模型等等模型,它将具有一定抽象度的问题转化为信息技术更加能理解的样子。

  具象点来说,概念模型就是根据现实世界中错综复杂地关系画出的一张实体与实体间关系的拓扑图,逻辑模型则是把这张拓扑图具体到库、表、列等数据库的要素,然后物理模型指导了如何把数据库中的结构以合理的方式存储到磁盘等物理介质上去。

  常用的数据(逻辑)模型有很多,但是比较常用的果然还是关系模型。基于这种模型设计出来的数据库自然就是关系型数据库了。

■  关系型数据库

  关系型数据库中有一些看起来十分拗口,但是实际上是挺直白的基本概念。

  关系:在关系型数据库中,“关系”就可以理解成一张二维表,记录了一些元组(元组在这里可以理解成是一条“记录”,元组中每一个值叫做一个“分量”)。很明显,一个关系应该是由跟此关系相关的所有域的笛卡尔乘积的一个子集。比如有三个域:D1={"张三",“李四”}、D2={"男","女"}、D3={"医生","护士"}。他们的笛卡尔乘积就是:

张三 医生
张三 护士
张三 医生
张三 护士
李四 医生
李四 护士
李四 医生
李四 护士

  而现实情况是张三李四只可能有一个性别,且只可能有一个职业,所以真实的关系应该是这个笛卡尔积的一个子集。这样一个关系可以记作R(D1,D2...Dn),其中n是关系的目或者度(Degree),表示一共有几个域参加了这个关系的形成,当n为1时关系称为单元关系,n为2时称为二元关系。。。为了表现一列数据具有统一的性质且独立于其他列的性质,需要为每一个列取个名字,所以一列的名字称为“属性”。可以看到,n元关系至少有n个属性(至少而不是刚好主要是考虑到可能有多个属性来自同一个域)。

  域(Domain):一组具有相同数据类型的值的集合

  候选码:如果一个属性组的值可以唯一地标识一个元组,那么就称该属性组是候选码。如果一个关系有多个候选码,那么就可以选定其中一个座位主键(或者叫主码)。不包含候选码的属性是非主属性或叫非码属性。如果极端情况下,一个关系中所有属性都是这个关系的候选码的话那么称为全码关系。

  对于一个关系型数据库而言,关系可以有三种,分别是基本关系,查询表和视图表。基本关系或叫基本表是指实际存在于存储介质中的表,它是实际数据的逻辑表示。查询表是用户进行查询之后得到的表,而视图表是指由基本表或其他视图表导出的表,是经过DBMS处理的虚表,不对应实际存储的数据。

  ●  基本表的性质

  对于一个基本表,需要明确以下几点性质:

  1. 列内分量是同质的,即同一类型的数据

  2. 不同的列可能出自同一个域,但必须要有不同的属性名

  3. 列的顺序无所谓

  4. 任意两个元组的候选码不能相同

  5. 行的顺序无所谓

  6. 分量必须取原子值,即分量必须是不可再分割的数据单位,不能表中嵌表
  ●  关系模式

  关系的描述称为关系模式,它可以形象地表示为R(U,D,DOM,F)。其中,R是关系名,U是组成该关系的属性名集合,D是属性组U中属性来自的域的集合,DOM是属性向域的映射集合,F是属性间数据的依赖性集合。这样表示一个关系模式逻辑上虽然十分准确,但是有点事无巨细的感觉。一般而言关系模式可以只关注关系名和关系中的各个属性,可以记为R(U)或者R(A1,A2...An)其中A1到An为属性名。而D,DOM这些参数实质上可以通过对属性的类型和长度进行描述来补全。

  关系是关系模式在某一时刻的具体状态或内容,关系模式是静态的稳定的,而关系是动态的不断变化的。

■  关系型数据库的操作与关系的完整性

  关系模型由关系数据结构,关系操作集合以及关系完整性结构三部分组成,之前讲到的都属于关系数据结构范畴内,接下来说明关系操作集合。

  关系模型中常用的操作包括查询(Query),插入(Insert),删除(Delete),修改(Update)四个部分。其中关于查询操作,因为关系型数据库或者说关系模式的特点,具有非常强大的功能。查询操作还可以进一步细分为SELECT,PROJECT,JOIN,DIVIDE,UNION,EXCEPT,INTERSECTION,笛卡尔积等等。在此之中比较常用的是SELECT,PROJECT,UNION,EXCEPT和笛卡尔积。

  关系模型中对数据的操作对象都是集合,也就是所有操作的方式都是一次一集合的方式。即对一个集合对象进行操作返回的也是一个集合对象。相对的,非关系型数据库的操作有可能就是一次一记录的操作。比如比较简单的一个k-v型数据库的一次简单查询得到的只是一条记录。

  对关系的操作通常用关系数据语言来实现,关系数据语言可以分为代数方式的关系代数,或者逻辑方式的关系演算两种。两种方式分别关注通过运算和语义的指定来操作数据。现在通用的SQL语言则是一种间于关系代数类语言和关系演算类语言之中的,具有数据定义和数据控制功能的强大数据库操作语言。具体的SQL说明会在下面单独列出。

  关系的完整性这个术语看着懵逼,其实就是说明了关系中一些参数的约束条件。这些约束性的出现时基于现实世界中的要求。关系模型中的完整性约束可以分为实体完整性,参照完整性和用户定义的完整性。其中实体完整性和参照完整性是所有关系模型必须满足的,用户定义的完整性体现了具体领域中的由语义指出的约束。

  实体完整性指如果属性A是关系R的主属性,那么A的任何一个分量都不能为空值。这是显而易见的,因为当主属性为空就表示该元组无法识别,更不用说区别于其他元组了。 

  参照完整性是指,关系模型中不同的实体之间可能会存在互相引用,这就导致了不同关系与关系间的相互引用。比如 学生(学号,姓名,专业号,年龄) 和 专业(专业号,专业名)两个关系间,在学生关系中专业号是引用自专业关系的。那么学生关系中引用过的专业号必须是存在于专业关系中,而不能是凭空捏造的。上述例子是最简单的一个参照完整性约束的说明,实际上引用可以发生在很多关系之间,甚至可以发生在一个关系内部,这也说明了参照完整性的适用范围十分广泛。

  在给出参照完整性的严格定义之前,首先来看一个概念“外码”。当属性F是关系R的一个非主属性,但同时F与另一个关系R’的主键对应的话,就称F是R的外码,并成R为相对于R'的参照关系,R'相对于R是被参照关系。比如上例中,专业号属性是学生关系的外码,专业关系是被参照关系而学生关系是参照关系。需要指出的是参照关系的外码的属性名并不一定要和被参照关系主键的属性名一致,可以根据实际需要取不同的名字。接下来就可以给出参照完整性的严格定义,就是说属性或属性组F是基本关系R的外码,说明它与基本关系S的主码K相对应,则对于R中F上每个元组的值都必须为空值(所有值都取空值)或者S中某个元素的主码值。

  用户定义的完整性是指某一个具体的应用场景中关系中数据应该满足的条件。比如某个属性只能取唯一值,某个非主属性也不能为空,年龄分数等属性应该在一定合理的逻辑范围内等等。这一部分约束在数据库系统发展早期总是通过应用程序来制定,现在DBMS基本上可以在数据库内部规定好这些约束从而让应用程序的编写者和使用者更加方便。

  教材中第二章末尾还详细讲解了关系代数和关系演算两方面的知识,因为这两种操作方式都已经不再通用,这里就不再详细展开讲了。接下来是重头戏,SQL的说明

■  SQL语言使用

  SQL的全称是Structured Query Language,结构化查询语言。由于其功能强大,现在已经成为关系型数据库的通用型语言了。同时不同厂商根据自己的想法也对SQL有不同的扩展。

  SQL语言将数据查询(DQL),数据操纵与控制(DML/DCL),数据定义(DDL)全部囊括在中,形成了风格统一,可以贯穿数据库整个生命周期的数据库操作语言。同时SQL是一个高度非过程化的语言,意思就是说SQL语言只关心语言得到的结果是什么而不关心具体是怎么做的。怎么做的留给了DBMS的程序来决定,这大大减轻了用户的负担并且提高了数据的独立性。

  相比于高级编程语言,SQL也是非常简单的。其操作的动词只有9个,分别是:

  数据查询  SELECT

  数据定义  CREATE , DROP , ALTER

  数据操控  INSERT , UPDATE , DELETE

  数据控制  GRANT , REVOKE

  ●  SQL的基本概念

  和之前说过(啊咧,还是没说过来着。。)的一样,SQL也是基于一种外模式/模式/内模式的架构,用户可以用SQL对基本表(模式)和视图表(外模式)进行查询和其他操作。在这里,基本表和视图表都是关系。而基本表对应到具体的存储文件则是内模式,一般用户不必关心存储文件的物理结构。

  视图表是从基本表中导出的表,本身不独立存在于存储结构之中。另外用户可以在视图表的基础上进一步导出视图表,视图表在概念和操作上和基本表一样,因为都是关系。

  一般而言SQL是在大多数RDBMS中通用的,但是也有一些RDBMS产品对SQL有自己定制化的改变,与标准SQL可能有10%-15%的出入,需要使用者在使用之前对此有所了解。下面将以建立一个学生数据库为例说明SQL的具体语句操作。

■  数据定义操作

  在这个库中我们大概要建立三张表:

    Student(Sno,Sname,Ssex,Sage,Sdept)  #字段分别是学号,姓名,性别,年龄和院系

    Course(Cno,Cname,Cpno,Ccredit)  #字段分别是课程编号,课程名,要求先修课程和学分数

    SC(Sno,Cno,Grade)  #选课情况表,三个字段分别是学号,课程号,课程成绩

  ●  数据定义

  关系型数据库支持三级模式结构,与外模式,模式,内模式相对应的我们可以操作的基本对象有视图,表,索引。模式作为一个把握数据整体的框架也可以作为对象。这四种对象基本上就是我们可以定义和删除的对象了。定义的关键字是CREATE,删除的关键字是DROP。另外针对表,我们还可以用ALTER关键字进行修改。

  定义模式:

CREATE SCHEMA "test_schema" AUTHORIZATION Frank;

  就是为用户Frank定义了一个模式test_schema。当没有指定模式名的时候默认模式名是用户名。模式从层级的角度来讲就是定义了一个命名空间。在这个命名空间里,被授权的用户可以进一步定义该模式的基本表,视图,索引等等。

  删除模式:

DROP SCHEMA "test_schema" <CASCADE | RESTRICT>;

  参数CASCADE和RESTRICT两者选其一,CASCADE表示删除模式的同时删除所有该模式下的数据库对象包括基本表视图等等,相当于force。如果是RESTRICT就表示在删除模式前对模式做检查,如果发现了其中有数据库对象就放弃删除。

  定义表:

CREATE TABLE Student(
  Sno char(9) primary key,
  Sname char(20) unique,
  Ssex char(2) not null,
  Sage int not null,
  Sdept char(20)
)

  创建了一张表,表的属性都被完整的指出了,需要注意这个顺序还是十分重要的。另外primary key,unique,not null这些关键字修饰了相关属性为主键,候选码,不能为空等。

  再来看如何建立一张课程表:

CREATE TABLE Course(
  Cno char(4) primary key,
  Cname char(10) not null,
  Cpno char(4),
  Ccredit int,
  FOREIGN KEY Cpno REFERENCES Course(Cno)
)

  因为Course表比较特殊,涉及到了一个外码(而且这个外码对应的就是表自身的主键,所以这个外码对应关系中,参照关系和被参照关系是同一个关系)。所以需要用FOREIGN KEY关键字声明。

  最后建立选课表SC:

CREATE TABLE SC(
  Sno char(9),
  Cno char(4),
  Grade int,
  primary key(Sno,Cno),  //指定多重主键可以这么操作
  foreign key (Sno) references Student(Sno),
  foreign key (Cno) refenreces Course(Cno)
)

   ●  数据定义中的域

  在上面例子中可以看到一些域,也就是在创建表的时候声明的各个属性的类型,其实可以看做一种域。常用的(但并非所有DBMS都是这样的)域有:

  char(n)  长度为n的长字符串

  varchar(n)  长度为n的变长字符串,char和varchar的区别在于varchar(10)是指定了字符串最大长度为10,不足10的时候按照实际情况存储,而char(10)不管实际长度多少都是按10个字节存储。相比之下,char由于统一储存格式,效率更高,是一种以空间换时间的做法。

  int  长整数

  smallint  短整数

  float(n)  精度为n的浮点数

  date  日期,格式默认是YYYY-MM-DD

  time  时间,格式默认是HH:MM:SS

  numeric(p,d)  定点数,由p位数字组成,小数点后面d位数

  ●  模式与表之间的关系

  上述定义模式和表的方法都是独立的,没有将两者联系起来。其实可以有:

create schema "test_schema" authorization Frank
  create table testtable(
    test int,
  );
/*以上是在建立模式的同时建立表*/
create table "test_schema".testtable(...);
/*这个是在建立表时把他和一个模式关联*/

  实际上一个表必须属于一个模式,当没有指定模式的时候系统会根据搜索路径来自动找到一个某个表应该属于的模式。

  修改基本表:

  之前说过,基本表可以直接修改,用ALTER关键字。这里的修改指的不是修改某些具体的数据,而是修改表结构,约束条件等更加高层次的修改。下面是一些例子:

alter table Student add S_entrance date;
/*给Student表添加一列“入学时间”的属性*/
alter table Student alter column Sage smallint;
/*将属性Sage的类型从INT改成SMALLINT*/
alter table Course add unique(Cname);
/*为Course表的Cname属性加上unique的约束条件*/
alter table Course drop unique(Cname);
/*删除Course表的Cname属性上的unique约束条件*/

  删除基本表:

  和删除模式类似的:

drop table <表名> [CASCADE | RESTRICT];

  CASCADE和RESTRICT二者选一。缺省情况下是RESTRICT模式。一般RESTRICT的删除会检查表是否被其他基本表引用,是否有视图,有触发器,有函数等等。

  创建索引:

  需要了解一下什么是索引(http://blog.csdn.net/kennyrose/article/details/7532032/)。总的来说,索引是一种为帮助提升查找数据效率而维护在数据库中的一种辅助数据。索引可以提升一次查询时的效率但是代价是占用了一些空间以及增删修改数据时要同步地维护索引。所以综合考虑,对于一些有代表性的数据(比如主键)以及读取频率比较高但是变动比较少的数据可以建立索引。具体的实现索引的技术除了上面那篇博文中提到的B树算法,还有基于hash值的索引等,具体用什么技术由DBMS决定。

  创建索引要基于现有的数据,比如:

create unique index Stusno on Student(Sno);

  这个语句中,unique表示此索引的每一个索引值只对应唯一的数据记录,Stusno是索引名,这句话相当于是以学号的升序(默认是升序)给Student表创建唯一索引

create cluster index Stusname on Student(Sname);

  这个语句中cluster是关键字,表示建立的索引是簇聚索引。簇聚索引是指这个索引中索引项的顺序和表中记录的物理顺序一致。具体性能特点还需进一步学习。。

create unique index SCno on SC(Sno ASC, Cno DESC);

  这句创建了一个唯一索引,其以SC表按学号升序和课程号降序建立。

  删除索引:

  删除索引就是用DROP INDEX <索引名> 就好了。

■  数据查询操作(单表查询)

  查询最主要的是SELECT语句。一般格式的SELECT语句是:

SELECT [ALL | DISTINCT] <目标列表达式> [ ,<目标列表达式> ]...
FROM <视图或表名> [ ,<视图或表名> ]...
[ WHERE <条件表达式> ]
[ GROUP BY <列名1> [ HAVING <条件表达式> ] ]
[ ORDER BY <列名2> [ ASC | DESC ] ]

  最常用的就是SELECT XXX FROM XXX WHERE XXX='XXX'的格式,意思就不多说了。GROUP BY 子句的意思是将结果按照<列名1>指定的列的值进行分组。比如有一个性别列,性别只有男和女,对这个列所有数据做一个set之后依照set中剩下的数据分类对所有元组进行分组。如果还带有HAVING短语,则是只有满足指定条件的组才会被列出。

  如果还有ORDERED BY子句的话,则结果表还会按照<列名2>指定的列的值进行升序或降序的排列。ASC是升序DESC是降序。

  下面是一些查询数据的SQL语句实例:

SELECT Sno,Sname FROM Student;
//选择所有学生的学号和姓名并展示
SELECT Sname,2004 - Sage FROM Student;
//查询一个经过计算的值
SELECT Sname NAME,'Year of Birth:'BIRTH,2004 - Sage BIRTHDAY,LOWER(Sdept) DEPARTMENT FROM Student;
//给选择出来的列取名字,注意这里大小写敏感了,因为要区分值和列名
SELECT DISTINCT Sno FROM SC;
//可以去除结果中重复的值,相当于set了一下。当没有DISTINCT关键字指定时默认这里有个ALL关键字
SELECT Sname FROM Student WHERE Sdept='CS';
//WHERE关键字支持各种各样的比较条件搜索,除了=外还有> , < , >= , <= , != , NOT等等
SELCT Sname FROM Student WHERE Sage BETWEEN 20 AND 23;
//除了比较还有确定范围,也有NOT BETWEEN 20 AND 23这种
SELECT Sname FROM Student WHERE Sdept IN ('CS','MA');
//还有IN和NOT IN
SELECT Sname FROM Student WHERE Sdept LIKE 'C%'
//LIKE和NOT LIKE关键字用于字符串匹配,后面跟的字符串是一个简单的类似正则串的东西,下面详细讲它的匹配规则。
SELECT Sname FROM Student WHERE Sdept IS NULL;
//判断是否是空值,注意空值是不能用=NULL的!
SELECT Sname FROM Student WHERE Sage < 20 and Sdept = 'CS';
//AND,OR,NOT联结多个条件实现多重条件的查询

  *关于LIKE的正则规则:LIKE后面跟的表达式通常用引号引起来,用到的正则逻辑符号主要是两个,一个是%。%相当于经典正则中的.*,即匹配从0个到任意多个任意字符。另一个是_,下划线相当于经典正则中的.,代表一个字符。如果要匹配%或者_本身那么在表达式中的两个符号前面加上\就可以转义了。比如LIKE 'DB\_config'。

  另外需要注意的一点是中文字符并不是一个字符,而是根据不同的编码格式而有2-3个字符,所以在用_匹配中文字符时应当注意多写几个。比如LIKE '欧阳__'匹配的是所有欧阳开头后面还跟着一个汉字的值。

  上面的所有实例都有一个共同的特点,就是FROM关键字后面都是一张表,即单表查询。实际上,和SELECT后面的子句一样,可以在FROM后面写上多张表然后用逗号分隔。这么做的结果就是初次选择出的结果是两个表所有元组组成的笛卡尔乘积。这显然不是很有意义。

  ●  聚集函数

  为了方便用户,SQL还自带了很多聚集函数,即通过它们可以方便地调出一些数据的综合属性。常用的聚集函数和用法如下:

  COUNT( [ DISTINCT | ALL ] * )  统计元组个数,参数DISTINCT和ALL可以控制是否统计重复出现项

  COUNT( [ DISTINCT | ALL ] <列名> )  统计某一个属性下有多少值

  SUM( [ DISTINCT | ALL]  <列名> )  求算一列值的总和(数据必须都是数值型的)

  AVG( [ DISTINCT | ALL ] <列名> )  求算一列值的平均(数据必须都是数值型的)

  MAX( [ DISTICT | ALL ] <列名> )  求一列值中的最大值

  MIN( [ DISTINCT | ALL ] <列名> )  求一列值中的最小值

  例子:

SELECT COUNT(DISTINCT Sno) FROM SC;
/*从选课表统计学生人数,因为一个学生选了多门课,所以要用DISTINCT关键字来去除重复的*/
SELECT AVG(Grade) FROM SC WHERE Cno='1';
/*统计选择1号课程的所有人的平均分*/
SELECT SUM(Ccredit) FROM SC,Course WHERE Sno='20121001' AND SC.Cno=Course.Cno;
/*统计学号为20021001学生在选课表中选课的总学分数*/

   需要注意的一点是,聚集函数中除了COUNT(*)之外,其余的都会略过一个列中的空值而只处理非空值。

  GROUP BY子句将查询结果按照指定的某一列或者多列的值对所有查询所得结果进行分组,常常用于对聚集函数所得到结果的进一步细化处理。比如:

SELECT Cno,COUNT(Sno) FROM SC GROUP BY Cno;
/*当没有groupby子句的时候,选择出错,因为不可能既选出实在数据又统计选择数目。但是加上groupby之后先用Cno为结果分组,然后得到结果再用COUNT计数,得到的就是选择每门课的人数是多少的表格了*/
SELECT Sno FROM SC GROUP BY Sno HAVING COUNT(*) > 3;
/*这里除了groupby子句,还用到了having条件。和where条件不同,having条件主要作用于组而不是基本表或者视图*/

■  数据查询操作(连接查询)

  以上提到的所有查询操作都是基于一张基本表或者视图的,实际应用中有很多查询涉及到两个及以上的表,这种查询时连接查询。细分连接查询还可以有等值连接查询,自然连接查询,自身连接查询,外连接查询等等。

 ●  等值连接查询和非等值连接查询

  利用where关键字来连接两个表的条件称为连接条件。当连接符是=时查询为等值连接查询,其他符号时则为非等值连接查询。通常要求where子句中的条件字段是两个表中的公共字段,比如:

SELECT student.*,SC.* FROM student,SC WHERE student.Sno = SC.Sno;
/*在属性名之前加上“表名.”是为了防止混淆。没有where子句的时候很明显得到的是student和SC两表的笛卡尔积,在有了子句之后,子句就可以从笛卡尔积中筛选出一些符合子句条件的元组了*/

  上面这个例子中选出来的结果中会有两列Sno,因为选择的两个表都是*。如果去掉一列Sno,那么这个结果也被称为自然连接(即没有重复属性列)。比如手动地去除了多余的一列Sno的话:

SELECT Student.Sno,Sname,Ssex,Sage,Sdept,Cno,Grade FROM Student,SC WHERE Student.Sno = SC.Sno;
/*因为Sname,Ssex这些属性名都是独一无二的,没必要特别之处是student.的,这样的select就避免了重复属性的出现,从概念上来说这就是一个自然连接*/

 ●  自身连接

  连接操作不仅在两个表之间可以进行,也可以在同一个表内自己连接自己。比如在Course表中我们设计了每个课程都可能有一个先修课程的编号。如果我们想找到每门课程和其先修课程编号的对应关系的话就可以如下这么操作:

SELECT FIRST.Cno,SECOND.Cpno FROM Course FIRST,Course SECOND WHERE FIRST.Cpno = SECOND.Cno;
/*为了实现自身连接,首先搞出两张表并取不一样的名字(FIRST和SECOND),然后通过以上形式来连接两张表中的不同字段*/

●  外连接

  在上面说到的等值连接的例子中,如果存在某些学生没有选课,那么他们的记录就会存在于stdudent表但是不存在于SC表中。这样子的话在等值连接选出来的结果中必然是没有这些学生的。如果想要让这些学生也出现在结果中那么就要用到外连接:

SELECT student.Sno,Sname,Ssex,Sage,Sdept,Cno,Grade FROM student LEFT OUT JOIN SC ON (student.Sno = SC.Sno);
/*或者写成FROM student LEFT OUT JOIN SC USING (Sno)*/

  外连接的定义就是让连接过程中,因为空值存在而使得部分会被忽略的元组也出现在结果中且空值用null填充。外连接还分成左外连接和右外连接和全连接,左外连接保证左边的表中所有元组都在结果中出现,右外连接则保证右边的全出现,全连接就是两者并集。相对的,一般意义上的连接就是内连接。

  ●  复合查询

  上面所有的查询过程中,where后面的查询条件都只有一个条件。用AND,OR,NOT这些关键字则可以使用复合条件查询。比如:

SELECT student.Sno,Sname,Cname,Grade FROM student,SC,Course WHERE student.Sno = SC.Sno AND SC.Cno = Course.Cno;
/*这个多表查询中,首先关联了student和SC中的学号字段,筛选出了所有选了课的学生,然后再关联了SC和Course表中的课程号字段,将结果中的课程号变为课程名*/

  正如上所示,运用关键字可以进行多重表的查询,也被称为多表连接。

■  数据库查询操作(嵌套查询)

  一个SELECT-FROM-WHERE句子被称为一个查询块,把一个查询块嵌套在另一个查询块的WHERE子句或者HAVING短语的条件中的查询叫做嵌套查询。比如:

SELECT Sname FROM student WHERE Sno IN (
  SELECT Sno FROM SC WHERE Cno='2'
);
/*内层查询从SC表中查出所有Cno是2的记录,然后以此为IN的依据再筛选从student中选出的数据*/

  需要注意的是,SQL虽然允许多重嵌套,比如在内层查询中再嵌套一个内层查询。但是对于ORDER BY子句,它只能修饰最外层的查询结果,即不能再任何一个内层查询中使用。此外,上面的查询中可以看到,内层查询(或者叫子查询)跟父查询时不相关的。此时这个查询也被称为不相关子查询。

  上面这个例子也可以通过之前说过的连接的方法来实现,比如SELECT Sname FROM student,SC WHERE student.Sname = SC.Sname and SC.Cno = '2'; 对于同样的查询结果,肯定有各种各样不同的查询方法和SQL语句,当数据量大的时候不同的查询语句可能带来性能上的差距是很大的。所以这就需要数据库的优化了。

  既然有不相关子查询,那么就肯定有相关子查询了。下面这个例子就是一个相关子查询:

SELECT Sno,Cno FROM SC x WHERE Grade > (
  SELECT AVG(Grade) FROM SC y WHERE y.Sno=x.Sno
)
/*选择每一个学生分数高于其所有课程平均分的课程
*注意这里where后面跟的还不是等号而是大于号
*另外子查询计算的是哪门课的平均分这个是根据每个父查询中Sno不同而决定的,所以是个相关子查询*/

  原理可以理解为这样:父查询中先选得一个Sno,然后把这个具体的值传入子查询中,计算出这个学生所有分数的平均分,再返回父查询和当时的Grade进行比较得出结果。

 ●  带有ANY或者ALL的嵌套查询

  还有一种嵌套查询中带有谓词ANY(部分系统中也有SOME的说法)或ALL。使用两者时必须要配合使用一些比较运算符。比如=,>=等等。一个典型的应用SQL句子:

select Sname,Sage from student where Sage<ANY(select Sage from student where Sdept='CS') AND Sdept != 'CS';
/*选择所有年龄比某一个计算机系学生小(相当于只要找到计算机系学生最大年龄,比那个数字小即可)的非计算机系学生,显示他们的名字和年龄*/

  而相对应的ALL就是代表了要比所有计算机系学生年龄小(即要小于所有计算机系学生中最小年龄)。从逻辑上来说,ANY,ALL可以和其他一些聚合函数和关键字做一些等价替换。比如where xx=ANY(...)就相当于是where xx IN (...);而<ALL(..)和>ALL(...)就相当于是<MIN(...)和>MAX(...)的意思了。

●  带EXISTS谓词的嵌套查询

  从自然语言的角度来思考,很容易把EXISTS和IN两个关键字搞混。其实在SQL中,EXISTS后面跟着的子查询是不返回任何查询结果而只返回一个true或者false的逻辑判断,如果查询结果为空返回false反之返回true。比如下面这个SQL句子:

select Sname from student where exists(select * from sc where Sno=student.Sno and Cno='1');
/*子查询是查看一个特定的学号指代的学生有没有选修1号课程,如果没有选子查询自然是空集,返回的是false,然后这个Sno的那个元组就不被父查询select出来。总的来说,这个句子的意义就是选择所有选择了1号课程的学生名字*/

  因为EXISTS不需要返回值,所以有时候是比其他查询方法更加高效的一个途径。

  *EXISTS有很多高级的用法,我看也看不懂。。以后在研究把

■  数据库查询操作(集合查询)

  每个SELECT句得到的都是一个子集,多个SELECT得到的集合之间自然可以进行一些集合操作。集合操作的关键字主要有UNION(并集操作),INTERSECT(交集)和EXCEPT(差集)。注意,参加集合擦操作的各个查询子集的列数必须相同且对应字段的数据类型也必须相同。

  比如查询所有计算机系和年龄不大于19岁的学生:

select * from student where Sdept='CS'
UNION
select * from stduetn where Sage <= 19;
/*SQL是无所谓分行的,这么写纯粹是为了好看。当然如果愿意的话不用特地写集合的运算,可以直接Sdept='CS' or Sage<=19*/

  在这里的计算中,系统会自动滤去重复的部分即年龄不大于19岁的计算机系学生的元组,这些元组只出现一遍。如果要保留重复元组那么集合关键字就应该写成UNION ALL。

  类似的我们可以很轻易地看出把UNION换成INTERSECT的话就可以算得交集,即不大于19岁的计算机系学生了。同样的可以将其改造成AND连接两个条件的一句SELECT语句。

  关于差集,也是类似的,只不过是写在前面的集合减去写在后面的集合。如果想改造成相对应的一条SELECT语句,就需要逻辑上对条件做一个修改然后用AND连接条件即可。比如:

select * from student where Sdept='CS'
EXCEPT
select * from student where Sage<=19;
/*选择出年龄大于19的所有计算机系学生,可以改造成下面的形式*/
select * from student where Sdept='CS' AND Sage>19;

  

■  数据查询操作总结

  在经历上述所有操作之后,就可以感受到SELECT这个语句是多么的丰富多彩了。实际上,SELECT正是SQL中的核心语句,其一般格式如下:

SELECT [ALL|DISTINCT] <目标列表达式> [别名] [,<目标列表达式> [别名]]...
FROM <表名或视图名> [别名] [,<表名或视图名> [别名]]...
[WHERE <条件表达式>]
[GROUP BY <列名1> [HAVING <条件表达式>]]
[ORDERED BY [ASC|DESC]]

  1.目标列表达式可选形式

  *  全部

  <表名>.*  某特定表的全部(写属性名就是指定字段,下面不多废话了)

  COUNT([DISTINCT|ALL] *)  对元组数计数,COUNT也可以换成其他聚集函数如SUM,AVG,MAX,MIN等。

  2. WHERE表达式可选形式

  <属性列名> <比较运算符> <属性列名>/<常量>/[ANY|ALL] (SELECT子查询)

  <属性列名> [NOT] BETWEEN <属性列名>/<常量>/(SELECT子查询) AND <属性列名>/<常量>/(SELECT子查询)

  <属性列名> [NOT] IN (值1,值2)/(SELECT子句)

  <属性列名> [NOT] LIKE <匹配串>  //注意匹配串的格式不是经典的正则,而是一种简化版的正则表达式

  <属性列名> [NOT] EXISTS (SELECT子句)

  以上的条件还可以通过AND,OR等关键字来进行条件串联。

■  数据更新(插入数据)

  数据更新可以分成向表中添加,从表中删除,以及修改表中的若干行数据。这里先来说说插入数据

  插入数据用到了INSERT关键字,可以分成插入元组(插入一行数据)以及插入子查询结果(插入多行数据)。

  ●  插入元组

  INSERT语句的一般格式是:

insert
into <表名> [(<属性列1>,...)]
values(<值1>[,<值2>...])

  SQL规定,如果into后面没有跟任何属性列的说明的话,那么默认values后面的各个值根据定义表时属性的顺序,依次赋给新元组的各个属性,不能多一个或者少一个且顺序严格对应。如果into后面写了属性列的话,那么就需要把values中的值和这里写出来的属性列的顺序一一对应,没有写出的属性列默认赋空值,此外还要注意得让定义表时说明的NOT NULL的字段不能是空值否则会报错。

  ●  插入子查询

  一行一行插入数据效率太低了,SQL支持将一个复杂的子查询的结果批量地插入表中。比如我想计算每个系学生的平均年龄然后插入另一个统计平均年龄的表格中的话就可以这么做:

insert
into Dept_age(Sdept,avg_age)
select Sdept,AVG(Sage) from student group by Sdept;

  当然,最好就是能够做到子查询的属性是和指出的表属性顺序一一对应,这样可以减少麻烦。

■  数据更新(修改数据)

  修改数据是指对表的某些部分进行修改,用到的关键字是UPDATE。一般格式为:

UPDATE <表名>
SET <列名>=<表达式> [,<列名>=<表达式>]...
[WHERE <条件>];
/*运用UPDATE和SET来修改数据,WHERE限定了修改的地方,如果没有WHERE那么就是修改所有元组的相关数据了*/

  除了基本的条件表达式,也可以在表达中加入子查询比如:

update sc
set Grade=Grade+1
where 'CS'=(select Sdept from student where student.Sno=sc.Sno)
/*不要忘了常量还可以写前面这种操作
这个句的作用就是把所有计算机系学生的课程成绩+1*/

■  数据更新(删除数据)

  删除语句的格式一般是:

DELETE FROM <表名>
[WHERE <条件>];

  当需要清空一个表但是不删除这个表本身的时候,我们不能用DROP而是用DELETE比如:

DELETE FROM sc;

  

■  视图操作 定义视图

  视图是从一个或者几个表中导出的表,与实际存在的基本表不同,视图表是一个虚表,数据库中只存放视图的定义而不存放视图中的数据。这些数据只在原先的基本表中存放。当原来基本表中数据发生了变化,那么在视图中的响应数据也就发生了变化。

  视图是一个独立的对象,可以被创建,查询,删除,也可以在视图的基础上定义出新的视图,对于视图的更新(增删改)操作规则是有一定的限制的。

  视图的定义用CREATE VIEW(也可被称为视图的建立),其一般操作格式是:

CREAATE VIEW <视图名> [(<列名> [,<列名>]...)]
AS <子查询>
[WITH CHECK OPTION]/*子查询可以是任何复杂的SELECT语句,但是通常不包括ORDERED BY和DISTINCT等要素
当有WITH CHECK OPTION存在时,表示这个视图的增删改操作要遵循子查询中的条件表达式给出的条件
组成视图的属性名要么全部给出要么全部省略,省略时默认其属性名就是子查询得到的属性名
*/

  

猜你喜欢

转载自www.cnblogs.com/franknihao/p/7157768.html