运行环境和数据:

  • sql server 2019,Microsoft SQL Server Management Studio 18
  • 数据:
  1. 创建表:在Microsoft SQL Server Management Studio 18中运行下面程序
-----------------------------------------------------------
-- Sams Teach Yourself SQL in 10 Minutes
-- http://forta.com/books/0672336073/
-- Example table creation scripts for Microsoft SQL Server.
------------------------------------------------------------------------------------
-- Create Customers table
-------------------------
CREATE TABLE Customers
(cust_id      char(10)  NOT NULL ,cust_name    char(50)  NOT NULL ,cust_address char(50)  NULL ,cust_city    char(50)  NULL ,cust_state   char(5)   NULL ,cust_zip     char(10)  NULL ,cust_country char(50)  NULL ,cust_contact char(50)  NULL ,cust_email   char(255) NULL
);--------------------------
-- Create OrderItems table
--------------------------
CREATE TABLE OrderItems
(order_num  int          NOT NULL ,order_item int          NOT NULL ,prod_id    char(10)     NOT NULL ,quantity   int          NOT NULL ,item_price decimal(8,2) NOT NULL
);----------------------
-- Create Orders table
----------------------
CREATE TABLE Orders
(order_num  int      NOT NULL ,order_date datetime NOT NULL ,cust_id    char(10) NOT NULL
);------------------------
-- Create Products table
------------------------
CREATE TABLE Products
(prod_id    char(10)      NOT NULL ,vend_id    char(10)      NOT NULL ,prod_name  char(255)     NOT NULL ,prod_price decimal(8,2)  NOT NULL ,prod_desc  varchar(1000) NULL
);-----------------------
-- Create Vendors table
-----------------------
CREATE TABLE Vendors
(vend_id      char(10) NOT NULL ,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
);----------------------
-- Define primary keys
----------------------
ALTER TABLE Customers WITH NOCHECK ADD CONSTRAINT PK_Customers PRIMARY KEY CLUSTERED (cust_id);
ALTER TABLE OrderItems WITH NOCHECK ADD CONSTRAINT PK_OrderItems PRIMARY KEY CLUSTERED (order_num, order_item);
ALTER TABLE Orders WITH NOCHECK ADD CONSTRAINT PK_Orders PRIMARY KEY CLUSTERED (order_num);
ALTER TABLE Products WITH NOCHECK ADD CONSTRAINT PK_Products PRIMARY KEY CLUSTERED (prod_id);
ALTER TABLE Vendors WITH NOCHECK ADD CONSTRAINT PK_Vendors PRIMARY KEY CLUSTERED (vend_id);----------------------
-- Define foreign keys
----------------------
ALTER TABLE OrderItems ADD
CONSTRAINT FK_OrderItems_Orders FOREIGN KEY (order_num) REFERENCES Orders (order_num),
CONSTRAINT FK_OrderItems_Products FOREIGN KEY (prod_id) REFERENCES Products (prod_id);
ALTER TABLE Orders ADD
CONSTRAINT FK_Orders_Customers FOREIGN KEY (cust_id) REFERENCES Customers (cust_id);
ALTER TABLE Products ADD
CONSTRAINT FK_Products_Vendors FOREIGN KEY (vend_id) REFERENCES Vendors (vend_id);
  1. 向表中填充数据
-------------------------------------------------------------
-- Sams Teach Yourself SQL in 10 Minutes
-- http://forta.com/books/0672336073/
-- Example table population scripts for Microsoft SQL Server.
----------------------------------------------------------------------------------------
-- Populate Customers table
---------------------------
INSERT INTO Customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES('1000000001', 'Village Toys', '200 Maple Lane', 'Detroit', 'MI', '44444', 'USA', 'John Smith', 'sales@villagetoys.com');
INSERT INTO Customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
VALUES('1000000002', 'Kids Place', '333 South Lake Drive', 'Columbus', 'OH', '43333', 'USA', 'Michelle Green');
INSERT INTO Customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES('1000000003', 'Fun4All', '1 Sunny Place', 'Muncie', 'IN', '42222', 'USA', 'Jim Jones', 'jjones@fun4all.com');
INSERT INTO Customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES('1000000004', 'Fun4All', '829 Riverside Drive', 'Phoenix', 'AZ', '88888', 'USA', 'Denise L. Stephens', 'dstephens@fun4all.com');
INSERT INTO Customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
VALUES('1000000005', 'The Toy Store', '4545 53rd Street', 'Chicago', 'IL', '54545', 'USA', 'Kim Howard');-------------------------
-- Populate Vendors table
-------------------------
INSERT INTO Vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES('BRS01','Bears R Us','123 Main Street','Bear Town','MI','44444', 'USA');
INSERT INTO Vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES('BRE02','Bear Emporium','500 Park Street','Anytown','OH','44333', 'USA');
INSERT INTO Vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES('DLL01','Doll House Inc.','555 High Street','Dollsville','CA','99999', 'USA');
INSERT INTO Vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES('FRB01','Furball Inc.','1000 5th Avenue','New York','NY','11111', 'USA');
INSERT INTO Vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES('FNG01','Fun and Games','42 Galaxy Road','London', NULL,'N16 6PS', 'England');
INSERT INTO Vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES('JTS01','Jouets et ours','1 Rue Amusement','Paris', NULL,'45678', 'France');--------------------------
-- Populate Products table
--------------------------
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('BR01', 'BRS01', '8 inch teddy bear', 5.99, '8 inch teddy bear, comes with cap and jacket');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('BR02', 'BRS01', '12 inch teddy bear', 8.99, '12 inch teddy bear, comes with cap and jacket');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('BR03', 'BRS01', '18 inch teddy bear', 11.99, '18 inch teddy bear, comes with cap and jacket');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('BNBG01', 'DLL01', 'Fish bean bag toy', 3.49, 'Fish bean bag toy, complete with bean bag worms with which to feed it');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('BNBG02', 'DLL01', 'Bird bean bag toy', 3.49, 'Bird bean bag toy, eggs are not included');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('BNBG03', 'DLL01', 'Rabbit bean bag toy', 3.49, 'Rabbit bean bag toy, comes with bean bag carrots');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('RGAN01', 'DLL01', 'Raggedy Ann', 4.99, '18 inch Raggedy Ann doll');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('RYL01', 'FNG01', 'King doll', 9.49, '12 inch king doll with royal garments and crown');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('RYL02', 'FNG01', 'Queen doll', 9.49, '12 inch queen doll with royal garments and crown');------------------------
-- Populate Orders table
------------------------
INSERT INTO Orders(order_num, order_date, cust_id)
VALUES(20005, '2012-05-01', '1000000001');
INSERT INTO Orders(order_num, order_date, cust_id)
VALUES(20006, '2012-01-12', '1000000003');
INSERT INTO Orders(order_num, order_date, cust_id)
VALUES(20007, '2012-01-30', '1000000004');
INSERT INTO Orders(order_num, order_date, cust_id)
VALUES(20008, '2012-02-03', '1000000005');
INSERT INTO Orders(order_num, order_date, cust_id)
VALUES(20009, '2012-02-08', '1000000001');----------------------------
-- Populate OrderItems table
----------------------------
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 1, 'BR01', 100, 5.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 2, 'BR03', 100, 10.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20006, 1, 'BR01', 20, 5.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20006, 2, 'BR02', 10, 8.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20006, 3, 'BR03', 10, 11.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20007, 1, 'BR03', 50, 11.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20007, 2, 'BNBG01', 100, 2.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20007, 3, 'BNBG02', 100, 2.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20007, 4, 'BNBG03', 100, 2.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20007, 5, 'RGAN01', 50, 4.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20008, 1, 'RGAN01', 5, 4.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20008, 2, 'BR03', 5, 11.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20008, 3, 'BNBG01', 10, 3.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20008, 4, 'BNBG02', 10, 3.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20008, 5, 'BNBG03', 10, 3.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 1, 'BNBG01', 250, 2.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 2, 'BNBG02', 250, 2.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 3, 'BNBG03', 250, 2.49);

1 数据库

1.1.1 数据库:

  • 指保存有组织的数据的容器(通常是一个文件或一组文件)
  • 数据库软件并不是数据库,而应该称为数据库管理系统(DBMS)
  • 数据库是通过DBMS(database manage studio)创建和操纵的容器,具体来说,各种数据库都有区别。

1.1.2 表

  • 在文件夹中存放资料时,会创建一个小文件夹,将相关的资料放入特定的文件中。这种文件再数据库中称为表。表可以保存客户清单,产品目录或者其他信息清单。
表(table)
某种特定类型数据的结构化清单
  • 存储在表中的数据是同一种类型的数据或者清单。
  • 数据库中的每一个表都有一个名字来标识自己,这个名字必须在数据库中唯一存在。
  • 虽然在数据库中不能使用相同表名,但不同数据库中完全可以使用相同的表名
  • 表具有一些特性,这些特性定义了数据在表中如何存储,包含存储什么样的数据,数据如何分解,各部分信息如何命名等信息。描述表的这组信息就是所谓的模式(schema),模式可以用来描述数据库中特定的表,也可以用来描述整个数据库(和其中表的关系)
模式
关于数据库和表的布局以及特性的信息

1.1.3 列和数据类型

  • 表由列组成,列存储表中的某部分信息
列(column)
表中的一个字段,所有表都是由一个或者多个列所组成的
  • 将数据库表想想成为一个网格,就像电子表格那样,网格中的每一列存储着某种特定的信息
