MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。
MySQL是一种关系数据库管理系统,关系数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
MySQL是一款市场占有率非常高的数据库管理系统,技术成熟、配置步骤相对简单,而且具有良好的可扩展性。
但是,由于Oracle公司在2009年收购了MySQL的母公司Sun,因此MySQL数据库项目也随之纳入Oracle麾下,逐步演变为保持着开源软件的身份,但又申请了多项商业专利的软件系统。
因此由MySQL项目创始者重新研发了一款名为MariaDB的全新数据库管理系统。该软件当前由开源社区进行维护,是MySQL的分支产品,而且几乎完全兼容MySQL。
[root@localhost~]# yum install mariadb mariadb-server
[root@localhost ~]# systemctl start mariadb
[root@localhost ~]# mysql_secure_installation
Enter current password for root (enter for none): 当前数据库密码为空,直接按回车键
Set root password? [Y/n] y
New password: 输入要为root管理员设置的数据库密码
Re-enter new password: 再次输入密码
Password updated successfully!
Reloading privilege tables..
... Success!
Remove anonymous users? [Y/n] y(删除匿名账户)
Disallow root login remotely? [Y/n] n(禁止root管理员从远程登录)
Remove test database and access to it? [Y/n] y(删除test数据库并取消对它的访问权限)
Reload privilege tables now? [Y/n] y(刷新授权表,让初始化后的设定立即生效)
在确认MariaDB数据库软件程序安装完毕并成功启动后请不要立即使用。为了确保数据库的安全性和正常运转,需要先对数据库程序进行初始化操作。
在防火墙策略中服务名称统一叫作mysql:
[root@localhost ~]# firewall-cmd --permanent --add-service=mysql
[root@localhost ~]# firewall-cmd --reload
[root@linuxprobe ~]# mysql -u root -p
Enter password: 此处输入root管理员在数据库中的密码
在登录MariaDB数据库后执行数据库命令时,都需要在命令后面用分号(;)结尾,这也是与Linux命令最显著的区别。
MariaDB [(none)]> SHOW databases;
root账户中的host项是localhost表示该账号只能进行本地登录,我们需要修改权限,输入命令:
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'root123' WITH GRANT OPTION;
FLUSH PRIVILEGES;
Windows 上安装 MySQL 相对来说会较为简单,点击链接 https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.11-winx64.zip 下载 zip 包。
最新版本可以在 MySQL 下载 中下载中查看。
点击 Download No thanks, just start my download.
下载完后,我们将 zip 包解压到相应的目录,这里我将解压后的文件夹放在 C:\web\mysql-8.0.11 下。
接下来我们需要配置下 MySQL 的配置文件
打开刚刚解压的文件夹 C:\web\mysql-8.0.11 ,在该文件夹下创建 my.ini 配置文件,编辑 my.ini 配置以下基本信息:
[client]
# 设置mysql客户端默认字符集
default-character-set=utf8
[mysqld]
# 设置3306端口
port = 3306
# 设置mysql的安装目录
basedir=C:\\web\\mysql-8.0.11
# 设置 mysql数据库的数据的存放目录,MySQL 8+ 不需要以下配置,系统自己生成即可,否则有可能报错
# datadir=C:\\web\\sqldata
# 允许最大连接数
max_connections=20
# 服务端使用的字符集默认为8比特编码的latin1字符集
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
接下来我们来启动下 MySQL 数据库:
以管理员身份打开 cmd 命令行工具,切换目录:
cd C:\web\mysql-8.0.11\bin
初始化数据库:
mysqld --initialize --console
执行完成后,会输出 root 用户的初始默认密码,如:
...
2018-04-20T02:35:05.464644Z 5 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: APWCY5ws&hjQ
...
APWCY5ws&hjQ 就是初始密码,后续登录需要用到,你也可以在登陆后修改密码。
输入以下安装命令:
mysqld install
启动输入以下命令即可:
net start mysql
注意: 在 5.7 需要初始化 data 目录:
cd C:\web\mysql-8.0.11\bin mysqld --initialize-insecure
初始化后再运行 net start mysql 即可启动 mysql。
当 MySQL 服务已经运行时, 我们可以通过 MySQL 自带的客户端工具登录到 MySQL 数据库中, 首先打开命令提示符, 输入以下格式的命名:
mysql -h 主机名 -u 用户名 -p
参数说明:
如果我们要登录本机的 MySQL 数据库,只需要输入以下命令即可:
mysql -u root -p
按回车确认, 如果安装正确且 MySQL 正在运行, 会得到以下响应:
Enter password:
若密码存在, 输入密码登录, 不存在则直接按回车登录。登录成功后你将会看到 Welcome to the MySQL monitor… 的提示语。
然后命令提示符会一直以 mysq> 加一个闪烁的光标等待命令的输入, 输入 exit 或 quit 退出登录。
sudo apt install mariadb-server
链接、测试
sudo mysql -u root -p
安装数据库实例
sudo mysql_secure_installation
启动&重启关闭
查看状态
sudo systemctl status mysql
启动
sudo systemctl start mysql
重启
sudo systemctl restart mysql
关闭
sudo systemctl stop mysql
配置
登录
sudo mysql -u root -p #输入root 用户密码
创建远程登录用户 ,
格式:create user ‘用户名’@’%’ identified by ‘密码’;
create user 'us'@'%' identified by '123';
授权(如果权限不足)
GRANT ALL PRIVILEGES ON *.* TO 'us'@'%' IDENTIFIED BY '123' WITH GRANT OPTION;
FLUSH PRIVILEGES;
# 不要开放root 远程登录权限
查看监听
netstat -an | grep 3306
#tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN
#如果显示 监听端口 是127.0.0.1:3306 那么其他计算机无法连接
#修改配置
vi /etc/mysql/mariadb.conf.d/50-server.cnf#修改结束记得用重启指令重启mysql服务
Instead of skip-networking the default is now to listen only on
localhost which is more compatible and is not less secure.
# bind-address = 127.0.0.1 #注释掉这一行
# 重启后显示
#tcp6 0 0 :::3306 ::? LISTEN
# 此时就可以远程访问mariadb了
# 远程访问
# 直接登录
mysql -h localhost -u us -p 123 #输入us 用户密码123
# 用客户端工具登录
# sqlyog navicat
# 异常处理
# Access denied for user ‘root’@‘localhost’
$ sudo systemctl stop mariadb # 停止服务
$ sudo mysqld_safe --skip-grant-tables & # 进入安全模式,并设置为后台进程
$ mysql -u root # 登陆mysql
select Host,User,plugin from mysql.user where User='root'; # 查询用户
update mysql.user set plugin='mysql_native_password'; #重置加密模式
update mysql.user set password=PASSWORD("newpassword") where User='root'; #重置密码
flush privileges; #刷新权限信息
exit
$ sudo kill -9 $(pgrep mysql) # 杀掉进程
$ sudo service mariadb start # 重新启动服务
$ mysql -u root -p #登陆mysql, 安装unix_soket
$ install plugin unix_socket soname ‘auth_socket’;
数据库(Database)是按照数据结构来组织、存储和管理数据的仓库,主要分为三大类:关系型数据库、层次式数据库、网络式数据库。
常用数据库多为关系型数据库RDBMS(Relational Database Management System),RDMBS是SQL的基础,同样也是所有现代数据库的基础,比如MS SQL Server, IBM DB2, Oracle, MySQL 以及 Microsoft Access。
RDBMS数据库主要特点:
Oracle - 目前世界上使用最为广泛的数据库管理系统
DB2 - IBM公司开发的,历史悠久且被认为是最早使用SQL的数据库产品,它拥有较为强大的商业智能功能。
SQL Server - 由Microsoft开发和推广的关系型数据库产品,最初适用于中小企业的数据管理
MySQL - MySQL是开放源代码的,GPL(General Public License)的许可下下载并根据个性化的需要对其进行修改。
PostgreSQL - 在BSD许可证下发行的开放源代码的关系数据库产品。
SQL是一种ANSI的标准计算机语言,指结构化查询语言,使我们有能力访问数据库。
常用命令。
查看服务器版本。
select version();
查看所有数据库。
show databases;
切换到指定数据库。
use mysql;
查看数据库下所有表。
show tables;
其常用他命令。
查看当前用户:select user();
当前日期时间select now();
查看数据库状态:\s;
退出数据库:\q ;
create database 库名 charset 字符集;
drop database 库名;
字段名1 类型[(宽度) 约束条件],
字段名2 类型[(宽度) 约束条件],
字段名3 类型[(宽度) 约束条件]
);engine=csv default charset=utf8mb4;
2.在建表的时候都需要注意的点:
整型、浮点型、字符类型、日期类型、枚举和集合。
1. 整数型
类型 | 字节 | 范围(有符号) | 范围(无符号) |
---|---|---|---|
TINYINT(m) | 1 | (-128,127) | (0,255) |
SMALLINT(m) | 2 | (-32768,32767) | (0,65535) |
MEDIUMINT(m) | 3 | (-8388608,8388607) | (0,16777215) |
INT(m) | 4 | (-2147483 648,2147483647) | (0,4294967295) |
BIGINT(m) | 8 | (-9223372036854775808,9223372036854775807) | (0,18446744073709551615) |
类型后面括号里的m
指的是显示宽度,显示宽度与所能存储的数据范围无关,目前已知的是当设置了ZEROFILL
(填充0
)约束条件时,填充 0
的 个数由m
决定,
例如:设置id int(5) ZEROFILL
,属性id
会填充成如下形式
+-----------+-----------+
| id | name |
+-----------+-----------+
| 00001 | Jane |
+-----------+-----------+
| 00002 | Mike |
+-----------+-----------+
类型 | 大小 | 用途 |
---|---|---|
FLOAT | 4字节 | 单精度浮点数值 |
DOUBLE | 8字节 | 双精度浮点数值 |
DECIMAL | 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 | 小数值 |
设一个字段定义为float(6,3)
,如果插入一个数123.45678
,实际数据库里存的是123.457
,但总个数还以实际为准,即6位。整数部分最大是3位,如果插入数12.123456
,存储的是12.1234
,如果插入12.12
,存储的是12.1200
.
浮点型在数据库中存放的是近似值,而定点类型在数据库中存放的是精确值。 为了能够引起大家的重视,请看下面的例子: 从上面的例子中我们看到 在mysql中 浮点数相对于定点数的优点是在长度一定的情况下,浮点数能够表示更大的数据范围;它的缺点是会引起精度问题。在今后关于浮点数和定点数的应用中,大家要记住以下几点: 若定义一个字段为timestamp,这个字段里的时间数据会随其他字段修改的时候自动刷新,所以这个数据类型的字段可以存放这条记录最后被修改的时间。 1. varchar 和 text: 1. 有 4 种 PRIMARY KEY、UNIPUE KEY、NOT NULL、FOREIGN KEY。 MySQL属性约束条件 MySQL通过外键约束来保证表与表之间的数据的完整性和准确性。 外键的使用条件: 外键的好处:可以使得两张表关联,保证数据的一致性和实现一些级联操作;
decimal(m,d) 参数m<65 是总个数,d<30且 dmysql> CREATE TABLE test (c1 float(10,2),c2 decimal(10,2));
Query OK, 0 rows affected (0.29 sec)
mysql> insert into test values(131072.32,131072.32);
Query OK, 1 row affected (0.07 sec)
mysql> select * from test;
+-----------+-----------+
| c1 | c2 |
+-----------+-----------+
| 131072.31 | 131072.32 |
+-----------+-----------+
1 row in set (0.00 sec)
c1
列的值由131072.32
变成了131072.31
,这就是浮点数的不精确性造成的。float
、double
(或real
)是浮点数,decimal
(或numberic
)是定点数。
3. 日期时间
类型
字节
范围
格式
用途
DATE
3
1000-01-01/9999-12-31
YYYY-MM-DD
日期值
TIME
3
‘-838:59:59’/‘838:59:59’
HH:MM:SS
时间值或持续时间
YEAR
1
1901/2155
YYYY
年份值
DATETIME
8
1000-01-01 00:00:00/9999-12-31 23:59:59
YYYY-MM-DD HH:MM:SS
混合日期和时间值
TIMESTAMP
4
1970-01-01 00:00:00/2038 结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038年1月19日 凌晨 03:14:07
YYYYMMDD HHMMSS
混合日期和时间值,时间戳
4. 字符串类型
类型
大小
用途
CHAR
0-255字节
定长字符串
VARCHAR
0-65535 字节
变长字符串
TINYBLOB
0-255字节
不超过 255 个字符的二进制字符串
TINYTEXT
0-255字节
短文本字符串
BLOB
0-65 535字节
二进制形式的长文本数据
TEXT
0-65 535字节
长文本数据
MEDIUMBLOB
0-16 777 215字节
二进制形式的中等长度文本数据
MEDIUMTEXT
0-16 777 215字节
中等长度文本数据
LONGBLOB
0-4 294 967 295字节
二进制形式的极大文本数据
LONGTEXT
0-4 294 967 295字节
极大文本数据
CHAR
和 VARCHAR
类型类似,但它们保存和检索的方式不同。它们的最大长度和是否尾部空格被保留等方面也不同。在存储或检索过程中不进行大小写转换。char(n)
若存入字符数小于n
,则以空格补于其后,查询之时再将空格去掉。所以 char
类型存储的字符串末尾不能有空格,varchar
不限于此。
2.char(n)
固定长度,char(4)
不管是存入几个字符,都将占用 4 个字节,varchar
是存入的实际字符数 +1 个字节(n<=255)或2个字节(n>255),所以varchar(4)
,存入 3 个字符将占用 4 个字节。
3.char
类型的字符串检索速度要比 varchar
类型的快。varchar
可指定 n
,text
不能指定,内部存储 varchar
是存入的实际字符数 +1 个字节(n<=255)或 2 个字节(n>255),text
是实际字符数 +2 个字节。
2.text
类型不能有默认值。
3.varchar
可直接创建索引,text
创建索引要指定前多少个字符。varchar
查询速度快于 text
, 在都创建索引的情况下,text
的索引似乎不起作用。BINARY
和 VARBINARY
类似于 CHAR
和 VARCHAR
,不同的是它们包含二进制字符串而不要非二进制字符串。也就是说,它们包含字节字符串而不是字符字符串。这说明它们没有字符集,并且排序和比较基于列值字节的数值值。BLOB
是一个二进制大对象,可以容纳可变数量的数据。有 4 种 BLOB
类型:TINYBLOB
、BLOB
、MEDIUMBLOB
和 LONGBLOB
。它们区别在于可容纳存储范围不同。TEXT
类型:TINYTEXT
、TEXT
、MEDIUMTEXT
和 LONGTEXT
。对应的这 4 种 BLOB 类型,可存储的最大长度不同,可根据实际情况选择。2.常用的约束条件
MySQL关键字
含义
NULL
数据列可包含NULL值
NOT NULL
数据列不允许包含NULL值
DEFAULT
默认值
PRIMARY KEY
主键
AUTO_INCREMENT
自动递增,适用于整数类型
UNSIGNED
无符号
ZEROFILL
填充零
CHARACTER SET name
指定一个字符集
1.两个表必须是InnoDB表,MyISAM表暂时不支持外键(据说以后的版本有可能支持,但至少目前不支持);
2.外键列必须建立了索引,MySQL 4.1.2以后的版本在建立外键时会自动创建索引,但如果在较早的版本则需要显示建立;
3.外键关系的两个表的列必须是数据类型相似,也就是可以相互转换类型的列,比如int和tinyint可以,而int和char则不可以;[CONSTRAINT <外键名>] FOREIGN KEY 字段名 [,字段名2,…]
REFERENCES <主表名> 主键列1 [,主键列2,…]
-- 如果存在名为school的数据库就删除它
drop database if exists school;
-- 创建名为school的数据库并设置默认的字符集和排序方式
create database school default charset utf8 collate utf8_bin;
-- 切换到school数据库上下文环境
use school;
-- 创建学院表
create table tb_college
(
collid int auto_increment comment '编号',
collname varchar(50) not null comment '名称',
collmaster varchar(20) not null comment '院长',
primary key (collid)
);
-- 创建学生表
create table tb_student
(
stuid int not null comment '学号',
stuname varchar(20) not null comment '姓名',
stusex boolean default 1 comment '性别',
stubirth date not null comment '出生日期',
stuaddr varchar(255) default '' comment '籍贯',
collid int not null comment '所属学院',
primary key (stuid),
foreign key (collid) references tb_college (collid)
);
-- 创建教师表
create table tb_teacher
(
teaid int not null comment '工号',
teaname varchar(20) not null comment '姓名',
teatitle varchar(10) default '助教' comment '职称',
collid int not null comment '所属学院',
primary key (teaid),
foreign key (collid) references tb_college (collid)
);
-- 创建课程表
create table tb_course
(
couid int not null comment '编号',
couname varchar(50) not null comment '名称',
coucredit int not null comment '学分',
teaid int not null comment '授课老师',
primary key (couid),
--课程表 多对一
foreign key (teaid) references tb_teacher (teaid)
);
-- 创建选课记录表
create table tb_record
(
recid int auto_increment comment '选课记录编号',
sid int not null comment '选课学生',
cid int not null comment '所选课程',
seldate datetime default now() comment '选课时间日期',
score decimal(4,1) comment '考试成绩',
primary key (recid),
foreign key (sid) references tb_student (stuid),
foreign key (cid) references tb_course (couid),
unique (sid, cid)
);
DML操作是指对数据库中表记录的操作,主要包括表记录的插入(insert)、更新(update)、删除(delete),是开发人员日常频繁使用的操作。
INSERT用来将一行或多行插入到数据库表。插入有几种方式:
(1)插入完整的行
基本的INSERT语法要求指定表名和插入到新行中的值,例如:
INSERT INTO Customers
VALUES('1000000006',
'Toy Land',
'123 Any Street',
'New York',
'NY',
'11111',
'USA',
NULL,
NULL);
这个例子将一个新顾客插入到Customers表中。存储到表中每一列的数据在VALUES子句中给出。如果某列没有值,如上面的cust_contact和cust_email列,则应该使用NULL值(如果表允许对该列指定空值)。
虽然这种语法很简单,但高度依赖于表中列的定义次序,并不安全,应该尽量避免使用。更安全的写法应在表名后的括号里明确给出列名。如下所示:
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);
(2)插入部分行
如果已经在表名后的括号里明确给出了列名,则可以只插入部分行:
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);
但需要注意省略的列必须满足以下条件:该列定义为允许NULL值或已给出默认值。否则MySQL将产生错误消息,相应的行不能成功插入。
(3)插入检索出的数据
INSERT一般用来给表插入具有指定列值的行。但可以利用它将SELECT语句的结果插入表中,这就是所谓的INSERT SELECT。顾名思义,它是由一条INSERT语句和一条SELECT语句组成的。假如想把另一表中的顾客列合并到Customers表中。不需要每次读取一行再将它用INSERT插入,可以如下进行::
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中列出的每一列对应于Customers表名后所跟的每一列。为简单起见,这个例子在 INSERT 和SELECT 语句中使用了相同的列名。但其实MySQL使用的是列的位置,SELECT 中的第一列将用来填充表列中指定的第一个列,第二列对应第二列,以此类推。这对于从使用不同列名的表中导入数据是非常有用的。
(1)更新数据
为了更新表中的数据,可使用 UPDATE 语句。基本的UPDATE语句由三部分组成:
例如,客户1000000005现在有了电子邮件地址,因此他的记录需要更新,语句如下:
UPDATE Customers
SET cust_email = '[email protected]'
WHERE cust_id = '1000000005';
UPDATE语句总是以要更新的表名开始,SET命令用来将新值赋给被更新的列,最后以WHERE子句结束。没有WHERE子句,DBMS将会用这个电子邮件地址更新Customers表中的所有行,这不是我们希望的。
要更新多个列时,只需要使用一条SET命令,每个“列=值”对之间用逗号分隔:
UPDATE Customers
SET cust_contact = 'Sam Roberts',
cust_email = '[email protected]'
WHERE cust_id = '1000000006';
要删除某个列的值时,可设置它为NULL(假如表定义允许NULL值):
UPDATE Customers
SET cust_email = NULL
WHERE cust_id = '1000000005';
注:更新数据时一定要细心,不要省略WHERE子句,否则稍不注意,就会更新表中的所有行。
(2)删除数据
使用DELETE语句可从一个表中删除数据,包括从表中删除特定的行甚至所有行。例如,从Customers表中删除一位顾客:
DELETE FROM Customers
WHERE cust_id = '1000000006';
DELETE FROM 指定从中删除数据的表名。WHERE 子句过滤要删除的行。如果省略 WHERE 子句,它将删除表中每个客户。
DELETE 删除整行而不是删除列。如需删除指定的列,可使用上述 UPDATE 语句。
注:删除数据时同样需要小心,因为如果执行 DELETE 语句而不带 WHERE 子句,表的所有数据都将被删除且不可恢复。此外,在对 UPDATE 或 DELETE 语句使用 WHERE 子句前,应该先用 SELECT 进行测试,以保证它过滤的是正确的记录。
-- 插入学院数据
insert into tb_college (collname, collmaster) values
('计算机学院', '左冷禅'),
('外国语学院', '岳不群'),
('经济管理学院', '风清扬');
-- 插入学生数据
insert into tb_student (stuid, stuname, stusex, stubirth, stuaddr, collid) values
(1001, '杨逍', 1, '1990-3-4', '四川成都', 1),
(1002, '任我行', 1, '1992-2-2', '湖南长沙', 1),
(1033, '王语嫣', 0, '1989-12-3', '四川成都', 1),
(1572, '岳不群', 1, '1993-7-19', '陕西咸阳', 1),
(1378, '纪嫣然', 0, '1995-8-12', '四川绵阳', 1),
(1954, '林平之', 1, '1994-9-20', '福建莆田', 1),
(2035, '东方不败', 1, '1988-6-30', null, 2),
(3011, '林震南', 1, '1985-12-12', '福建莆田', 3),
(3755, '项少龙', 1, '1993-1-25', null, 3),
(3923, '杨不悔', 0, '1985-4-17', '四川成都', 3),
(4040, '隔壁老王', 1, '1989-1-1', '四川成都', 2);
-- 删除学生数据
delete from tb_student where stuid=4040;
-- 更新学生数据
update tb_student set stuname='杨过', stuaddr='湖南长沙' where stuid=1001;
-- 插入老师数据
insert into tb_teacher (teaid, teaname, teatitle, collid) values
(1122, '张三丰', '教授', 1),
(1133, '宋远桥', '副教授', 1),
(1144, '杨逍', '副教授', 1),
(2255, '范遥', '副教授', 2),
(3366, '韦一笑', '讲师', 3);
-- 插入课程数据
insert into tb_course (couid, couname, coucredit, teaid) values
(1111, 'Python程序设计', 3, 1122),
(2222, 'Web前端开发', 2, 1122),
(3333, '操作系统', 4, 1122),
(4444, '计算机网络', 2, 1133),
(5555, '编译原理', 4, 1144),
(6666, '算法和数据结构', 3, 1144),
(7777, '经贸法语', 3, 2255),
(8888, '成本会计', 2, 3366),
(9999, '审计学', 3, 3366);
-- 插入选课数据
insert into tb_record (sid, cid, seldate, score) values
(1001, 1111, '2017-09-01', 95),
(1001, 2222, '2017-09-01', 87.5),
(1001, 3333, '2017-09-01', 100),
(1001, 4444, '2018-09-03', null),
(1001, 6666, '2017-09-02', 100),
(1002, 1111, '2017-09-03', 65),
(1002, 5555, '2017-09-01', 42),
(1033, 1111, '2017-09-03', 92.5),
(1033, 4444, '2017-09-01', 78),
(1033, 5555, '2017-09-01', 82.5),
(1572, 1111, '2017-09-02', 78),
(1378, 1111, '2017-09-05', 82),
(1378, 7777, '2017-09-02', 65.5),
(2035, 7777, '2018-09-03', 88),
(2035, 9999, default, null),
(3755, 1111, default, null),
(3755, 8888, default, null),
(3755, 9999, '2017-09-01', 92);
DQL是从数据库中查看数据.
在数据分析过程中,SELECT语句是最经常使用的SQL语句。它的用途是从一个或多个表中检索数据。为此,至少需要给出两条信息——想选择什么,以及从什么地方选择。
(1)检索单个列
最基础的SELECT 语句如下所示:
SELECT prod_name
FROM Products;
从 Products 表中检索一个名为prod_name 的列
在上述语句中,所需的列名在 SELECT 关键字之后给出, FROM关键字指出从其中检索数据的表名。返回的数据没有过滤,也没有经过排序。输出如下所示:
(2)检索多个列
从一个表中检索多个列,只需在 SELECT 关键字后给出多个列名,列名之间以逗号分隔:
SELECT prod_id, prod_name, prod_price
FROM Products;
从 Products 表中检索prod_id, prod_name, prod_price三个列
(3)检索所有列
使用星号(*)通配符可以检索所有列而不必逐个列出所有的列名:
SELECT *
FROM Products;
返回 Products 表中所有列
注:使用通配符的好处是比较方便,且能检索出名字未知的列。但通常会降低检索和应用程序的性能,除非确实需要表中的每个列,否则最好别使用*通配符。
(4)检索不同的行
如前所述,SELECT语句会返回所有匹配的行。如果不希望相同的记录重复出现,可使用 DISTINCT 关键字:
SELECT DISTINCT vend_id
FROM Products;
返回 Products 表中不同(唯一)的vend_id 行
该语句只返回三行不重复值的vend_id。如下所示:
与之相对的,如果不指定DISTINCT关键字,只输入SELECT vend_id FROM Products;则会返回9行(分别对应Products 表中9种产品):
注:不能部分使用 DISTINCT关键字。DISTINCT作用于所有的列,不仅仅是跟在其后的那一列。例如,指定SELECT DISTINCT vend_id, prod_price,除非指定的两列完全相同,否则所有的行都会被检索出来。
(5)限制结果
若只需要返回第一行或前几行,可使用 LIMIT 子句:
SELECT prod_name
FROM Products
LIMIT 5;
只返回 Products 表中前5行记录
除了使用LIMIT关键字指定返回的行数之外,还可以使用OFFSET 关键字指定从哪一行开始检索:
SELECT prod_name
FROM Products
LIMIT 5 OFFSET 5;
返回 Products 表中从第5行起的5行数据
可以发现,在这个例子中只返回了4行,这是因为在Products表中只有9种产品,而第一个被检索的产品称为第0行,最后一个被检索的产品是第8行,所以最终只返回了4行数据。
注:MySQL还支持简化版的“LIMIT 4O FFSET 3”,即“LIMIT 3,4”,表示从行3开始取4行。
这个部分会简单介绍如何使用 SELECT 语句的 ORDER BY 子句对检索出来的数据在一个或多个列上进行排序。
(1)按单个列排序
使用ORDER BY子句进行排序方法如下:
SELECT prod_name
FROM Products
ORDER BY prod_name;
检索 Products 表的prod_name列,并按prod_name列字母顺序排序
与之对比,若不指定顺序,输入“SELECT prod_name FROM Products”语句则输出结果为:
如果不排序,数据一般以它在底层表中出现的顺序显示,这有可能是数据最初添加到表中的顺序。但如果数据进行过更新或删除,则这个顺序将会受到DBMS重用回收存储空间的方式的影响。因此,关系数据库设计理论认为,如果不明确规定排序顺序,则不应该假定检索出的数据的顺序有任何意义。
(2)按多个列排序
要按多个列排序,只需指定多个列名,列名之间用逗号分开即可:
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price, prod_name;
检索Products 表中的3个列,先按价格排序,然后按名称排序
注: ORDER BY子句允许选用非检索的列作为排序依据。
(3)按列位置排序
ORDER BY支持按相对列位置进行排序:
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY 2, 3;
先按SELECT清单中第二个列prod_price(价格)进行排序,然后按第三个列prod_name(名称)排序
按列相对位置排序好处在于不用重新输入列名,但需要特别注意每个列名的确切位置,以免在对SELECT清单进行更改时错用列名排序。
(4)指定排序方向
排序时,默认为升序排序(从A到Z),但可以指定DESC关键字降序排序(从Z到A):
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price DESC, prod_name;
对prod_price列降序排序,对prod_name列仍按升序排序。
从上述例子中可以看出,DESC关键字只作用于直接位于其前面的列名。如果想在多个列上进行降序排序,必须对每一列指定DESC关键字。
使用 ORDER BY 和 LIMIT 的组合,能够找出一个列中最高或最低的值。下面的例子演示如何找出最昂贵物品的值:
SELECT prod_price
FROM Products
ORDER BY prod_price DESC
LIMIT 1;
注:需要注意ORDER BY子句的位置。在给出 ORDER BY 子句时,应该保证它位于 FROM 子句之后。如果使用 LIMIT关键字 ,则它须位于 ORDER BY之后。一般来说,ORDER BY 子句必须是SELECT 语句中的最后一条子句。
本节会介绍如何使用SELECT语句的WHERE子句指定搜索条件过滤返回的数据。
(1)使用WHERE子句
在 SELECT 语句中,WHERE子句在FROM子句之后给出,返回满足指定搜索条件的数据:
SELECT prod_name, prod_price
FROM Products
WHERE prod_price = 3.49;
从products表中检索两个列,但只返回prod_price值为3.49的行
除了上述例子的相等条件判断,WHERE子句还支持以下条件操作符:
例如,使用<>操作符(或!=)进行不匹配检查:
SELECT vend_id, prod_name
FROM Products
WHERE vend_id <> 'DLL01';
从Products表中列出所有不是供应商DLL01制造的产品
再如,使用BETWEEN操作符可匹配处于某个范围内的值。需要指定范围的端点值:
SELECT prod_name, prod_price
FROM Products
WHERE prod_price BETWEEN 5.99 AND 9.49;
检索价格在5.99美元和9.49美元之间的所有产品(包括5.99美元和9.49美元)
此外,还有一个特殊的IS NULL子句,可用来检查具有 NULL 值的列:
SELECT cust_name
FROM CUSTOMERS
WHERE cust_email IS NULL;
返回没有邮件地址为NULL的顾客。
(2)组合WHERE子句
SQL还允许使用AND或OR操作符组合出多个WHERE子句,例如使用AND操作符:
SELECT prod_id, prod_price, prod_name
FROM Products
WHERE vend_id = 'DLL01' AND prod_price <= 4;
检索由供应商DLL01制造且价格小于等于4美元的所有产品的名称和价格(需同时满足两个条件)
同理,可使用OR操作符检索匹配任一条件的行:
SELECT prod_name, prod_price
FROM Products
WHERE vend_id = 'DLL01' OR vend_id = 'BRS01';
检索由供应商DLL01或供应商BRS01制造的所有产品的名称和价格(只需满足任一条件即可)
(3)求值顺序
WHERE子句允许包含任意数目的AND和OR操作符以进行复杂、高级的过滤。但需要注意求值顺序:AND操作符优先级高于OR操作符,但可以使用圆括号明确地指定求值顺序:
SELECT prod_name, prod_price
FROM Products
WHERE (vend_id = 'DLL01' OR vend_id = ‘BRS01’)
AND prod_price <= 10;
选择由供应商DLL01或BRS01制造的,且价格在10美元及以下的所有产品
(4)IN操作符
IN操作符用来指定条件范围,范围中的每个条件都可以进行匹配。条件之间用逗号分隔:
SELECT prod_name, prod_price
FROM Products
WHERE vend_id IN ( 'DLL01', 'BRS01' )
ORDER BY prod_name;
检索由供应商DLL01和BRS01制造的所有产品
注:IN操作符完成了与OR相同的功能,但与OR相比,IN操作符有如下优点:
(4)NOT操作符
NOT操作符用来否定跟在它之后的条件:
SELECT vend_id,prod_name
FROM Products
WHERE NOT vend_id = 'DLL01'
ORDER BY prod_name;
用来检索除了供应商DLL01之外制造的所有产品
上面的例子也可以使用<>操作符来完成。但在更复杂的子句中,NOT是非常有用的。例如,在与IN操作符联合使用时,NOT可以非常简单地找出与条件列表不匹配的行:
SELECT vend_id,prod_name
FROM Products
WHERE vend_id NOT IN ( 'DLL01', 'BRS01' )
ORDER BY prod_name;
用来检索除了供应商DLL01和BRS01之外制造的所有产品
注:和多数其他 DBMS允许使用 NOT 对各种条件取反不同,MySQL支持使用 NOT 对 IN 、 BETWEEN 和EXISTS子句取反。
(5)LIKE操作符
使用LIKE操作符和通配符可以进行模糊搜索,以便对数据进行复杂过滤。最常使用的通配符是百分号( % ),它可以表示任何字符出现任意次数:
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE '%bean bag%';
检索产品名含有[bean bag]字段的所有产品
另一个有用的通配符是下划线(_)。它的用途与%一样,但只匹配单个字符,而不是多个或0个字符:
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE '_ inch teddy bear';
检索产品名含有[ inch teddy bear]字段的产品,且[ inch teddy bear]字段前有且只能有一个字符
注:通配符搜索只能用于文本字段(串),非文本数据类型字段不能使用通配符搜索。
这部分将会介绍什么是计算字段以及MySQL支持的部分数据处理函数。
(1)计算字段
有时候从数据库中直接检索出来的数据并不是我们想要的,我们希望得到的是经过转换、计算或格式化过的数据。例如,在物品订单表中存储的是物品的价格和数量,但我们需要的是每个物品的总价格(用价格乘以数量即可)。这时候计算字段就可以排上用场了。计算字段并不实际存在于数据库表中,而是运行时在SELECT语句内创建的。
SELECT prod_id,
quantity,
item_price,
quantity*item_price AS expanded_price
FROM OrderItems
WHERE order_num = 20008;
求得订单号20008中每个物品的数量、单价以及总价格(单价乘数量)
在该例中,quantity*item_price 表示对检索出的数据进行算术计算, AS expanded_price指示SQL创建了一个包含指定计算结果的名为expanded_price的计算字段。其中expanded_price叫做别名,用AS关键字赋予,客户端应用可以像使用其他列一样引用这个新计算列。
SQL支持+、-、*、/(加减乘除)四种基本算术操作符。此外,圆括号可用来区分优先顺序。
除了算术运算,我们还经常使用拼接字段。例如,vendors 表的两个列 vend_name 和 vend_country分别存储了供应商的名字和位置信息,假如要生成一个供应商报表,需要按照 name(location) 这样的格式列出供应商的信息,即把vend_name和 vend_country两个列拼接起来。在MySQL中,可使用Concat() 函数来拼接两个列:
SELECT Concat(vend_name,’(’,vend_country,’)’)
AS vend_title
FROM Vendors
ORDER BY vend_name;
将vend_name和 vend_country两个列拼接成 name(location) 的格式,并叫做别名vend_title
(2)数据处理函数
与大多数其他计算机语言一样,SQL支持利用用函数来处理数据。上述用来拼接两个列的Concat()就是函数的一个例子 。大多数SQL实现支持4种类型的函数:文本处理函数、日期和时间处理函数、数值处理函数以及返回DBMS正使用的特殊信息(如返回用户登录信息,检查版本细节)的系统函数。一下简要介绍前三种数据处理函数。
SELECT vend_name, UPPER(vend_name) AS vend_name_upcase
FROM Vendors
ORDER BY vend_name;
将vend_name列转换为大写
下表列出了一些常用的文本处理函数。
上表中的SOUNDEX 需要做进一步的解释。 SOUNDEX 是一个将任何文本串转换为描述其语音表示的字母数字模式的算法。 SOUNDEX 考虑了类似的发音字符和音节,使得能对串进行发音比较而不是字母比较。
例如,Customers表中有一个顾客Kids Place,其联系名为Michelle Green。但如果这是 错误的输入,此联系名实际上应该是Michael Green,如果使用SOUNDEX()函数进行搜索,它就有匹配所有发音类似于Michael Green的联系名:
SELECT cust_name, cust_contact
FROM Customers
WHERE SOUNDEX(cust_contact) = SOUNDEX('Michael Green');
匹配所有发音类似于Michael Green的联系名
下表列出了一些常用的日期和时间处理函数。
需要注意MySQL使用的日期格式必须为yyyy-mm-dd,例如2017年1月1日,应写成2017-01-01。一般如果要对日期进行比较,需使用Date()函数:
SELECT cust_id, order_num,order_date
FROM Orders
WHERE Date(order_date) BETWEEN '2012-01-01' AND '2012-01-31';
匹配2012年1月份下的所有订单
还有另外一种办法是使用Year()和Month()函数:
SELECT cust_id, order_num,order_date
FROM Orders
WHERE Year(order_date) = 2012 AND Month(order_date) = 1;
匹配2012年1月份下的所有订单
(3)聚集函数
有时我们实际需要的是表中数据的汇总信息,而不是实际数据本身,比如确定表中行数、表列的最大值、最小值和平均值等。为此,MySQL给出了5个聚集函数:
SELECT AVG(prod_price) AS avg_price
FROM Products;
返回Products表中所有产品的平均价格
SELECT COUNT(*) AS num_cust
FROM Customers;
返回Customers表中顾客的总数
另一种方式是使用COUNT(column)对特定列中具有值的行进行计数,忽略NULL值:
SELECT COUNT(cust_email) AS num_cust
FROM Customers;
返回Customers表中具有电子邮件地址的顾客的总数
在此例子中,使用COUNT(cust_email)对cust_email列中有值的行进行计数,输出结果为3表示5个顾客中只有3个顾客有电子邮件地址,忽略NULL值。
SELECT MAX(prod_price) AS max_price
FROM Products;
返回Products表中最贵物品的价格
注:MAX()一般用来找出最大的数值或日期值,在用于文本数据时,如果数据按相应的列排序,则 MAX() 返回最后一行。
SELECT SUM(item_price*quantity) AS total_price
FROM OrderItems
WHERE order_num = 20005;
返回订单中所有物品价钱之和
在使用以上聚集函数时,如果只想聚集不同值,可指定 DISTINCT 参数去重:
SELECT AVG(DISTINCT prod_price) AS price_avg,
MAX(DISTINCT prod_price) AS price_max
FROM Products
WHERE vend_id = 'DLL01';
返回产品的平均价格及最高价格,但只考虑各个不同的价格
从本例中可以看出,使用了DISTINCT后,排除了多个具有相同的较低价格的物品之后,price_avg相对比较高。但是将DISTINCT用于MIN()和MAX()并不会改变一个列中的最小值和最大值。此外,DISTINCT不能用于COUNT(*)。
注:与DISTINCT参数相对的是ALL参数,但ALL参数不需要指定,因为这是默认的。
本小节将会介绍如何使用GROUP BY 子句和 HAVING 子句分组数据。
(1)使用GROUP BY 子句创建分组
GROUP BY 子句能把数据分为多个逻辑组,并使用聚集函数对每个组进行聚集计算,以解决诸如返回每个供应商提供的产品数目、返回只提供单项产品的供应商所提供的产品或返回提供10个以上产品的供应商等问题。考察以下例子:
SELECT vend_id, COUNT(*) AS num_prods
FROM Products
GROUP BY vend_id;
vend_id为产品供应商的ID,num_prods为计算字段(用COUNT(*)函数建立),GROUP BY 子句将根据vend_id进行数据分组
从输出中可以看到,供应商BRS01有3个产品,供应商DLL01有4个,供应商FNG01有2个产品。使用GROUP BY 子句时,应注意以下问题:
(2)使用HAVING 子句过滤分组
除了能用GROUP BY分组数据外,SQL还允许过滤分组,规定包括哪些分组,排除哪些分组。例如,在上例中,我们已使用GROUP BY子句根据供应商进行数据分组,并返回每个供应商有多少个产品。现如果只想返回有三个产品以上的供应商:
SELECT vend_id, COUNT(*) AS num_prods
FROM Products
GROUP BY vend_id
HAVING COUNT(*) >= 3;
GROUP BY子句根据供应商进行数据分组,HAVING 子句过滤并返回三个产品以上的供应商
从以上例子可以看出,HAVING子句和WHERE子句功能相似,有关WHERE的所有用法(包括通配符条件和带多个操作符的子句)都适用于HAVING。唯一的差别是,WHERE过滤行,而HAVING过滤分组。
当然,我们也可以在一条语句中同时使用WHERE和HAVING子句:
SELECT vend_id, COUNT(*) AS num_prods
FROM Products
WHERE prod_price >= 4
GROUP BY vend_id
HAVING COUNT(*) >= 3;
和上述例子相比,多增加了一条WHERE子句,过滤所有prod_price至少为4的行
加上WHERE子句,输出结果少了一行,因为供应商DLL01销售4个产品,但价格都在4以下。
至此,回顾一下SELECT语句中子句的顺序:
本小节将会介绍什么是子查询,如何使用它们。
(1)利用子查询进行过滤
首先考察以下问题:本教程中使用的数据库表都是关系表,其中关于订单的信息存储在两个表中:Orders表存储订单编号、客户ID、订单日期;OrderItems表存储各订单的具体包含物品;顾客的实际信息存储在Customers表中。
现在,假如需要列出订购物品RGAN01的所有顾客,应该怎样检索?下面列出了具体步骤:
上述每个步骤都可以单独作为一个查询来执行。可以把一条SELECT语句返回的结果用于另一条SELECT语句的WHERE子句。第一条SELECT语句较为简单:
SELECT order_num
FROM OrderItems
WHERE prod_id = 'RGAN01';
对prod_id为RGAN01的所有订单物品,检索其order_num列
现在我们知道了订单编号,只需查询与订单20007和20008相关的顾客ID:
SELECT cust_id
FROM Orders
WHERE order_num IN (20007,20008);
检索与订单20007和20008相关的顾客ID
现在,结合这两个查询,把第一个查询变为子查询:
SELECT cust_id
FROM Orders
WHERE order_num IN (SELECT order_num
FROM OrderItems
WHERE prod_id = 'RGAN01');
检索订单中包含物品RGAN01的顾客ID
现在得到了订购物品RGAN01的所有顾客的ID,第三步是检索这些顾客ID的顾客信息:
SELECT cust_name, cust_contact
FROM Customers
WHERE cust_id IN ('1000000004','1000000005');
检索这些顾客ID为1000000004和1000000005的顾客信息
仿照上例,把其中的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'));
检索订购物品RGAN01的所有顾客
为了执行上述SELECT语句,MySQ实际上必须执行三条SELECT语句。最里边的子查询返回订单号列表,此列表用于其外面的子查询的WHERE子句。外面的子查询返回顾客ID列表,此顾客ID列表用于最外层查询的WHERE子句。最外层查询返回所需的数据。
可见,在WHERE子句中使用子查询能够编写出功能很强且很灵活的SQL语句。对于能嵌套的子查询的数目没有限制,不过在实际使用时由于性能的限制,不能嵌套太多的子查询。并且使用子查询并不总是执行这种类型的数据检索的最有效的方法,后续学习连接(JOIN)时我们还会遇到这个例子。
(2)作为计算字段使用子查询
使用子查询的另一方法是创建计算字段。假如需要显示Customers表中每个顾客的订单总数,其中订单与相应的顾客ID存储在Orders表中。执行这个操作,要遵循下面的步骤:
SELECT cust_name,
cust_state,
(SELECT COUNT(*)
FROM Orders
WHERE Orders.cust_id = Customers.cust_id) AS orders
FROM Customers
ORDER BY cust_name;
SELECT COUNT(*)对表中的行进行计数,并且通过提供一条WHERE子句来过滤某个特定的顾客ID,仅对该顾客的订单进行计数。最外层外层查询对每个顾客执行COUNT(*)。
注意到子查询中的WHERE子句与前面使用的WHERE子句稍有不同,因为它使用了完全限定列名(Orders.cust_id和Customers.cust_id),将Orders表中的cust_id和当前正从Customers表中检索的cust_id进行比较。它的语法是用一个句点分隔表名和列名。当有可能混淆列名时,就必须使用完全限定列名来避免歧义。
我们知道,作为关系数据库管理系统(RDBMS)一部分的关系数据库,是用相互之间存在关系的表来组织数据的。下图展示了本教程所使用的五张表之间的关系:
我们曾在“导入样例表”小节中对这五张表的内容及其关系的进行了详细地描述。虽然可以建一张更大的表来存储所有的订单和客户细节,但是使用五张表有其优点:
简而言之,关系表的设计就是要把信息分解成多个表,各表通过某些共同的值互相关联,从而更有效地存储以及更方便地处理。但在实际当中,经常有必要一次性跨多个表来访问相关数据。为了完成这一任务,SQL查询语句使用JOIN语句来指定多个表之间的关系。
(1)内连接
连接(JOIN)使数据库用户能够从2个或多个表中选择适当的列。下面的SQL查询给出了一个最常见类型的连接示例:内连接(inner join).
SELECT vend_name, prod_name, prod_price
FROM Vendors AS V
INNER JOIN Products AS P
ON V.vend_id = P.vend_id;
从Vendors和Products两张表中返回各个供应商所提供的产品和价格
上面语句中,INNER JOIN将Vendors和Products两表关联,关联需要一个或多个字段作为连接桥梁。例子中的桥梁就是vend_id,我们使用on语句,将Vendors表的vend_id字段和Products的id字段匹配。
这里需要注意的是,因为字段可能重名,所以需要使用完全限定名以避免歧义。此外,表Vendors和Products分别使用AS关键字定义了别名V和P。用别名来代替完整的表明以提高查询的可读性。
在“子查询”该小节中,我们曾用以下子查询语句来检索订购物品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 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';
检索订购物品RGAN01的所有顾客
这个查询中的返回数据需要使用3个表。但在这里,我们没有在嵌套子查询中使用它们,而是使用了两个WHERE子句条件来连接表。第三个WHERE子句条件过滤产品RGAN01的数据。
(2)外连接
如上所述,在所有连接类型中,Inner Join(内连接)最常见,可以缩写成Join,找的是两张表共同拥有的字段。但假设我们想对每个顾客下的订单进行计数,包括那些至今尚未下订单的顾客,这就需要包含那些在相关表中没有关联行的行。这种连接称为外连接(OUTER JOIN)。
外连接语法与内连接类似,但必须使用RIGHT或LEFT关键字指定包括其所有行的表(LEFT指出的是OUTER JOIN左边的表,而RIGHT指出的是OUTER JOIN右边的表)。下面的例子使用LEFT OUTER JOIN从FROM子句左边的表(Customers表)中选择所有行:
SELECT Customers.cust_id, Orders.order_num
FROM Customers LEFT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id;
检索包括没有订单顾客在内的所有顾客
输出结果中,cust_id为1000000002那一行的Order_num为空,就是因为cust_id无法匹配上,返回了Null。如果改成Inner Join,则不会返回整个cust_id为1000000002所在行。这是Inner Join和Left Join的区别。
另外一种基本的外连接形式是RIGHT OUTER JOIN。它们之间的唯一差别是所关联的表的顺序。换句话说,调整FROM或WHERE子句中表的顺序,左外联结可以转换为右外联结。如A LEFT JOIN B 等价于 B RIGHT JOIN A因此,这两种外联结可以互换使用,哪个方便就用哪个。
多数SQL查询只包含从一个或多个表中返回数据的单条SELECT语句。但是,SQL也允许利用UNION操作符来组合多条SELECT语句,并将结果作为一个查询结果集返回。这些组合查询通常称为并(union)或复合查询(compound query)。
主要有两种情况需要使用组合查询:
使用UNION很简单,只需在各条SELECT语句之间放上关键字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';
检索Illinois、Indiana和Michigan等美国几个州的所有顾客的报表,以及包括不管位于哪个州的所有的Fun4All。
第一条SELECT把Illinois、Indiana、Michigan等州的缩写传递给IN子句,检索出这些州的所有行。第二条SELECT利用简单的相等测试找出所有Fun4All。两条SELECT语句之间用UNION关键字分隔。
上述UNION语句从查询结果集中自动去除了重复的行,例如Indiana州有一个Fun4All单位,所以两条SELECT语句都返回该行。使用UNION时,重复的行会被自动取消。但如果想返回所有的匹配行,可使用UNION ALL代替UNION:
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI')
UNION ALL
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
检索Illinois、Indiana和Michigan等美国几个州的所有顾客的报表,以及包括不管位于哪个州的所有的Fun4All。不取消重复的行。
使用组合查询时需要注意的是:
-- 查询所有学生信息
select * from tb_student;
-- 查询所有课程名称及学分(投影和别名)
select couname, coucredit from tb_course;
select couname as 课程名称, coucredit as 学分 from tb_course;
-- 查询所有学生的姓名和性别(条件运算)
select stuname as 姓名, case stusex when 1 then '男' else '女' end as 性别 from tb_student;
select stuname as 姓名, if(stusex, '男', '女') as 性别 from tb_student;
-- 查询所有女学生的姓名和出生日期(筛选)
select stuname, stubirth from tb_student where stusex=0;
-- 查询所有80后学生的姓名、性别和出生日期(筛选)
select stuname, stusex, stubirth from tb_student where stubirth>='1980-1-1' and stubirth<='1989-12-31';
select stuname, stusex, stubirth from tb_student where stubirth between '1980-1-1' and '1989-12-31';
-- 查询姓"杨"的学生姓名和性别(模糊)
select stuname, stusex from tb_student where stuname like '杨%';
-- 查询姓"杨"名字两个字的学生姓名和性别(模糊)
select stuname, stusex from tb_student where stuname like '杨_';
-- 查询姓"杨"名字三个字的学生姓名和性别(模糊)
select stuname, stusex from tb_student where stuname like '杨__';
-- 查询名字中有"不"字或"嫣"字的学生的姓名(模糊)
select stuname, stusex from tb_student where stuname like '%不%' or stuname like '%嫣%';
-- 查询没有录入家庭住址的学生姓名(空值)
select stuname from tb_student where stuaddr is null;
-- 查询录入了家庭住址的学生姓名(空值)
select stuname from tb_student where stuaddr is not null;
-- 查询学生选课的所有日期(去重)
select distinct seldate from tb_record;
-- 查询学生的家庭住址(去重)
select distinct s8tuaddr from tb_student where stuaddr is not null;
-- 查询男学生的姓名和生日按年龄从大到小排列(排序)
select stuname as 姓名, datediff(curdate(), stubirth) div 365 as 年龄 from tb_student where stusex=1 order by 年龄 desc;
-- 查询年龄最大的学生的出生日期(聚合函数)
select min(stubirth) from tb_student;
-- 查询年龄最小的学生的出生日期(聚合函数)
select max(stubirth) from tb_student;
-- 查询男女学生的人数(分组和聚合函数)
select stusex, count(*) from tb_student group by stusex;
-- 查询课程编号为1111的课程的平均成绩(筛选和聚合函数)
select avg(score) from tb_record where cid=1111;
-- 查询学号为1001的学生所有课程的平均分(筛选和聚合函数)
select avg(score) from tb_record where sid=1001;
-- 查询每个学生的学号和平均成绩(分组和聚合函数)
select sid as 学号, avg(score) as 平均分 from tb_record group by sid;
-- 查询平均成绩大于等于90分的学生的学号和平均成绩
-- 分组以前的筛选使用where子句 / 分组以后的筛选使用having子句
select sid as 学号, avg(score) as 平均分 from tb_record group by sid having 平均分>=90;
-- 查询年龄最大的学生的姓名(子查询/嵌套的查询)
select stuname from tb_student where stubirth=( select min(stubirth) from tb_student );
-- 查询年龄最大的学生姓名和年龄(子查询+运算)
select stuname as 姓名, datediff(curdate(), stubirth) div 365 as 年龄 from tb_student where stubirth=( select min(stubirth) from tb_student );
-- 查询选了两门以上的课程的学生姓名(子查询/分组条件/集合运算)
select stuname from tb_student where stuid in ( select stuid from tb_record group by stuid having count(stuid)>2 );
-- 查询学生姓名、课程名称以及成绩(连接查询)
select stuname, couname, score from tb_student t1, tb_course t2, tb_record t3 where stuid=sid and couid=cid and score is not null;
-- 查询学生姓名、课程名称以及成绩按成绩从高到低查询第11-15条记录(内连接+分页)
select stuname, couname, score from tb_student inner join tb_record on stuid=sid inner join tb_course on couid=cid where score is not null order by score desc limit 5 offset 10;
select stuname, couname, score from tb_student inner join tb_record on stuid=sid inner join tb_course on couid=cid where score is not null order by score desc limit 10, 5;
-- 查询选课学生的姓名和平均成绩(子查询和连接查询)
select stuname, avgmark from tb_student, ( select sid, avg(score) as avgmark from tb_record group by sid ) temp where stuid=sid;
select stuname, avgmark from tb_student inner join ( select sid, avg(score) as avgmark from tb_record group by sid ) temp on stuid=sid;
-- 查询每个学生的姓名和选课数量(左外连接和子查询)
select stuname, ifnull(total, 0) from tb_student left outer join ( select sid, count(sid) as total from tb_record group by sid ) temp on stuid=sid;
-- 创建名为hellokitty的用户
create user 'hellokitty'@'%' identified by '123123';
-- 将对school数据库所有对象的所有操作权限授予hellokitty
grant all privileges on school.* to 'hellokitty'@'%';
-- 召回hellokitty对school数据库所有对象的insert/delete/update权限
revoke insert, delete, update on school.* from 'hellokitty'@'%';
视图是关系型数据库中将一组查询指令构成的结果集组合成可查询的数据表的对象。简单的说,视图就是虚拟的表,但与数据表不同的是,数据表是一种实体结构,而视图是一种虚拟结构,你也可以将视图理解为保存在数据库中被赋予名字的SQL语句。
使用视图可以获得以下好处:
create view vw_avg_score
as
select stuname, avgscore
from
tb_student t1,
(select sid, round(avg(score), 1) as avgscore from tb_record group by sid) t2
where stuid=sid;
提示:因为视图不包含数据,所以每次使用视图时,都必须执行查询以获得数据,如果你使用了连接查询、嵌套查询创建了较为复杂的视图,你可能会发现查询性能下降得很厉害。因此,在使用复杂的视图前,应该进行测试以确保其性能能够满足应用的需求。
使用视图。
select stuname, avgscore from vw_avg_score order by avgscore desc;
+--------------+----------+
| stuname | avgscore |
+--------------+----------+
| 杨过 | 95.6 |
| 任我行 | 53.5 |
| 王语嫣 | 84.3 |
| 纪嫣然 | 73.8 |
| 岳不群 | 78.0 |
| 东方不败 | 88.0 |
| 项少龙 | 92.0 |
+--------------+----------+
删除视图。
drop view vw_avg_score;
说明:如果希望更新视图,可以先用上面的命令删除视图,也可以通过
create or replace view
来更新视图。
视图的规则和限制。
order by
子句,但如果从视图中检索数据时也使用了order by
,那么该视图中原先的order by
会被覆盖。迄今为止,我们使用的大多数SQL语句都是针对一个或多个表的单条语句,但在实际开发中经常会遇到某个操作需要多条SQL语句才能完成的情况。例如,电商网站在受理用户订单时,需要做以下一系列的处理。
我们可以通过创建存储过程来解决这些问题。简单的说,存储过程就是为以后的使用而保存的一条或多条MySQL语句的集合。通过存储过程,可以将复杂的操作封装起来,而且有助于保证数据的一致性;如果将来业务发生了变动,只需要调整修改存储过程即可。对于调用存储过程的用户来说,存储过程并没有暴露数据表的细节,而且执行存储过程比执行单独的SQL要快。
下面的存储过程实现了查询某门课程的最高分、最低分和平均分。
delimiter $$
create procedure sp_get_score(courseId int,
out maxScore decimal(4,1),
out minScore decimal(4,1),
out avgScore decimal(4,1))
begin
select max(score) into maxScore from tb_record where cid=courseId;
select min(score) into minScore from tb_record where cid=courseId;
select avg(score) into avgScore from tb_record where cid=courseId;
end $$
delimiter ;
说明:在定义存储过程时,因为可能需要书写多条SQL,而分隔这些SQL需要使用分号作为分隔符,如果这个时候,仍然用分号表示整段代码结束,需要执行这段代码,那么定义存储过程的SQL就会出现错误,所以上面我们用
delimiter $$
将整段代码结束的标记定义为$$
,那么代码中的分号将不再表示整段代码的结束,直到遇到end $$
整段代码才输入完成。定义完存储过程后,我们又将通过delimiter ;
将结束符改回成分号。
上面定义的存储过程有四个参数,其中第一个参数是输入参数,代表课程的编号,后面的参数都是输出参数,因为存储过程不能定义返回值,只能通过输出参数将执行结果带出,定义输出参数的关键字是out
,默认情况下参数都是输入参数。
调用存储过程。
call sp_get_score(1111, @a, @b, @c);
获取输出参数的值。
select @a as 最高分, @b as 最低分, @c as 平均分;
删除存储过程。
drop procedure sp_get_score;
实体完整性 - 每个实体都是独一无二的
引用完整性(参照完整性)- 关系中不允许引用不存在的实体
域完整性 - 数据是有效的
数据类型及长度
非空约束(not null)
默认值约束(default)
检查约束(check)
说明:在MySQL数据库中,检查约束并不起作用。
事务:一系列对数据库进行读/写的操作。
事务的ACID特性
MySQL中的事务操作
开启事务环境
start transaction
提交事务
commit
回滚事务
rollback
我们用如下所示的数据库来演示在Python中如何访问MySQL数据库。
drop database if exists hrs;
create database hrs default charset utf8;
use hrs;
drop table if exists tb_emp;
drop table if exists tb_dept;
create table tb_dept
(
dno int not null comment '编号',
dname varchar(10) not null comment '名称',
dloc varchar(20) not null comment '所在地',
primary key (dno)
);
insert into tb_dept values
(10, '会计部', '北京'),
(20, '研发部', '成都'),
(30, '销售部', '重庆'),
(40, '运维部', '深圳');
create table tb_emp
(
eno int not null comment '员工编号',
ename varchar(20) not null comment '员工姓名',
job varchar(20) not null comment '员工职位',
mgr int comment '主管编号',
sal int not null comment '员工月薪',
comm int comment '每月补贴',
dno int comment '所在部门编号',
primary key (eno)
);
alter table tb_emp add constraint fk_emp_dno foreign key (dno) references tb_dept (dno);
insert into tb_emp values
(7800, '张三丰', '总裁', null, 9000, 1200, 20),
(2056, '乔峰', '分析师', 7800, 5000, 1500, 20),
(3088, '李莫愁', '设计师', 2056, 3500, 800, 20),
(3211, '张无忌', '程序员', 2056, 3200, null, 20),
(3233, '丘处机', '程序员', 2056, 3400, null, 20),
(3251, '张翠山', '程序员', 2056, 4000, null, 20),
(5566, '宋远桥', '会计师', 7800, 4000, 1000, 10),
(5234, '郭靖', '出纳', 5566, 2000, null, 10),
(3344, '黄蓉', '销售主管', 7800, 3000, 800, 30),
(1359, '胡一刀', '销售员', 3344, 1800, 200, 30),
(4466, '苗人凤', '销售员', 3344, 2500, null, 30),
(3244, '欧阳锋', '程序员', 3088, 3200, null, 20),
(3577, '杨过', '会计', 5566, 2200, null, 10),
(3588, '朱九真', '会计', 5566, 2500, null, 10);
在Python 3中,我们通常使用纯Python的三方库PyMySQL来访问MySQL数据库,它应该是目前最好的选择。
安装PyMySQL。
pip install pymysql
添加一个部门。
import pymysql
def main():
no = int(input('编号: '))
name = input('名字: ')
loc = input('所在地: ')
# 1. 创建数据库连接对象
con = pymysql.connect(host='localhost', port=3306,
database='hrs', charset='utf8',
user='root', password='123456')
try:
# 2. 通过连接对象获取游标
with con.cursor() as cursor:
# 3. 通过游标执行SQL并获得执行结果
result = cursor.execute(
'insert into tb_dept values (%s, %s, %s)',
(no, name, loc)
)
if result == 1:
print('添加成功!')
# 4. 操作成功提交事务
con.commit()
finally:
# 5. 关闭连接释放资源
con.close()
if __name__ == '__main__':
main()
删除一个部门。
import pymysql
def main():
no = int(input('编号: '))
con = pymysql.connect(host='localhost', port=3306,
database='hrs', charset='utf8',
user='root', password='123456',
autocommit=True)
try:
with con.cursor() as cursor:
result = cursor.execute(
'delete from tb_dept where dno=%s',
(no, )
)
if result == 1:
print('删除成功!')
finally:
con.close()
if __name__ == '__main__':
main()
更新一个部门。
import pymysql
def main():
no = int(input('编号: '))
name = input('名字: ')
loc = input('所在地: ')
con = pymysql.connect(host='localhost', port=3306,
database='hrs', charset='utf8',
user='root', password='123456',
autocommit=True)
try:
with con.cursor() as cursor:
result = cursor.execute(
'update tb_dept set dname=%s, dloc=%s where dno=%s',
(name, loc, no)
)
if result == 1:
print('更新成功!')
finally:
con.close()
if __name__ == '__main__':
main()
查询所有部门。
import pymysql
from pymysql.cursors import DictCursor
def main():
con = pymysql.connect(host='localhost', port=3306,
database='hrs', charset='utf8',
user='root', password='123456')
try:
with con.cursor(cursor=DictCursor) as cursor:
cursor.execute('select dno as no, dname as name, dloc as loc from tb_dept')
results = cursor.fetchall()
print(results)
print('编号\t名称\t\t所在地')
for dept in results:
print(dept['no'], end='\t')
print(dept['name'], end='\t')
print(dept['loc'])
finally:
con.close()
if __name__ == '__main__':
main()
分页查询员工信息。
import pymysql
from pymysql.cursors import DictCursor
class Emp(object):
def __init__(self, no, name, job, sal):
self.no = no
self.name = name
self.job = job
self.sal = sal
def __str__(self):
return f'\n编号:{self.no}\n姓名:{self.name}\n职位:{self.job}\n月薪:{self.sal}\n'
def main():
page = int(input('页码: '))
size = int(input('大小: '))
con = pymysql.connect(host='localhost', port=3306,
database='hrs', charset='utf8',
user='root', password='123456')
try:
with con.cursor() as cursor:
cursor.execute(
'select eno as no, ename as name, job, sal from tb_emp limit %s,%s',
((page - 1) * size, size)
)
for emp_tuple in cursor.fetchall():
emp = Emp(*emp_tuple)
print(emp)
finally:
con.close()
if __name__ == '__main__':
main()
word=‘123456’,
autocommit=True)
try:
with con.cursor() as cursor:
result = cursor.execute(
‘update tb_dept set dname=%s, dloc=%s where dno=%s’,
(name, loc, no)
)
if result == 1:
print(‘更新成功!’)
finally:
con.close()
if name == ‘main’:
main()
5. 查询所有部门。
```python
import pymysql
from pymysql.cursors import DictCursor
def main():
con = pymysql.connect(host='localhost', port=3306,
database='hrs', charset='utf8',
user='root', password='123456')
try:
with con.cursor(cursor=DictCursor) as cursor:
cursor.execute('select dno as no, dname as name, dloc as loc from tb_dept')
results = cursor.fetchall()
print(results)
print('编号\t名称\t\t所在地')
for dept in results:
print(dept['no'], end='\t')
print(dept['name'], end='\t')
print(dept['loc'])
finally:
con.close()
if __name__ == '__main__':
main()
分页查询员工信息。
import pymysql
from pymysql.cursors import DictCursor
class Emp(object):
def __init__(self, no, name, job, sal):
self.no = no
self.name = name
self.job = job
self.sal = sal
def __str__(self):
return f'\n编号:{self.no}\n姓名:{self.name}\n职位:{self.job}\n月薪:{self.sal}\n'
def main():
page = int(input('页码: '))
size = int(input('大小: '))
con = pymysql.connect(host='localhost', port=3306,
database='hrs', charset='utf8',
user='root', password='123456')
try:
with con.cursor() as cursor:
cursor.execute(
'select eno as no, ename as name, job, sal from tb_emp limit %s,%s',
((page - 1) * size, size)
)
for emp_tuple in cursor.fetchall():
emp = Emp(*emp_tuple)
print(emp)
finally:
con.close()
if __name__ == '__main__':
main()