分解数据
正确的将数据分解为多个列很重要,通过分解数据,才有可能利用特定的列对数据进行分类和过滤。如果城市和州组合在一个列中,则按州进行分类或者过滤就会十分困难
  • 数据库中每一个列都有相应的数据类型。数据类型(datatype)定义了列可以存储那些数据种类。例如:如果列中存储的是数字,则相应的数据类型应该为数值类型。若列中存储的是日期,文本,注释,金额等,则应该规定好恰当的数据类型
数据类型:
所允许的数据类型。每个表列都有相应的数据类型,它限制(或允许)该列存储的数据
  • 数据类型限定了可存储在列中的数据种类,帮助正确的分类数据,并在优化磁盘使用方面起重要作用。

1.1.4 行

  • 表格中的数据是按行存储的,所保存的每个记录存储在自己的行内。
  • 例如:顾客表可以每行存储一个顾客,表中的行编号为记录的编号

1.1.5 主键

  • 表中的每一行都应该有一列(或几列)可以唯一标识自己。顾客表可以使用顾客编号,而订单表可以使用订单ID,雇员表可以使用雇员ID或雇员社会安全号。
主键(primary key)
一列(或一组列),其值能够唯一标识表中的每一行
  • 唯一标识表中每行的这个列(或这几个列)称为主键。主键用来标识一个特定的行,没有主键,更新或者删除表中的特定行就极为困难,因为你不能保证操作只涉及相关的行。
应该总是定义主键
虽然并不是总需要主键,但多数数据库设计者都会保证它们城建的每一个表具有一个主键,以便于以后的数据操作和管理。
  • 表中的任何列都可以作为主键,只要他满足以下条件
  • 任意两行都不具有相同的主键值
  • 每一行都必须具有一个主键值(主键列不允许NULL值)
  • 主键列中的值不允许修改或者更新
  • 主键值不能重用(若某行从表中删除,那么它的主键不能赋值给后面的新行)
  • 主键通常定义在表的一列上,但也可以一起使用多列作为主键,在使用多列作为主键时,上述条件必须 应用到所有列,且所有列值的组合必须是唯一的。

什么是SQL

  • SQL即Structure Query Language(结构化查询语言)的缩写,是一种专门用来与数据库沟通的语言。
  • 与其他高级语言不同,SQL中包含的词汇很少。设计SQL的目的是很好的完成一项任务–提供一种从数据库中读写数据的简单有效的方法。
  • SQL有如下的优点:
  • SQL不是某个特定数据库供应商专有的语言,几乎所有重要的DBMS都支持SQL。
  • SQL所有语句都是由很强的描述性英语单词组成,而且数目并不多
  • 灵活使用SQL语言元素,可以进行非常复杂和高级的数据库操作
SQL扩展
许多DBMS厂商通过增加语句或指令,对SQL进行了扩展。扩展的目的是提供执行特定操作的额外功能或简化方法。
标准SQL由ANSI标准委员会管理,从而称为ANSI SQL.所有主要的DBMS,即使有自己的扩展,也都会支持ANSI SQL.各个实现有自己的名称,如PL/SQL,Transact-SQL.
后面讲述的主要是ANSI SQL

附录A

A.1样例表

  • 后面案例中使用的表是一个假想玩具经销商使用的订单录入系统的组成部分。这些表用来完成以下任务:
  • 管理供应商
  • 管理产品目录
  • 管理顾客列表
  • 录入顾客订单
这里使用的表不完整,现实世界中的订单录入系统还会记录这里所没有的大量数据(如工资和记账信息,发货追踪信息等).不过,这些表确实示范了现实世界中遇到的各种数据的组织和关系,可以将这些技术用于自己的数据库

  • 这里使用Microsoft SQL Server进行数据录入

检索数据

  • 主要介绍如何使用SELECT语句从表中检索一个或者多个数据列

2.1 SELECT语句

  • 每个SQL语句都是由一个或者多个关键字构成的,SELECT语句的作用是从一个或者多个表中检索信息。
关键字(keyword)
作为SQl组成部分的保留字,不能用作表或者列的名称
  • 为了使用SELECT检索表数据,必须给出两条信息–想选择什么,以及从什么地方选择。

2.2 检索单个列

SELECT prod_name
FROM Products;
  • 上述语句利用SELECT语句从Products表中检索一个名为prod_name的列,所需的列名称写在SELECT关键字之后,FROM关键字指出从哪个表中检索数据。
未排序数据
若没有明确排序查询结果,则返回的数据没有特定的顺序(一般会有一个默认排序),返回数据的顺序可能是数据被添加到表中的顺序,也可能不是。只要返回相同数目的行,就是正常的。
结束SQL语句:
多条SQL语句必须以分号分割,多数DBMS不需要在单条SQL语句后面加上分号,但始终加上分号总不是坏事。
SQL语句大小写:
SQL语句不区分大小写,因此SELECT和select是相同的。
只是许多SQL开发人员喜欢对SQL关键字使用大写,而对列名和表名使用小写,这样做能使代码更易于阅读
在处理SQl语句时,所有的空格都会被自动忽略

2.3 检索多个列

  • 仍然使用SELECT语句,唯一的不同是必须在SELECT关键字后给出多个列名,列名之间必须以逗号分隔。
  • 注意:最后一个列名后面不能添加逗号
SELECT prod_id,prod_name,prod_price
FROM Products;
  • 这里指定了三个列名,列名与列名之间用逗号分割。
数据表示:
从输出可以看到,SQL语句一般返回原始的,无格式的数据。数据的格式化是表示问题,而不是检索问题。因此,表示(如把上面的价格值显示为正确的十进制数值货币金额)
一般在显示数据的应用程序中规定。通常很少直接使用实际检索出来的数据。

2.4 检索所有列

  • 在实际列名的位置使用*号通配符可以做到这一点
SELECT *
FROM Produts;
  • 如果给定一个通配符*,则返回表中的所有列。列的顺序一般是列在表定义中出现的物理顺序。
  • 使用通配符:一般不会使用,但往往会检索很多不需要的列,从而造成资源的浪费
  • 检索未知列:由于不明确指定列名,所以能检索出表中名字未知的列

2.5检索不同的值

  • 上面的输出中返回的都是所有匹配的行,但如果不希望每个值每次都出现。例如,检索products表中所有产品供应商的ID(类似集合set)
SELECT vend_id
FROM Produts;
  • SELECT语句返回9行,即使只有三个产品供应商,因为products表中有9种产品,那么如何检索不同的值?
  • 使用DISTINCT(去重) 关键字:它指示数据库只返回不同的值
SELECT DISTINCT vend_id
FROM Produts;
  • 使用DISTINCT关键字告诉DBMS只返回不同的vend_id行。
  • 注意:不能部分使用DISTINCT,DISTINCT关键字作用于所有的列,不仅仅是跟在其后的那一列,指定多列则会进行组合,类似于分组。

2.6 限制结果

  • 如果只想返回第一行或者一定数量的行,该如何做呢?在SQL Server中,使用TOP关键字来限制最多返回多商行,例如:
SELECT TOP 5 prod_name
FROM Products
  • 上面代码使用SELECT TOP 5语句,只检索前5行数据。
  • 为了得到后面5行数据,需要指定从哪开始以及检索的行数,如:
SELECT prod_name
FROM Products
LIMIT 5 OFFSET 5;
-- SQL SERVER不适用

2.7 使用注释:

  • 随着SQL语句变长,复杂性增加,会需要一些描述性的注释;或者为了测试代码需要注释掉一部分代码。
  • 使用–来进行行内注释
  • 在一行的开头使用#,则一整行都将作为注释。
  • 也可以使用/**/进行多行注释

3. 排序检索数据:

  • 主要使用SELECT语句中的ORDER BY子句,根据需要排序检索出数据。

3.1 排序数据

  • 如果直接使用SELECT关键字返回数据库表中的单个列,其输出并没有特定的顺序。
  • 如果不排序,数据一般将以它在底层表中出现的顺序显示,这有可能是数据最初添加到表中的顺序。关系数据库设计理论认为:如果不明确规定排序顺序,则不应该假定检索出的数据顺序有任何的意义。
  • 子句(clause):SQL语句由子句构成,一个子句通常由一个关键字加上所提供的数据组成。子句的例子有我们之前在select语句中的from子句。
  • 为了明确的排序用SELECT语句检索出来的数据,使用ORDER BY子句进行排序。ORDER BY子句取一个或多个列的名字,据此对输出进行排序。例如:
SELECT prod_name
FROM Products
ORDER BY prod_name;
  • 除了指示DBMS软件对prod_name列以字母顺序排序数据的ORDER BY子句之外,这条语句与前面的语句相同。
  • ORDER BY 子句的位置:在指定一条ORDER BY子句的时候,应该保证它是SELECT语句中最后一条子句,如果它不是最后的子句,将会出现错误消息。
  • 通过非选择列进行排序:通常,RODER BY子句中使用的列将是为显示而选择的列。但是,实际上并不一定要这样,用非检索的列排序数据是完全合法的。

3.2 按多个列排序

  • 经常需要按不止一个列进行数据排序。例如,如果要显示雇员名单,可能希望按照姓和名进行排序(首先按照姓排序,然后在每个姓中再按照名排序)。
  • 要按照多个列排序,只需要简单指定列名,列名之间用逗号分开即可(和选择多个列类似)
  • 下面的代码检索3个列,并按照其中两个列对结果进行排序。(默认num从小到大,string从a~z)
SELECT prod_id,prod_price,prod_name
FROM Products
ORDER BY prod_price,prod_name;
  • 重要的是理解按多个列排序的时候,排序的顺序完全按照规定进行。换句话说,对于上述例子中的输出,仅在多个行具有相同的prod_price值的时候才对产品按照prod_name的顺序进行排序(也就是排序有先后顺序,逗号前面的先排),如果prod_price列中所有的值都是唯一的,则不会按照prod_name进行排序。

3.3按列位置进行排序

  • 除了能用列名指定出排序顺序之外,ORDER BY还支持按照相对列位置进行排序:
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY 2, 3;
  • 可以看到,这里的输出与上面的查询相同,唯一不同的就是ORDER BY可以直接指定列的相对位置(SELECT选择的列),而不需要绝对名称。
  • 好处是不需要重新输入列名。缺点是不明确给出列名有可能造成错用列名排序;其次,在对SELECT清单进行更改的时候容易错误地对数据进行排序(忘记对ORDER BY子句进行相应的改动)
  • 最后,如果进行排序的列不在SELECT清单中,显然不能够使用这个技术
  • 如果有必要,可以混合使用实际列名和相对列位置

3.4 指定排序方向

  • 数据排序不限于升序排序,这只是默认的排序顺序,还可以使用OREDER BY子句进行降序排列,为了使用降序排列,必须指定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列指定DESC,对prod_name列不指定。因此,prod_price列以降序排序,而prod_name列(每个价格内)仍然按照标准的升序排序。
  • 如果想要在多个列上进行降序排序,必须对每一个列指定DESC关键字。

4.过滤数据

  • 使用SELECT语句的WHERE子句指定搜索条件

4.1 使用WHERE子句

  • 数据库表一般包含大量的数据,很少需要检索表中的所有行。对想要搜索的数据需要指定搜索条件,搜索条件也称为过滤条件。
  • 在SELECT语句中,数据根据WHERE子句中指定的搜索条件进行过滤。
SELECT prod_name, prod_price
FROM Products
WHERE prod_price = 3.49;
  • 这条语句从products表中检索两个列,但不反回所有行,只返回prod_price值为3.49的行。

4.2 WHERE子句操作符

  • 子句操作符:
=;<>(不等于);!=;<;<=;!<;>;>=;!>;BETWEEN;IS NULL

4.2.1 检查单个值

  • 列出所有价格小于10$的产品
SELECT prod_name, prod_price
FROM Products
WHERE prod_price < 10;

4.2.2 不匹配检查

  • 列出所有不是供应商DLL01制造的产品:
SELECT vend_id, prod_name
FROM Products
WHERE vend_id <> 'DLL01';
  • 单引号用来限制字符串,如果将值与字符串类型的列进行比较,就需要限定引号;用来与数值列进行比较的值不需要引号。
  • <>和!=通常可以替换,主要是看使用的DBMS支不支持

4.2.3 范围值检查

  • 例如使用BETWEEN操作符检索价格在5~10$之间的产品,使用BETWEEN … AND …
SELECT prod_name, prod_price
FROM Products
WHERE prod_price BETWEEN 5 AND 10;
  • 从这个例子可以看到,在使用BETWEEN时,必须指定两个值——所需范围的低端值和高端值。这两个值必须用AND关键字分隔。BETWEEN匹配范围中所有的值,包括指定的开始值和结束值。

4.2.4 空值检查

  • 在创建表时,表设计人员可以指定其中的列能否不包含值。在一个列不包含值时,称其包含空值NULL。
SELECT cust_name
FROM CUSTOMERS
WHERE cust_email IS NULL;

高级数据过滤

5.1 组合WHERE子句

  • 为了进行更强的过滤控制,SQL允许给出多个WHERE子句。这些子句有两种使用方式,即以AND子句或者OR子句的方式使用
操作符(operator)
用来联结或改变 WHERE子句中的子句的关键字,也称为逻辑操作符
(logical operator)。

5.1.1 AND操作符

要通过不止一个列进行过滤,可以使用AND操作符给WHERE子句附加条件。下面的代码给出了一个例子:

SELECT prod-ID,PROD-price,prod-NAME
FROM Produts
WHERE vend_id='DLL01' AND prod_price<=4
  • 此SQL语句检索由供应商DLL01制造且价格小于等于4美元的所有产品的名称和价格。
  • 如果需要添加ORDER BY子句,那么它应该放在WHERE子句之后

5.1.2 OR操作符

  • 和AND使用类似,代码示例:
SELECT prod_name, prod_price
FROM Products
WHERE vend_id = 'DLL01' OR vend_id = ‘BRS01’;

5.1.3 求值顺序

  • 在同时使用多个OR和AND语句的过程中,若没有括号,则默认AND优先级大于OR优先级
  • 可以使用括号提升优先级
  • 例如:
SELECT prod_name, prod_price
FROM Products
WHERE (vend_id = 'DLL01' OR vend_id = 'BRS01')AND prod_price >= 10;

5.2 IN操作符

  • 类似于python中的in,IN操作符用来指定条件范围(集合).IN取一组由逗号分隔,阔在圆括号内的合发值,例如:
SELECT prod_name, prod_price
FROM Products
WHERE vend_id IN ( 'DLL01', 'BRS01' )
ORDER BY prod_name;
  • 本质上,IN操作符是在简化OR操作符的内容
  • IN的最大优点是可以包含其他SELECT语句,能够更动态地建立WHERE子句。

5.3 NOT操作符

NOT
WHERE子句中用来否定其后条件的关键字。
SELECT prod_name
FROM Products
WHERE NOT vend_id = 'DLL01'
ORDER BY prod_name;
  • 对于简单地WHERE子句,使用NOT没有优势,但在更复杂地子句中,NOT可以与IN操作符连用,非常简单地找出与条件列表不匹配地行

6. 使用通配符进行过滤

  • 主要介绍什么是通配符,如何使用通配符以及怎样使用LIKE操作符进行通配搜索,以便于对数据进行复杂过滤

6.1 LIKE操作符:

  • 前面地操作符都是针对已知值进行过滤,强调必须是完整存在的值。但这种过滤方法并不是任何时刻都好用,例如如何搜索产品中包含文本been bag的所有产品?
  • 用简单的比较操作符无法达成目的时,可以构造一个通配符搜索模式,找出在产品名的任何位置出现的bean bag的产品。(类似正则表达式)
通配符(wildcard)
用来匹配值的一部分的特殊字符。
搜索模式(search pattern)
由字面值、通配符或两者组合构成的搜索条件。
  • 通配符本身实际上是 SQL的WHERE子句中有特殊含义的字符,SQL支持几种通配符。为在搜索子句中使用通配符,必须使用LIKE操作符。LIKE指示 DBMS,后跟的搜索模式利用通配符匹配而不是简单的相等匹配进行比较。
  • 通配符搜索只能用于文本字段,非文本数据类型字段不能使用通配符搜索。

6.1.1 %通配符

  • 最常使用的通配符是百分号(%)。在搜索串中,%表示任何字符出现任意次数。例如,为了找出所有以词Fish起头的产品,可发布以下SELECT语句:
SELECT prod_id,prod_name
FROM Products
WHERE prod_name LIKE 'Fish%';
  • 此例子使用了搜索模式Fish%,在执行这条子句时,将检索任意以Fish起头的词,%告诉DBMS接受Fish之后的任意字符,不论多少。
  • 注意:搜索可以选择区分大小写,这个需要在系统中进行配置
  • 通配符可在搜索模式中的任意位置使用,并且可以使用多个通配符。下面的例子使用两个通配符,它们位于模式的两端:
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE '%bean bag%';
  • 注意:%可以匹配任何东西,除了NULL

6.1.2 下划线_通配符

  • _的用途和%一样,不过只匹配单个字符,而不是多个字符
  • 例如:
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE '__ inch teddy bear';
  • 每一个_总是刚好匹配一个字符,不能多也不能少

6.1.3 方括号([])通配符

  • 方括号([])通配符用来指定一个字符集,必须匹配指定位置的一个字符(也就是或的意思)。
  • 例如,找出所有名字以J或M起头的联系人,可以进行如下查询:
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]%'
  • 当然,也可以使用NOT cust_contact LIKE '[JM]%'实现类似结果

创建计算字段

  • 和python的函数定义类似

7.1 计算字段

  • 存储在数据库表中的数据不是应用程序所需要的格式,例如:
  • 需要显示公司名,同时还需要显示公司的地址,但这两个信息存储在不同的表列中。
  • 城市、州和邮政编码存储在不同的列中,但邮件标签打印程序需要把它们作为一个有恰当格式的字段检索出来
  • 列数据大小写混合的,但报表程序需要把所有的数据按照大写表示出来
  • 物品订单表存储物品的价格和数量,不存储每个物品的总价格。但为了打印发票,需要计算物品的总价格。
  • 上述每个例子中,存储在表中的数据都不是应用程序所需要的。我们需要直接从数据库中检索出转换,计算或格式化过的数据,而不是检索出数据,然后再在客户端应用程序中重新格式化
  • 这就是计算字段可以排上用场的地方了。计算字段并不实际存在于数据库表中。计算字段是运行时在SELECT语句内创建的
字段(field)
基本上与列(column)的意思相同,经常互换使用,不过数据库列一
般称为列,而术语字段通常与计算字段一起使用。

7.2 拼接字段

  • Vendors表包含供应商名和地址信息。假如要生成一个供应商报表,需要在格式化的名称(位置)中列出供应商的位置。
  • 解决办法是把两个列拼接起来。在SQL的SELECT语句中,可以使用一个特殊的操作符来拼接两个列。根据使用的DBMS,此操作符为+或||
  • 例如:
SELECT vend_name + ' (' + vend_country + ')'
FROM Vendors
ORDER BY vend_name;
  • 上面两个SELECT语句拼接以下元素:
  • 存储在vend_name列中的名字;
  • 包含一个空格和一个左圆括号的字符串;
  • 存储在vend_country列中的国家;
  • 包含一个右圆括号的字符串。
  • 由于空格是我们所不需要的,这个部分可以使用SQL的RTRIM()函数来完成
SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')'
FROM Vendors
ORDER BY vend_name;
  • RTRIM()函数去掉值右边的所有空格。通过使用 RTRIM(),各个列都进行了整理。
说明:TRIM 函数
大多数 DBMS都支持RTRIM()(正如刚才所见,它去掉字符串右边的
空格)、LTRIM()(去掉字符串左边的空格)以及 TRIM()(去掉字符
串左右两边的空格)。

使用别名:

  • 为拼接后的地址字段赋予列名,使用AS关键字进行赋予,便于引用与查询(不会改变原表格,相当于生成新表格)
SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')' AS vend_title
FROM Vendors
ORDER BY vend_name;

7.3 执行算术计算

  • 检索订单号20008中的所有物品:
SELECT prod_id, quantity, item_price
FROM OrderItems
WHERE order_num = 20008;
  • 计算汇总物品的价格
SELECT prod_id,quantity,item_price,quantity*item_price AS expanded_price
FROM OrderItems
WHERE order_num = 20008;
计算字段始终是在SELCET内的
  • SQL支持+,-,*,/,圆括号可以用来区分优先顺序
提示:如何测试计算
SELECT语句为测试、检验函数和计算提供了很好的方法。虽然SELECT
通常用于从表中检索数据,但是省略了FROM子句后就是简单地访问和
处理表达式,例如SELECT 3 * 2;将返回 6,SELECT Trim(' abc ');
将返回 abc,SELECT Now();使用 Now()函数返回当前日期和时间。
现在你明白了,可以根据需要使用SELECT语句进行检验。

8. 使用函数处理数据

8.1 函数

  • SQL也可以使用函数来处理数据。函数一般是在数据上执行的,为数据的转换和处理提供了方便。
  • 需要注意的是,每个DBMS都有特定的函数,事实上,只有少数几个函数被所有主要的DBMS等同地支持。虽然所有类型地函数一般都可以在每一个DBMS中使用,但每个函数的名称和语法可能及其不同。例如:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QiVmdi8g-1658224348313)(image1.jpg)]
  • 可以看到,与SQL语句不一样,SQL函数是不可移植的。这意味着特定SQL实现编写的代码在其他实现中可能不正常。

8.2 使用函数

8.2.1 文本处理函数

  • 例如®TRIM(L)可以取出列值的空格。
  • 这个例子使用UPPER()函数将文本转换为大写
SELECT vend_name, UPPER(vend_name) AS vend_name_upcase
FROM Vendors
ORDER BY vend_name;

以下是一些常用的文本处理函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ioze3Kf4-1658224348315)(image2.jpg)]

SOUNDEX需要做进一步的解释。SOUNDEX是一个将任何文
本串转换为描述其语音表示的字母数字模式的算法。 SOUNDEX考虑了
类似的发音字符和音节,使得能对字符串进行发音比较而不是字母比
较。虽然 SOUNDEX不是 SQL概念,但多数 DBMS都提供对 SOUNDEX
的支持。
  • 下面给出一个使用SOUNDEX()函数的例子。Customers表中有一个顾客Kids Place,其联系名为Michelle Green。但如果这是错误的输入,此联系名实际上应该是Michael Green,该怎么办呢?显然,按正确的联系名搜索不会返回数据,如下所示:
SELECT cust_name, cust_contact
FROM Customers
WHERE cust_contact = 'Michael Green';
  • 现在试一下使用 SOUNDEX()函数进行搜索,它匹配所有发音类似于Michael Green的联系名:
SELECT cust_name, cust_contact
FROM Customers
WHERE SOUNDEX(cust_contact) = SOUNDEX('Michael Green');
  • 在这个例子中,WHERE子句使用SOUNDEX()函数把cust_contact列值和搜索字符串转换为它们的 SOUNDEX值。因为 Michael Green和
    Michelle Green发音相似,所以它们的SOUNDEX值匹配,因此WHERE子句正确地过滤出了所需的数据。

8.2.2 日期和时间处理函数

  • 以SQL Server为例,检索2012年的所有订单:
SELECT order_num
FROM Orders
WHERE DATEPART(yy, order_date) = 2012;
这个例子(SQL Server和 Sybase版本以及Access版本)使用了DATEPART()
函数,顾名思义,此函数返回日期的某一部分。DATEPART()函数有两个
参数,它们分别是返回的成分和从中返回成分的日期。在此例子中,
DATEPART()只从order_date列中返回年份。通过与2012比较,WHERE
子句只过滤出此年份的订单。

8.2.3 数值处理函数

  • 在主要的DBMS函数中,数值函数是最一致,最统一的函数。
  • 常用的有
ABS()
COS()
EXP()
PI()
SIN()
SQRT()
TAN()
...

关于具体DBMS所支持的算术处理函数,可以参阅相应的官方文档。

9 汇总数据

  • 主要介绍SQL的聚集函数

9.1 聚集函数

  • 主要是确定表中的行数(或者满足某个条件或包含某个特定值的行数)
  • 获得表中某些行的和
  • 找出表列(或所有行或某些特定行)的最大,最小,平均值。
聚集函数:
对某些运行的函数,计算并返回一个值
AVG():返回某列的平均值
COUNT():返回某列的行数
MAX()
MIN()
SUM()
  • 使用:
SELECT AVG(prod_price) AS avg_price
FROM Products;
  • 求特定行列的平均值
SELECT AVG(prod_price) AS avg_price
FROM Products
WHERE vend_id = 'DLL01';
  • AVG()函数忽略值为NULL的行
  • COUNT(*)对表中的行的数目或过滤后的行的数目进行计数,不论数值中是否有NULL
  • COUNT(column)只计数非NULL的行数
SELECT COUNT(*) AS num_cust
FROM Customers;
SELECT COUNT(cust_email) AS num_cust
FROM Customers;
  • MAX(),MIN(),SUM()函数同理,同时这三个函数都忽略值为NULL的行

9.2 聚集不同值

  • 对所有执行计算,指定ALL参数或者不指定参数(因为ALL是默认行为)
  • 如果只包含不同的值,需要指定DISTINCT参数
  • 例如:
SELECT AVG(DISTINCT prod_price) AS avg_price
FROM Products
WHERE vend_id = 'DLL01';
  • 注意,DISTINCT不能用于COUNT(*),而且用于MIN,MAX其实没有任何的意义

9.3 组合聚集函数

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;

10 分组数据

  • 主要涉及到两个新的SELECT语句子句:GROUP BY子句和HAVING子句

10.1 数据分组

  • 目前位置的所有计算都是在表的所有数据或匹配特定的where子句的数据上进行的,比如:
SELECT COUNT(*) AS num_prods
FROM Products
WHERE vend_id = 'DLL01';
  • 若需要返回每一个供应商提供的产品数目,或者返回只提供一项产品的供应商产品等需求时,使用分组可以将数据分为多个逻辑组,对每个组进行聚集计算。

10.2 创建分组

  • 分组是使用GROUP BY子句建立的
SELECT vend_id, COUNT(*) AS num_prods
FROM Products
GROUP BY vend_id;
  • 上面的 SELECT语句指定了两个列:vend_id包含产品供应商的 ID,num_prods为计算字段(用 COUNT(*)函数建立)。GROUP BY子句指示DBMS按vend_id排序并分组数据。这就会对每个vend_id而不是整个表计算 num_prods一次。从输出中可以看到,供应商 BRS01有 3个产品,供应商DLL01有4个产品,而供应商FNG01有2个产品。
  • 使用GROUP BY子句前的一些规定
  • GROUP BY子句可以包含任意数目的列, 因而可以对分组进行嵌套。更细致的进行分组。
  • GROUP BY子句必须出现在WHERE子句之后,OREDR BY 子句之前

10.3 过滤分组

  • 由于WHERE子句过滤指定的是行而不是分组,SQL为此提供了另一个子句,即HAVING子句。HAVING非常类似于WHERE,事实上,目前为止所有的WHERE子句都可以用HAVING来替代,唯一的差别是WHERE过滤行,而HAVING 过滤分组
  • HAVING支持所有的WHERE操作符
  • 例如:
SELECT cust_id,COUNT(*) AS orders
FROM Orders
GROUP BY cust_id
HAVING COUNT(*)>=2;
  • 这条SELECT语句的前三行类似于上面的语句。最后一行增加了HAVING子句,它过滤COUNT(*) >= 2(两个以上订单)的那些分组。
  • 假如想进一步过滤上面的语句,使它返回过去 12个月内具有,两个以上订单的顾客。为此,可增加一条WHERE子句,过滤出过去 12个月内下过的订单,然后再增加HAVING子句过滤出具有两个以上订单的分组。
  • WHERE和HAVING,GROUP BY组合
SELECT vend_id, COUNT(*) AS num_prods
FROM Products
WHERE prod_price >= 4
GROUP BY vend_id
HAVING COUNT(*) >= 2;
  • WHERE子句过滤所有prod_price至少为4的行,然后按vend_id分组数据,HAVING子句过滤计数为 2或 2以上的分组。

10.4 分组和排序

SELECT order_num, COUNT(*) AS items
FROM OrderItems
GROUP BY order_num
HAVING COUNT(*) >= 3
ORDER BY items, order_num;

10.5 SELECT子句顺序

  • 回顾一下SELECT语句中子句的顺序:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4RrUEJpc-1658224348316)(image3.jpg)]

11 使用子查询

11.1 子查询

  • SELECT语句是SQL的查询,我们迄今位置看到的所有SELECT语句都是简单查询,即从单个数据库表中检索数据的单条语句。
  • SQL还允许创建子查询(subquery),即嵌套在其他查询中的查询。

11.2 利用子查询进行过滤

  • 文章中使用的所有数据库表都是关系表。订单存储在两个表中,每个订单包含订单编号,客户ID,订单日期。在ORDERS表中存储为一行。各订单的物品存储在相关的OrdersItems表中。Orders表不存储顾客信息,只存储顾客ID,顾客的实际信息存储在Customers表中。
  • 加入需要列出订购物品RGAN01的所有顾客,应该如何检索?
  1. 检索包含物品你RGAN01的所有订单的编号(OrderItems表中)
  2. 检索具有前一步骤列出的订单编号的所有顾客ID
  3. 检索前一步骤返回的所有顾客ID的顾客信息。
  • 上述的每个步骤都可以单独作为一个查询来执行,可以把一条SELECT语句返回的结果用于另一条SELECT语句的WHERE子句。
  • 也可以使用子查询来把三个查询组合成一条语句。
SELECT order_num
FROM OrderItems
WHERE prod_id='RGAN01'
  1. 现在,我们知道了哪个订单包含要检索的物品,下一步查询与订单20007和20008相关的顾客ID
SELECT cust_id
FROM Orders
WHERE order_num IN (20007,20008);
  1. 结合这两个查询,把第一个查询(返回订单号)变为子查询:
SELECT cust_id
FROM Orders
WHERE order_num IN (SELECT order_numFROM OrderItemsWHERE prod_id = 'RGAN01');
  • SELECT语句中,子查询总是从内向外处理。
格式化 SQL
包含子查询的 SELECT语句难以阅读和调试,它们在较为复杂时更是
如此。如上所示,把子查询分解为多行并进行适当的缩进,能极大地
简化子查询的使用。
顺便一提,这就是颜色编码起作用的地方,好的 DBMS客户端正是出
于这个原因使用了颜色代码 SQL。
  1. 现在得到了订购物品RGAN01的顾客的ID,下一步是检索这些顾客ID的顾客信息。检索两列的SQL语句,并转换为子查询:
FROM Customers
WHERE cust_id IN (SELECT cust_idFROM OrdersWHERE order_num IN (SELECT order_numFROM OrderItemsWHERE prod_id = 'RGAN01'));
  • 由此可见,在WHERE子句中使用子查询能够编写出功能很强且很灵活的SQL语句。

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

  • 假如需要显示customers表中每个顾客的订单总数。订单与相应的顾客ID存储在Orders表中
  • 执行这个操作,需要两步:
  1. 从Customers表中检索顾客列表
  2. 对于检索出的每一个顾客,统计其在Orders表中的订单数目
  • 可以使用一条WHERE子句来过滤某个特定的顾客ID,仅对该顾客的订单进行计数。
  • 要对每个顾客执行COUNT(*),应该将它作为一个子查询:
SELECT cust_name, cust_state,(SELECT COUNT(*) FROM Orders WHERE Orders.cust_id = Customers.cust_id) AS orders
FROM Customers
  • 这里本质上是走了一个循环,内部的Orders.cust_id会分别比较外部的每一个Customers.cust_id,并分别求和。这条子查询对检索的每一个顾客执行一次,在此例中,子查询执行了五次,因为检索出了五个顾客。
  • WHERE Orders.cust_id = Customers.cust_id告诉SQL,比较Orders表中的cust_id和当前正从Customers表中检索的cust_id.

12 联结表

12.1 联结

  • 联结是利用SQL的SELECT能执行的最重要的操作。

12.1.1 关系表

  • 为了理解关系表,先来看一个例子:
  • 有一个包含产品目录的数据库表,其中每类物品占一行。对于每一种物品,要存储的信息包括产品描述,价格,以及生产该产品的供应商。
  • 现在有同一供应商生产的多种物品,那么在何处存储供应商名,地址,联系方法等供应商信息呢?将这些数据与产品信息分开存储的理由是:
  1. 同一供应商生产的产品,其供应商信息都是相同的,对于每个产品重复此信息既浪费时间又浪费存储空间
  2. 如果公共上信息发声变化,例如供应商迁址或者电话号码变动,只需要修改一次即可。
  3. 如果有重复数据(每种产品都存储供应商信息),则很难保证每次输入该数据的方式都相同。不一致的数据在报表中就很难利用。
  • 核心是,相同的数据多次出现并不是一件好事,这是关系数据库设计的基础。关系表的设计就是要把信息分解成多个表,一类数据一个表,各个表通过某些共同的值互相关联(所以才叫关系数据库)
  • 在这个例子中可以建立两个表,一个存储供应商信息,另一个存储产品信息。Vendors表包含所有的供应商信息,每个供应商占一行,具有唯一地表示,此表示称为主键(primary key),可以是供应商ID或其他任意的唯一值。
  • Products表只存储产品信息,除了存储供应商ID(Vendors表的主键)外,它不存储其他有关供应商的信息。Vendors表的主键将Vendors表与Products表关联,利用供应商ID能从Vendors表中找出相应的供应商详细信息。
  • 这样做的好处是:
    1. 供应商信息不重复,不会浪费时间和空间
    2. 如果供应商信息变动,可以只更新Vendors表中的单个记录,相关表中的数据不用改动
    3. 由于数据不重复,数据显然是已知的,使得处理数据和生成报表更加简单。
  • 总之,关系数据可以有效地存储,方便地处理,因此,关系数据库地可伸缩性远比非关系数据库要好
可伸缩(scale)
能够适应不断增加的工作量而不失败。设计良好的数据库或应用程序
称为可伸缩性好(scale well)。

12.1.2 为什么使用联结

  • 如果将数据存储在多个表中,怎样用一条SELECT语句就检索出数据呢?
  • 答案是使用联结。简单来说,联结是一种机制,用来在一条SELECT语句中关联表,因此称为联结。使用特殊地语法,可以联结多个表返回一组输出,联结在运行时关联表中正确地行。

12.2 创建联结

  • 创建联结分厂简单,指定要连接地所欲表以及关联它们地方式即可。
  • 例如:
SELECT vend_name, prod_name, prod_price
FROM Vendors, Products
WHERE Vendors.vend_id = Products.vend_id;
  • 和上文中的子查询案例相似,这里一共查询了COUNT(Product.vend_id)次
  • 这两个表用where子句正确的联结,where子句指示dbms将Vendors表中的vend_id与Products表中的vend_id进行匹配。

12.2.1 WHERE子句的重要性

  • 使用WHERE子句建立联结关系似乎有点奇怪,但实际上是有一个很充分理由的。在一条SELECT语句联结几个表的时候,相应的关系是在运行中构造的。在数据库表的定义中没有指示DBMS如何对表进行联结的内容。在联结两个表时,实际要做的是将第一个表中的每一行与第二个表中的每一行进行配对。WHERE子句作为过滤条件,只包含那些匹配给定条件的行。
笛卡儿积(cartesian product)
由没有联结条件的表关系返回的结果为笛卡儿积。检索出的行的数目
将是第一个表中的行数乘以第二个表中的行数。
  • 为了理解这一点,例如:
SELECT vend_name, prod_name, prod_price
FROM Vendors, Products;

12.2.2 内联结

  • 目前为止使用的联结称为等值联结(equijoin),它基于两个表之间的相等测试。这种联结也称为内连接。其实,可以对这种联结使用稍微不同的语法,明确指定联结的内省。例如:
SELECT vend_name, prod_name, prod_price
FROM Vendors INNER JOIN Products
ON Vendors.vend_id = Products.vend_id;

12.2.3 联结多个表

  • 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;
  • 这个例子显示订单20007中的物品。订单物品存储在OrderItems表中。每个产品按其产品 ID存储,它引用Products表中的产品。这些产品通过供应商 ID联结到Vendors表中相应的供应商,供应商 ID存储在每个产品的记录中。这里的FROM子句列出三个表,WHERE子句定义这两个联结条件,而第三个联结条件用来过滤出订单20007中的物品。

13 创建高级联结

  • 主要介绍另外一些联结,如何使用表别名,如何对被联结的表使用聚集函数。

13.1 使用表别名

  • SQL除了可以对列名和计算字段使用别名之外,还允许给表名起别名。这样做主要有两个理由:

    1. 缩短sql语句;
    2. 允许在一条SELECT语句中多次使用相同的表
  • eg:
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 使用不同类型的联结:

13.2.1 自联结

  • 使用表别名的一个主要原因是能在一条SELECT语句中不指一次引用相同的表:
  • 假如要给JIM JOHNS同一公司的所有顾客发送一封信件。这个查询要求先找出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的引用具有歧义性。解决此问题,需要使用表别名,将一个表当成两个不同的两个表看待。

13.2.2 自然联结

  • 无论何时对表进行联结,应该至少有一列不止一次出现在一个表中(被联结的列)。标准的联结(如内联结)返回所有的数据,相同的列可能多次出现。自然联结排除多次出现,使每一列只返回一次。
  • 自然联结要求你只能选择那些唯一的列,一般对一个表使用通配符SELECT*,而对其他表使用明确的自己来完成。
SELECT C.*, O.order_num, O.order_date,OI.prod_id, OI.quantity, OI.item_price
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.3 外联结

  • 许多联结将一个表中的行与另一个表中的行相关联,但有时候需要包含没有关联行的那些行。例如:

    1. 对每个顾客下的订单进行计数,包括那些至今尚未下订单的顾客
    2. 列出所有产品以及订购数量,包括没有人订购的产品
    3. 计算平均销售规模,包括那些至今尚未下订单的顾客
  • 对于那些联结包含了相关表中没有关联行的行,这种联结称为外联结。
  • 外联结语法和内联结类似,要检索包括没有订单顾客在内的所有顾客,可如下进行:
SELECT Customers.cust_id, Orders.order_num
FROM Customers LEFT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id;
  • 与内联结关联两个表中的行不同的是,外联结必须包括没有关联的行。在使用OUTER JOIN语法的时候,必须使用RIGHT或者LEFT关键字指定包括其所有行的表。上面的例子使用LEFT OUTER JOIN从FROM子句左边的表(CUSTOMERS)中选择所有行。
  • 另外,还存在一种外联结,就是全外联结(FULL OUTER JOIN),它检索两个表中的所欲行并关联那些可以关联的行,与左外联结或右外联结包含一个表的不关联的行不同,全外联结包含两个表的不关联的行。

13.3 使用带聚集函数的联结

  • 例如,要检索所有顾客以及每个顾客所下的订单数,下面的代码使用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;
  • 聚集函数也可以方便地与其他联结一起使用。请看下面地例子:
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;
  • 这个例子使用左外联结来包含所有地顾客,甚至包含那些没有任何订单地顾客

14 组合查询

  • 主要讲述如何利用UNION操作符将多条SELECT语句结合成一个结果集。

14.1 组合查询:

  • 多数SQL查询只包含一个或多个表中返回数据的单条SELECT语句。但是,SQL也允许执行多个查询(多条SELECT语句),并将结果作为一个查询结果集返回。这些组合查询通常称为并(union)或复合查询(compound query)
  • 主要有两种情况需要使用组合查询:
    1. 在一个查询中从不同的表返回结构数据
    2. 对一个表执行多个查询,按一个查询返回数据。
  • 组合查询和多个WHERE条件:多数情况下,组合相同表的两个查询所完成的工作与具有多个WHERE子句条件的一个查询所完成的工作相同。换句话说,任何具有多个WHERE子句的SELECT语句都可以作为一个组合查询。

14.2 创建组合查询

  • 利用UNION,可以给出多条SELECT语句,将他们的结果组成一个结果集

14.2.1 使用UNION

  • 例如:假如需要 Illinois、Indiana和 Michigan等美国几个州的所有顾客的报表,还想包括不管位于哪个州的所有的 Fun4All。当然可以利用WHERE子句来完成此工作,不过这次我们使用UNION。
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
  • 其实也就是对结果求并集,不过需要注意的是必须对应相同的列
  • 这里给出使用多条WHERE子句的相同查询:
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI')
OR cust_name = 'Fun4All';
  • 在较为复杂的过滤条件,或者从多个表中检索数据的时候,使用UNION可能会使处理更加简单,

14.2.3 包含或取消重复的行

  • UNION从查询结果集中自动去除了重复的行,这是UNION的默认行为,如果想返回所有的匹配行,可以使用UNION ALL而不是UNION。

14.2.4 对组合查询结果进行排序

  • 在使用UNION组合查询时,只能使用一条ORDER BY子句,它必须谓语最后一条SELECT语句之后。
  • 例如:
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All'
ORDER BY cust_name, cust_contact;
说明:其他类型的 UNION
某些 DBMS还支持另外两种UNION:EXCEPT(有时称为MINUS)可用
来检索只在第一个表中存在而在第二个表中不存在的行;而
INTERSECT可用来检索两个表中都存在的行。实际上,这些UNION很
少使用,因为相同的结果可利用联结得到。
记两个表为集合A,B
UNION就是求A+B
EXCEP就是求A-B
INTERSECT就是求AB

15 插入数据

  • 使用INSERT语句将数据插入表中

插入完整的行

INSERT INTO Customers
VALUES('1000000006','Toy Land','123 Any Street','New York','NY','11111','USA',NULL,NULL);
  • INTO关键字是可选的,提供这个关键字主要是保证SQL代码在DBMS之间可以移植
  • 上面的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('1000000006','Toy Land','123 Any Street','New York','NY','11111','USA',NULL,NULL);
  • 因为给出了列名,所以次序不再产生影响
注意:小心使用 VALUES
不管使用哪种INSERT语法,VALUES的数目都必须正确。如果不提供列
名,则必须给每个表列提供一个值;如果提供列名,则必须给列出的每
个列一个值。否则,就会产生一条错误消息,相应的行不能成功插入。
  • 也可以只插入部分行,但必须要提供表的列名,其他未插入的行数据默认为NULL(也可以提供默认值)
  • 例如:
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');

插入检索出的数据

  • 可以利用INSERT将SELECT语句的结果插入到表中,也就是所谓的INSERT 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;
  • CustNew表的结构与附录 A中描述的Customers表相同。在填充CustNew时,不应该使用已经在 Customers中用过的 cust_id值(如果主键值重复,后续的INSERT操作将会失败)。
  • INSERT-SELECT使用一条INSERT插入多行数据。

从一个表复制到另一个表

  • 有一种数据插入不使用INSERT语句。要将一个表的内容复制到一个全新的表(运行中创建的表),可以使用SELECT…INTO
SELECT *
INTO CustCopy
FROM Customers;
  • CustCopy表中创建(并填充)与Customers表的每一列相同的列。要想只复制部分的列,可以明确给出列名,而不是使用*通配符。

16 更新和删除数据

  • 使用UPDATE和DELETE语句进一步操作表数据

更新数据

  • 更新数据使用UPDATE

    1. 更新表中的特定行
    2. 更新表中的所有行
  • 注意:不要省略WHERE子句,使用UPDATE时一定要细心,因为稍不注意,就会更新表中的所有行。使用这条语句之前,要十分谨慎。
  • 基本的UPDATE语句由三个部分组成,分别是:
    1. 要更新的表
    2. 列名和他们的新值
    3. 确定要更新那些行的过滤条件
  • 例如:客户1000000005有了电子邮箱地址,因此他的记录需要更新,语句如下:
UPDATE Customers
SET cust_email='kim@thetoystore.com'
WHERE cust_id='1000000005'
  • UPDATE语句总是以要更新的表名开始,以WHERE子句结束。
  • 更新多个列,也只使用一条SET命令,每一个"列=值"之间用逗号分割
UPDATE Customers
SET cust_contact = 'Sam Roberts',cust_email = 'sam@toyland.com'
WHERE cust_id = '1000000006';
  • UPDATE语句中可以使用子查询,使得能用SELECT语句检索出的数据更新列数据。
  • 想要删除某个列的值,只需要设置那个值为NULL即可。
UPDATE Customers
SET cust_email = NULL
WHERE cust_id = '1000000005';
  • NULL表示没有值,空字符串’'是一个值。

删除数据

  • 使用DELETE

    1. 从表中删除特定的行
    2. 从表中删除所有的行
  • 和UPDATE一样,一定不能够省略WHERE子句
  • 使用逻辑和UPDATEA一样,
DELETE FROM Customers
WHERE cust_id = '1000000006'
  • DELETE不需要列名或者通配符,删除的是整行而不是删除列。要删除指定的列,需要使用UPDATE更新值为NULL
  • 如果想从表中删除所有行,不需要使用DELETE,直接使用TRUNCATETABLE语句即可,速度还更块。

更新和删除的原则

  1. 除非确实打算更新和删除每一行,否则不要使用不带WHERE子句的UPDATE或DELETE语句。
  2. 保证每个表都有主键,尽可能像WHERE子句那样使用它。
  3. 在UPDATE或DELETE语句使用WHERE子句之前,应该先用SELECT进行测试,保证它过滤的是正确的记录,以防止编写的WHERE子句不正确。
  • 保持着能不用尽量不用的原则,尽可能创建新表,而不是在原始的表头上进行修改。

17 创建和操作表

  • 创建、更改、和删除表的基本知识。

创建表

  1. 使用DBMS交互式创建
  2. 使用CREATE TABLE语句进行创建
  • 使用CREATE TABLE创建表,必须给出下列信息:

    1. 新表的名字,在关键字CREATE TABLE之后给出
    2. 表列的名字和定义,用逗号分隔
    3. 有的DBMS还要求指定表的位置
  • 例如创建使用的Products表:
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
);
  • 从上面的例子可以看出,表名紧跟CREATE TABLE关键字,所有列在圆括号中,各个列之间用逗号分隔,每列的定义以列名开始,后面紧跟列的数据类型,整条语句以圆括号后的分号结束。

使用NULL值

  • 允许NULL值的列也允许在插入行的时候不给出该列的值。不允许NULL的列不接受没有列值的行,换句话说,在插入或者更新行的时候,该列必须要有值。每个表列要么是NULL列,要么是NOT NULL列。
  • 注意:只有不允许NULL值的列可以作为主键

指定默认值

  • 在出啊入行的时候如果不给出值,DBMS将自动采用默认值,默认值在CREATE TABLE语句的列定义中用关键字DEFAULT指定:
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
);
  • 默认值经常用于日期或事件戳列,例如:通过指定引用系统日期的函数或变量,将系统日期用作默认日期。SQL SERVER使用GETDATE()
  • 尽量多使用DEFAULT值而不是NULL列

更新表

  • 如果需要更新表的定义,可以使用ALTER TABLE
  1. 理想情况下,不要再表中包含数据的时候进行更新,应该在表的设计过程中充分考虑未来可能的需求,避免对标的结构做大的改动
  2. 所有的DBMS都允许给现有的表增加列,不过对所增加列的数据类型有所限制
  3. 在ALTER TABLE之后给出要更改的表名(必须存在)
  4. 列出要做出那些更改
  • 增加列
ALTER TABLE Vendors
ADD vend_phone CHAR(20);
  • 删除列:
ALTER TABLE Vendors
DROP COLUMN vend_phone;
  • 使用ALTER TABLE和使用UPDATE和DELETE一样要十分小心

删除表

  • 删除表(整个表所有)使用DROP TABLE语句即可
  • 需要注意的是,删除表没有确认,也不能撤销,所以在删除的时候需要反复确认

重命名表

  • SQL SERVER使用sp_rename存储过程完成表的重命名

18 使用视图

  • 视图是虚拟的表,与包含数据的表不一样,视图只包含使用时动态检索数据的查询。类似于变量
SELECT cust_name,
FROM Customers, Orders, OrderItems
WHERE Customers.cust_id = Orders.cust_id
AND OrderItems.order_num = Orders.order_num
AND prod_id = 'RGAN01';
  • 此查询用来检索订购了某种产品的顾客,想要利用这个数据较为困难。
  • 加入能把整个查询包装成一个名为ProductCustomers的虚拟表,则可以轻松地检索出相同的数据。
  • 这就是视图的作用,类似于变量,可以用来反复迭代,只消耗动态资源。

视图的优点

  1. 重用SQL语句
  2. 简化复杂的SQL操作,在编写查询后,可以方便的重用它而不必知道其基本查询细节
  3. 使用表达的一部分而不是整个表
  4. 保护数据,授予用户访问表的特定部分的权限,而不是整个表的权限
  5. 更改数据格式和表示,视图可以返回与底层表的表示和格式不同的数据。

创建视图

  • 使用CREATE VIEW来创建视图,与CREATE TABLE一样,CREATE VIRE只能用于创建不存在的视图
  • 删除视图可以使用DROP语句,语法为DROP VIEW view_name。覆盖(更新)视图,必须先删除它,然后再重新创建
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,将列出订购了任意产品的顾客。
  • 检索订购了产品RGAN01的顾客,可以:
SELECT cust_name, cust_contact
FROM ProductCustomers
WHERE prod_id = 'RGAN01';
  • 可以看出,视图极大地简化了复杂 SQL语句的使用。利用视图,可一次性编写基础的 SQL,然后根据需要多次使用。
  • 视图对于应用普通的where子句也很有用。例如,定义CustomerEMailList视图,过滤没有电子邮件地址的顾客。
CREATE VIEW CustomerEMailList AS
SELECT cust_id, cust_name, cust_email
FROM Customers
WHERE cust_email IS NOT NULL
  • 现在,可以像使用其他表一样使用视图CustomerEMailList
  • 视图将当前查询的表存储为变量,查询能够进行的一切操作在视图中都可以进行

19 使用存储过程

  • 存储过程更类似于一个功能实现函数,或者模块化。

19.1 存储过程

  • 迄今为止,我们使用的大多数SQL语句都是针对一个或者多个表的单条语句。并非所有操作都这么简单,经常会有一些复杂的操作需要多条语句才能完成。例如:

    1. 为了处理订单,必须核对以保证库存中有相应的物品
    2. 如果物品有库存,需要预定,不再出售给别的人,并且减少物品数据以反应正确的库存量
    3. 库存中没有的物品需要订购,这需要与供应商进行某种交互
    4. 关于那些物品入库(和可以立即发货)和那些物品退订,需要同指相应的顾客
  • 这显然不是一个完整的例子,它甚至超出了本书中所用的样例表的范围,但足以表达我们的意思了,执行这个处理需要针对许多表的多条SQL语句。此外,需要执行的具体SQL语句以及其次序也不是固定的,它们可能回根据物品是否在库存中而变化。
  • 那么,如何编写代码呢?可以单独编写每条SQL语句,并根据结果有条件的执行其他语句。在每次需要这个处理时,都必须做这些工作。
  • 可以创建存储过程。简单来说,存储过程就是为以后使用而保存的一条或者多条SQL语句。可以将其视作皮文件,虽然它们的作用不仅限于批处理。

19.2 为什么要使用存储过程

  1. 通过把处理封装在一个易用的单元中,可以简化复杂的操作
  2. 由于不要求反复建立一系列处理步骤,因而保证了数据的一致性。如果所有开发人员和应用程序都使用同一存储过程,则所使用的代码都是相同的。这一点的延申就是防止错误,需要执行的步骤越多,出错的可能性就越大。
  3. 简化对变动的管理。如果表名,列名或业务逻辑有变化。只需要更改存储过程的代码,使用它的人员甚至不需要知道这些变化
  4. 因为存储过程通常以编译过的形式存储,所以DBMS处理命令所需的工作量少,提高了性能
  5. 存在一些只能用在单个请求的SQL元素和特性,存储过程可以使用它们来编写功能更强更灵活的代码
  • 缺陷:

    1. 可移植的存储过程是不存在的,但可以进行内部修改达到功能可移植
    2. 一般来说,编写存储过程比编写SQL语句更为复杂,需要更高的技能,更丰富的经验。因此,许多数据库管理员将限制存储过程的创建作为安全措施。

19.3 执行存储过程

  • 执行存储过程的SQL语句很简单,即EXECUTE,EXECUTE接受存储过程名和需要传递给它的任何参数。
EXECUTE AddNewProduct( 'JTS01', 'Stuffed Eiffel Tower', 6.49,'Plush stuffed toy with the text La
➥Tour Eiffel in red white and blue' );
  • 这里执行一个名为 AddNewProduct的存储过程,将一个新产品添加到Products表中。AddNewProduct有四个参数,分别是:供应商 ID(Vendors表的主键)、产品名、价格和描述。这 4个参数匹配存储过程中 4个预期变量(定义为存储过程自身的组成部分)。此存储过程将新行添加到Products表,并将传入的属性赋给相应的列。
  • 以下是存储过程完成的工作
    1. 验证传递的数据,保证所有的4各参数都有值;
    2. 生成用作主键的唯一ID
    3. 将新产品插入Products表,在核实的列中存储生成的主键和传递的数据
  • 对于具体的DBMS,可能包括以下的执行选择:
    1. 参数可选,具有不提供参数时的默认值
    2. 不按照次序给出参数,以参数=值的方式给出参数的值
    3. 输出参数,允许存储过程正执行的应用程序中更新所用的参数
    4. 使用SELECT语句检索数据
    5. 返回代码,允许存储过程返回一个值正在执行的应用程序

19.4 创建存储过程

  • 看一个简单的存储过程的例子,它对邮件发送清单中具有邮件地址的顾客进行计数
CREATE PROCEDURE MailingListCount
AS
DECLARE @cnt INT
SELECT @cnt=COUNT(*)
FROM Customers
WHERE NOT cust_email IS NULL;
RETURN @cnt;
  • 此存储过程没有参数,调用程序检索SQL Server的返回代码提供的值。其中DECLARE语句声明了一个名为@cnt的局部变量(SQL Server中所有局部变量名都以@起头),然后在SELECT语句中使用这个变量,让它包含COUNT()函数返回的值,最后用RETURN @cnt语句将计数返回给调用程序
  • 调用SQL Server的例子可以像下面这样:
DECLARE @ReturnValue INT
EXECUTE @ReturnValue=MailingListCount;
SELECT @ReturnValue;
  • 这个例子在Orders表中插入一个新订单。
CREATE PROCEDURE NewOrder @cust_id CHAR(10)
AS
--为order_number定义变量
DECLARE @order_num INT
--得到当前最大的订单号
SELECT @order_num=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
  • 在编写存储过程的时候应该尽量多加注释
  • 当然,也有另外一种写法:
CREATE PROCEDURE NewOrder @cust_id CHAR(10)
AS
-- Insert new order
INSERT INTO Orders(cust_id)
VALUES(@cust_id)
-- Return order number
SELECT order_num = @@IDENTITY
  • 这次由 DBMS生成订单号。大多数 DBMS都支持这种功能;SQL Server中称这些自动增量的列为标识字段(identity field),而其他 DBMS称之为自动编号(auto number)或序列(sequence)。传递给此过程的参数也是一个,即下订单的顾客 ID。订单号和订单日期没有给出,DBMS对日期使用默认值(GETDATE()函数),订单号自动生成。怎样才能得到这个自动生成的 ID?在 SQLServer上可在全局变量@@IDENTITY中得到,它返回到调用程序(这里使用SELECT语句)。

20 管理事务处理

  • 和python中的代码运行错误检查机制类似
  • 事务处理是一种机制,用来管理必须成批执行的 SQL操作,保证数据库不包含不完整的操作结果。利用事务处理,可以保证一组操作不会中途停止,它们要么完全执行,要么完全不执行(除非明确指示)。如果没有错误发生,整组语句提交给(写到)数据库表;如果发生错误,则进行回退(撤销),将数据库恢复到某个已知且安全的状态。
  • SQL server中会明确标识事务处理块的开始和结束,以
  • BEGIN TRANSACTION
  • COMMIT TRANSACTION
  • 在这个例子中,BEGIN TRANSACTION 和COMMIT TRANSACTION语句之间的SQL必须完全执行或者完全不执行
  • SQL中的ROLLBACK命令用来回退(撤销)SQL语句,例如:
DELETE FROM Orders;
ROLLBACK;

21 使用游标(用的较少)

22 高级SQL特性

  • 主要涉及几个高级数据处理特征:约束,索引和触发器

22.1 约束

  • 关系数据库存储分解为多个表的数据,每个表存储相应的数据,利用键来建立从一个表到另一个表里面的引用(由此产生了术语引用完整性)
  • 正确的进行关系数据库的涉及,需要一种方法保证只在表中插入合法数据,例如,如果Orders表存储订单信息,OrderItem表存储订单详细内容,应该保证OrderItems中引用的任何订单ID都存在于Orders表中。类似的,在Orders表中引用的任何顾客必须存在于Customers表中。
-- 约束:
-- 管理如何插入或处理数据库数据的规则
  • 大多数约束是在表内定义的

22.1.1 主键

  • 主键是一种特殊的约束,用来保证一列(或一组列) 中的值是唯一的,而且永不改动。换句话说,表中的一列(或多个列)的值唯一标识表中的每一行。这方便了直接或交互地处理表中地行,没有主键,要安全地UPDATE或DELETE特定行而不影响其他行会非常困难。
  • 表中地任何一列只要满足以下条件,都可以用于主键
    1. 任意两行的主键值都不相同
    2. 每行都具有一个主键值(即列中不允许NULL值)
    3. 包含主键值的列从不修改和更新
    4. 主键值不能重用。如果从表中删除某一行,其主键值不分配给新行。
  • 一种定义主键的方法是创建它
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
);
  • 在此例子中,给表的vend_id列定义添加关键字PRIMARYKEY,使其成为主键
  • 如果需要将某一列提升为主键,可以:
ALTER TABLE Vendors
ADD CONSTRAINT PRIMARY KEY(vend_id)
  • 这里定义相同的列为主键,但使用的是CONSTRAINT语法。此语法也可以用于CREATE TABLE 和ALTER TABLE语句。

21.2 外键

  • 外键是表中的一列,其值必须列在另一个表的主键中。外键是保证引用完整性的极其重要的部分,这里举个例子:
  • Orders表将录入到系统的每个订单作为一行包含在其中,顾客信息存储在Customers表中.Orders表中的订单通过顾客ID与Customers表中的特定行相关联。顾客ID为Customers表的主键,每个顾客都有唯一的ID.订单号为Orders表的主键,每个订单都有唯一的订单号。
  • Orders表中顾客ID列的值不一定是唯一的,如果某个顾客有多个订单,则有多个行具有相同的顾客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 Cistomers(cust_id)
  • 外键有助于防止意外删除
  • 在定义外键之后,DBMS不允许删除在另一个表中具有关联行的行,例如,不能删除关联订单的顾客。删除该顾客的唯一方法是先删除相关的所有外键所在表中的订单,直至外键不存在此ID。

22.1.3 唯一约束:

  • 唯一约束保证一列中的数据是唯一的,类似于主键
  • 表可以包含多个唯一与约束,但每一个表只允许一个主键
  • 唯一约束列可以包含NULL值
  • 唯一约束列可以修改或者更新
  • 唯一约束列的值可以重复使用
  • 与主键不一样,唯一约束不能够用来定义外键
  • 如果想要保证一个列是唯一约束的,可以在上面使用UNIQUE约束做到

22.1.4 检查约束

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
);
-- 也可以使用ALTER TABLE语句添加如下约束
ALTER TABLE OrderItems
-- 检查名为gender的列只包含M或F
ADD CONSTRAINT CHECK (gender LIKE '[MF]')

22.2 创建索引

  • 使用CREATE INDEX table_name ON table1(column)

22.3 触发器

  • 触发器是特殊的存储过程,能够在特定的数据库活动发声时自动执行。能够与特定表上的INSERT,UPDATE和DELETE操作相关联。
  • 下面的例子创建一个触发器,对所有的INSERT和UPDATE操作,将Customers表中的cust_state列转换为大写
CREATE TRIGGER customer_state
ON Customers
FOR INSERT, UPDATE
AS
UPDATE Customers
SET cust_state = Upper(cust_state)
WHERE Customers.cust_id = inserted.cust_id;
  • 主要是提供数据的访问权

单号).同时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 Cistomers(cust_id)
  • 外键有助于防止意外删除
  • 在定义外键之后,DBMS不允许删除在另一个表中具有关联行的行,例如,不能删除关联订单的顾客。删除该顾客的唯一方法是先删除相关的所有外键所在表中的订单,直至外键不存在此ID。

22.1.3 唯一约束:

  • 唯一约束保证一列中的数据是唯一的,类似于主键
  • 表可以包含多个唯一与约束,但每一个表只允许一个主键
  • 唯一约束列可以包含NULL值
  • 唯一约束列可以修改或者更新
  • 唯一约束列的值可以重复使用
  • 与主键不一样,唯一约束不能够用来定义外键
  • 如果想要保证一个列是唯一约束的,可以在上面使用UNIQUE约束做到

22.1.4 检查约束

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
);
-- 也可以使用ALTER TABLE语句添加如下约束
ALTER TABLE OrderItems
-- 检查名为gender的列只包含M或F
ADD CONSTRAINT CHECK (gender LIKE '[MF]')

22.2 创建索引

  • 使用CREATE INDEX table_name ON table1(column)

22.3 触发器

  • 触发器是特殊的存储过程,能够在特定的数据库活动发声时自动执行。能够与特定表上的INSERT,UPDATE和DELETE操作相关联。
  • 下面的例子创建一个触发器,对所有的INSERT和UPDATE操作,将Customers表中的cust_state列转换为大写
CREATE TRIGGER customer_state
ON Customers
FOR INSERT, UPDATE
AS
UPDATE Customers
SET cust_state = Upper(cust_state)
WHERE Customers.cust_id = inserted.cust_id;
  • 主要是提供数据的访问权

SQL必知必会个人总结(SQL SERVER)相关推荐

  1. 《MySQL必知必会》所有SQL语句图表集合(可作为查询表使用)---已完结

    本篇文章是对<MySQL必知必会>所有语句知识点的图表集合,适合快速查询遗忘的SQL语句. 本文的脉络结构,首先先给出<MySQL必知必会>的目录和本书中用到的数据表的UML图 ...

  2. asp sql ip地址排序_SQL必知必会读书笔记,30分钟入门SQL!

    点击上方SQL数据库开发,关注获取SQL视频教程 SQL专栏 SQL数据库基础知识汇总 SQL数据库高级知识汇总 来源:https://segmentfault.com/p/1210000011760 ...

  3. GitHub#SQL#:SQL必知必会

    https://github.com/CyC2018/Interview-Notebook 一.基础 二.创建表 三.修改表 四.插入 五.更新 六.删除 七.查询 八.排序 九.过滤 十.通配符 十 ...

  4. sql必知必会的数据初始化

    之前已经配置好mysql的工作环境,但是还缺少可以进行操作的文件,即缺少对应的一个数据库和其中的5个表. 下载相关代码 在网址http://www.forta.com/books/0672325675 ...

  5. mysql日期维表sql文件_《MySQL必知必会》笔记(SQL练习+建表语句)

    站在巨人的肩上 Standing On Shoulders Of Giants 部分转自:https://www.jianshu.com/p/294502893128 https://blog.csd ...

  6. 《MySQL必知必会》SQL文件

    <MySQL必知必会>SQL文件: 表的创建和外键绑定: # 在Mysql中取消外键约束 SET FOREIGN_KEY_CHECKS=0; DROP TABLE customers; D ...

  7. SQL Server必知必会

    SQL Server必知必会 2009-10-27-17:57:57 Structure     Query     Language:SQL 结构化       查询      语言 数据库产品: ...

  8. 【SQL必知必会笔记(3)】SELECT语句的WHERE子句数据过滤操作

    上个笔记主要介绍了利用SELECT语句检索单个/多个/所有列,并利用DISTINCT关键字检索具有唯一性的值.利用LIMIT/OFFSET子句限制结果:以及利用ORDER BY子句排序检索出的数据,主 ...

  9. SQL必知必会(一)SQL基础篇

    SQL基础 1.SQL语言 2.SQL开发规范 3.DB.DBS 和 DBMS 的区别是什么 4.SQL执行顺序 1.oracle中执行顺序 2.MYSQL执行顺序 3.sql关键字执行顺序 5. I ...

  10. sql 数据库前两列值乘_SQL | SQL 必知必会笔记 (一 )

    原文:SQL | SQL 必知必会笔记 (一 ) 作者: PyStaData 基本概念 一些规则 多条 SQL 语句必须以分号分隔. SQL 不区分大小写,但一般习惯关键字用大写,列名和表名使用小写. ...

最新文章

  1. iOS - OC - XML 解析 - NSXMLParser
  2. prometheus监控_使用Prometheus和Grafana监视开放自由
  3. oracle重做日志文件版本不一致问题处理
  4. SAP CRM和SAP Hybris的action profile
  5. I. Space Station(hash记忆化+dp)
  6. html5发布原文,HTML5 第二份草案发布
  7. mysql netcdf_Linux下用Intel编译器编译安装NetCDF-Fortan库(4.2以后版本)
  8. 95-140-122-源码-transform-算子reduce
  9. 《第一行代码》学习笔记11-活动Activity(9)
  10. kali破解压缩包密码
  11. 着色缓存器_新的缓存着色器预处理器(实验性)可加快构建速度
  12. linux下编译opencv的无kuda版本,自定义路径
  13. 【Python_010】利用python对excel进行去重
  14. C盘中哪些文件可以删除?
  15. 微软简体和繁体字体转换
  16. 九宫格构图学习[1]
  17. excel中如何自动添加邮箱后缀
  18. k8s1.18 StorageClass 使用rbd-provisioner提供ceph rbd持久化存储
  19. vue3路由切换时的动画效果
  20. 学java的第九天,面向对象 23.2.15

热门文章

  1. 计算一年中的第多少天
  2. 牛逼!“京东热” 框架 JD-hotkey 开源了...单机 QPS 可达 37 万!!
  3. 做个问答社区要多久?这个开源项目直接拿去用!
  4. 《鬼谷子本经阴符七术》全文翻译
  5. 【Web API系列教程】1.3 — 实战:用ASP.NET Web API和Angular.js创建单页面应用程序(上)
  6. Linux下搭建File Browser文件管理系统
  7. 各网络厂商MIB库获取
  8. 财务金融工具-生成收付款计划
  9. 国嵌C语言总结(1-5)
  10. Tensorflow Allocation Memory: Allocation of 38535168 exceeds 10% of system memory解决办法