MySQL数据库基础知识
一、数据库说明
1.1 什么是数据库
数据库(Database)是一种存储结构化数据的方式,一般存在两种类型的数据库: 关系型数据库、非关系型数据库。
关系型数据库: 存储二维关系表的仓库, 二维关系表是由列(特征)和行组成的。提供标准的SQL语句进行管理(创建、修改、删除)。关系型数据库一般都支持约束、索引、视图、函数、存储过程、触发器、表、字典(动态视图)等对象。【重点】
非关系型数据库: 不需要使用SQL,通过SDK(软件开发环境-模块或库, Software Development Kit)相关的方法进行操作,不支持二维关系结构,又称之为NoSQL数据库。包含redis、mongodb等。
1.2 有哪些关系型数据库
大厂的关系型数据库: DB2(IBM公司)、Oracle(甲骨文公司)、MySQL(原Sun公司,目前也是甲骨文公司)。
其它的关系型数据库:MariDB(个人, 同MySQL一个作者)、 posgreSQL(开源的数据库)
Hadoop家族的数据库: Hive(依赖MySQL, MapReduce), Hbase(实时)
嵌入式设备中的数据库: sqlite3(没有特定的数据类型)、IotDB(分布式物联网存储的实时数据库)
1.3 数据范式
限制表的设计, 让表的结构更合理、减少数据冗余。
如设计存储书相关信息的表:
ID 书编号
name 书名
cover 封面图片连接
summary 介绍
author 作者
author_years 作者的写作年限
author_nickname 作者别名
author_info 作者的介绍
author_jg 作者的家乡
author_primary_book_name 作者代表作品
store_name 书店名称
store_city 书店所在的城市
store_boss 书店的老板姓名
store_tel 书店的联系电话
publish_date 书的发布时间
publisher 出版社
假如 某一个作者有10本书,作者的相关信息重复了10次,每一条数据的作者相关信息存在近500个汉字,按UTF8编码计算,每一行作者数据的字节大小为 500*3=1500,近1.2K, 10条件记录近12K。
表的第一范式: 确保每一个列保持原子性(列不可分隔)。
根据第一范式分隔书的表为某本书的表、作者表、书店表:
-- book表
ID 书编号
name 书名
cover 封面图片连接
summary 介绍
publisher 出版社
publish_date 书的发布时间
-- 作者表
author 作者姓名
author_years 作者的写作年限
author_nickname 作者别名
author_info 作者的介绍
author_jg 作者的家乡
author_primary_book_name 作者代表作品
-- store 书店表
store_name 书店名称
store_city 书店所在的城市
store_boss 书店的老板姓名
store_tel 书店的联系电话
表的第二范式: 确保每一列的数据都和主键相关, 即表中存在主键约束。
-- 作者表
ID 作者编号(主键)
author 作者姓名
author_years 作者的写作年限
author_nickname 作者别名
author_info 作者的介绍
author_jg 作者的家乡
author_primary_book_name 作者代表作品
-- store 书店表
ID 书店(主键)
store_name 书店名称
store_city 书店所在的城市
store_addres 书店的地址
store_boss 书店的老板姓名
store_tel 书店的联系电话
表的第三范式: 确保每一列都和主键直接相关,而不能间接相关。即存在主外键关系。
-- book表
ID 书编号
name 书名
..
author_id 作者的ID, 外键
store_id 书店的ID, 外键
二、DDL语句
DDL 数据库描述或定义语言(Data Definition Language),包含数据库、表、视图、函数、索引、存储过程、触发器等对象的创建、修改和删除等操作。
2.1 创建和删除数据库
创建数据库语法:
create database <数据库名> [ charset <字符集> ];
如,创建bookdb数据库
create database bookdb;
删除数据库,即将库中所有对象全部删除【慎用】
drop database [if exists] <数据库名称>;
if exists
确保SQL正常执行,如果数据库存在,则删除,不存在pass。
如,删除bookdb库
drop database bookdb;
2.2 创建表
官方文档:https://dev.mysql.com/doc/refman/8.0/en/create-table.html
创建表之前,必须先使用 use <数据库名>
语句打开某一个数据库,表示在某一数据库下创建表。
简洁的语法:
create table [if not exists] <表名>(
<字段名> <字段类型>[ (字段数据长度) ] [约束] [comment 注释文本],
...,
[constraint 表级约束语句]
) [engine=INNODB|MYISAM|MEMERY|CSV] [character set 字符集];
说明:
- 字段名,不能和数据库的关系冲突,一般同Python语言标识符的命名类同,由字母、数字和下划线及特殊符号组成。
- 字段类型,mysql提供了丰富的字段类型,包含:
- 字符类型
char
, 固定长度的字符类型,即分配多少长度(字符的个数),即用多少长度。
【扩展】正则表达式中匹配中文的unicode编码范围:\u4e00-\u9fa5
- 可变字符类型
varchar
,与char不同,根据内容器的多少,使用多少。不会使用给定的最大长度。 - 整数类型
int
和integer
- 小数类型
float
、double
、decimal
(可以设置小数点后的精确位) - 日期类型
date
- 日期时间类型
timestamp
包含日期部分和时间部分 - 文本类型
text
- 大文本类型
clob
- 二进制类型
blob
- 约束, 根据不同的约束类型,检查数据,如果不合法,则抛出异常显示违反
xxx
约束。
- 主键约束 primary key: 非空,唯一的
- 外键约束 foreign key: 值,依赖于外部主表的主键值。外键字段的值可以为空的。
- 唯一约束 unique : 值在整个一列中是唯一的 ,值可以存在一个空值。
- 非空约束 not null : 列值不能为空
- 默认值 default : 列值在设计时,可以指定默认值
- 自增 auto_increment: 列值是某一个序列的下一个值,不需要指定。
- 引擎
- InnoDB : 支持事务、行级锁、外键约束 【默认】
- MEMERY: 使用hash函数, 存储于内存,使用临时表。用于缓存
- MyISAM: 不支持事务、行级锁和外键约束
- CSV: 以CSV格式存储数据
如,创建附合第三个范式的图书表、作者表
drop database if exists bookdb;
create database bookdb;
use bookdb;
drop table if exists author;
drop table if exists book;
create table if not exists author
(
author_id integer primary key auto_increment,
name varchar(20),
city varchar(20),
phone char(11)
);
create table if not exists book
(
book_id integer auto_increment comment '图书编号',
name varchar(50) unique comment '书名',
summary text comment '简介',
price decimal(5, 2) comment '单价',
act_price decimal(5, 2) comment '活动价',
cover_url varchar(100) comment '封面的URL地址',
author_id integer,
constraint book_pk primary key (book_id),
constraint book_author_fk foreign key (author_id) references author(author_id) on delete cascade
);
show tables;
【重要说明】constraint子句
constraint [约束名称] 约束类型 [index_type: {BTREE, HASH}] (字段名,...)
[references 引用表的名称(引用的字段名) on <delete|update> <restrict|cascade|set null|no action|set default> ]
约束类型: primary key、unique、foreign key
只有primary key和unique 存在index_type关键字
只有foreign key 存在 references 关键字。
【扩展-docker创建与宿主机同步的mysql容器】
准备工作:
- 确认 /root/mysql 目录是否从某一个mysql已运行容器的/etc/mysql复制的
- 确认/root/mysql/my.cnf 文件中关于
character-set-server
是否配置 - 确认/root/mysql/conf.d/mysql.cnf 文件中
default-character-set
是否配置 - 确认/root/mysql/sql/init.sql 脚本是否存在
docker run -d --name db000 -e MYSQL_ROOT_PASSWORD=root -p 3313:3306 --network db-net -v /root/mysql:/etc/mysql mysql
docker exec db000 ls /etc/mysql
docker exec -it db000 mysql -uroot -proot
mysql> show databases;
mysql> create database if not exists bookdb;
mysql> use bookdb;
mysql> source /etc/mysql/sql/init.sql
mysql> show tables;
2.3 修改表
修改表: 表名、表字段名、字段类型及字段的约束。
2.3.1 修改表名
alter table <表名> rename [to | as] <新表名>;
修改book表为tb_book
alter table book rename as tb_book;
修改author表为tb_author
alter table author rename to tb_author;
2.3.2 修改字段名
alter table <表名> change [column] <旧列名> <新列名> <类型[(长度)]>
修改tb_book表中的name字段为book_name
alter table tb_book change column name book_name varchar(40);
修改tb_book表的price
为src_price
名称,数据类型保持不变
alter table tb_book change price src_price decimal(5,2);
2.3.3 修改字段类型
alter table <表名> modify <列名> <类型[(数据长度)]> [约束]
修改tb_book表的summary
的类型改为 varchar
,且数据长度为200个字符
alter table tb_book modify summary varchar(200);
2.3.4 删除字段主外键约束
2.3.4.1 删除外键约束
alter table <表名> drop foreign key <约束名称>;
删除tb_book表的book_author_fk
外键约束:
alter table tb_book drop foreign key book_author_fk;
2.3.4.2【查询表的相关约束】
依据information_schema字典库的table_constraints表
查询bookdb数据库中所有表的约束信息
select table_name,constraint_name, constraint_type
from information_schema.table_constraints
where table_schema='bookdb';
+------------+-----------------+-----------------+
| TABLE_NAME | CONSTRAINT_NAME | CONSTRAINT_TYPE |
+------------+-----------------+-----------------+
| tb_author | PRIMARY | PRIMARY KEY |
| tb_book | name | UNIQUE |
| tb_book | PRIMARY | PRIMARY KEY |
| tb_book | book_author_fk | FOREIGN KEY |
+------------+-----------------+-----------------+
2.3.4.3 删除主键约束
alter table <表名> drop primary key;
删除tb_author表的主键约束
alter table tb_author modify author_id integer ;
alter table tb_author drop primary key;
2.3.4.4 删除唯一约束
alter table <表名> drop {index|key} 唯一约束名或列名
【注意】默认情况唯一约束名即为列名。
删除tb_book表的name字段的唯一约束
alter table tb_book drop index name;
2.3.5 删除字段
alter table <表名> drop [column] <字段名>;
删除tb_book的summary字段
alter table tb_book drop summary;
删除tb_book的author_id字段
alter table tb_book drop author_id;
2.3.6 添加字段
alter table <表名>
add [column] <列名> 数据类型[(数据长度)] [约束] [comment 注释信息]
添加tb_book的summary字段,类型为text, 不能为空
alter table tb_book
add column summary text not null;
添加tb_book的author_id字段,类型为integer
alter table tb_book
add column author_id integer;
2.3.7 添加字段约束
alter table <表名>
add [constraint [约束名] ]
| primary key [index_type: {using btree|hash}] (字段1, 字段2, ....)
| unique [key | index ] [index_name] [index_type] (字段1, 字段2, ...)
| foreign key (字段1, 字段2,...) references 引用表名(字段1,字段2, ...) [on delete|update restrict|cascade|set null|no action|set default]
【索引分类】主键索引、唯一索引、普通索引、组合索引
【索引类型】BTREE、HASH
添加tb_author表的author_id字段的主键约束,并设置为自增
alter table tb_author modify author_id integer primary key auto_increment;
-- alter table tb_author add primary key (author_id);
添加tb_book的author_id的外键约束,引用tb_author的author_id字段
alter table tb_book
add constraint book_author_fk foreign key (author_id) references tb_author(author_id);
2.4 删除表
drop table [if exists] <表名>
如,删除tb_book表
drop table tb_book;
【注意】无论是删除表或列,则表存在的相关约束都会被删除。
2.5 查询表结构
方式1:
DESC <表名>
方式2:
show create table <表名>
查看表创建时的SQL语句。
同样,查看数据库的创建SQL语句:
show create database <数据库名>
2.6 查看所有的表
方式1: show tables
方式2: 使用information_schema字典的tables表
select table_name,table_type,table_schema
from information_schema.tables
where table_schema='bookdb';
2.7 子查询创建表
基于SQL查询语句,快速创建一张无约束的表。
create table <表名>
select子句;
如:创建北京作者信息的表
create table tb_bejin_author
select * from tb_author where city='北京';
三、DML语句
DML(Data Manipulation Language) 数据操纵语言,主要负责数据(行或记录)的插入、更新和删除操作。
3.1 insert语句
insert语句是向表中插入一行或多行数据。
3.1.1 单行数据插入
insert into <表名> [(字段1, 字段2, ...)]
values (字段值1, 字段值2, ...)
说明:
- 表名之后,通过
()
声明向哪些字段插入数据;默认是全部字段,插入值的顺序按创建表时声明字段顺序。 - values后的
()
中不同类型的数据,必须按插入值字段的类型匹配,如字符类型必须使用单引号''
,数值类型不能使用单引号,日期类型可以使用单引号。
如,新增一位作者,姓名为disen,电话17791692095,城市是西安
-- author_id, name, city, phone
insert into tb_author
values(1, 'disen', '西安', '17791692095');
如,新增西安
城市的李九江
作者,他的电话是120
insert into tb_author(city, name, phone)
values('西安','李九江','120');
【注意】如果遇到自增列auto_increment
时,避免显式方式指定列值。
3.1.2 多行数据插入
insert into <表名> [((字段1, 字段2, ...))] values
(字段值1, 字段值2, ...),
(字段值1, 字段值2, ...),
(字段值1, 字段值2, ...),
...
(字段值1, 字段值2, ...);
如,插入3位作者信息
insert into tb_author(city, name, phone) values
('北京', '老邓', '18918810018'),
('北京', '老刘', '17866541239'),
('杭州', '老马', '18766541908');
3.1.3 子查询方式插入
使用场景: 一般在分表存储数据时使用。
insert into <表名> [(字段1, ...)]
select子句;
如:将tb_author表中的所有北京的作者插入到tb_bejin_author表
insert into tb_bejin_author
select * from tb_author where city='北京';
【练习-添加tb_book表数据】
insert into tb_book(book_name, src_price, summary, cover_url) values
('Python入门第一版本', 20, 'Python2.7精讲', null),
('Python3精通', 50, 'Disen主讲Python3.7', '/static/img/1001.jpg'),
('MySQL5.7', 90, 'Mysql 5.7的DDL,DML,DQL等', '/static/img/1002.jpg'),
('MySQL8.0', 12, 'MySQL 8.0的全部内容', null),
('Linux精通到放弃', 28, 'Linux Centos7', null),
('Linux Shell编程', 21, 'Shell脚本开发',null);
3.2 update语句
update主要负责数据的更新或修改。
update <表名> set
字段1=字段值1 [,字段2=字段值2, ...]
[where 子句]
说明: where子句,支持等值条件、逻辑运算、空值。
- 等值条件:
字段名=值
,字段名 >= | < | <= | != 值
,字段名 between 起始值 and 结束值
闭区间 - 逻辑运算: 指多个条件之间的逻辑关系,包含
and
、or
、not
, 另外,字段名 in(值1, 值2, 值3,...)
等价于字段名=值1 or 字段名=值2 or 字段名=值3
。 - 空值:
is null
、not null
如,更新tb_author西安的disen作者的城市为上海
update tb_author set
city='上海'
where city='西安' and name='disen';
如, 修改tb_author表中disen作者的姓名为’老狄’,城市修改为’北京’
update tb_author set
city='北京',name='老狄'
where name='disen';
如,修改tb_book表,将原价的20到30之间的所有图书打7折
select book_name, src_price,act_price
from tb_book;
-- tb_book(book_id, book_name, src_price, act_price, ....)
update tb_book set
act_price=src_price*0.7
where src_price between 20 and 30;
【注意】在更新某一字值时,可以引用原字段的数据。如act_price=src_price*0.7
。
如,将tb_book表中所有cover_url字段为空修改为/static/img/default.jpg
select book_name, cover_url from tb_book;
update tb_book set
cover_url='/static/img/default.jpg'
where cover_url is null;
【注意】更新数据时,避免全部数据更新,即必须带有where条件
3.3 delete语句
delete删除表的数据,不会影响表结构本身。
delete from <表名>
[where 子句]
【注意】如果delete语句不存在where子句时,表示清空表的内容。
如, 删除tb_book表中活动价格低于10元的数据
select book_name, act_price from tb_book;
begin;
delete from tb_book
where act_price <= 15;
commit;
【练习-外键约束的数据插入和级联操作】
-- tb_author(author_id, name, city, phone)
-- tb_book(book_id, book_name, ..., author_id)
insert into tb_author(name, city, phone)
values
('老李','西安', '19887726619'),
('小王','北京', '19826661190');
mysql> select * from tb_author;
+-----------+--------+--------+-------------+
| author_id | name | city | phone |
+-----------+--------+--------+-------------+
| 1 | 老李 | 西安 | 19887726619 |
| 2 | 小王 | 北京 | 19826661190 |
+-----------+--------+--------+-------------+
2 rows in set (0.00 sec)
mysql> select book_id,book_name, author_id from tb_book;
+---------+----------------------+-----------+
| book_id | book_name | author_id |
+---------+----------------------+-----------+
| 2 | Python3精通 | NULL |
| 3 | MySQL5.7 | NULL |
| 4 | MySQL8.0 | NULL |
| 5 | Linux精通到放弃 | NULL |
+---------+----------------------+-----------+
-- 修改tb_book表的book_id是2的author_id为3,是否可以成功呢?如果不成功,抛出什么错误呢?
-- 原因: 更改的author_id不在tb_author表的主键中。
update tb_book
set author_id=3
where book_id=2;
mysql> update tb_book
-> set author_id=3
-> where book_id=2;
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`bookdb`.`tb_book`, CONSTRAINT `book_author_fk` FOREIGN KEY (`author_id`) REFERENCES `tb_author` (`author_id`) ON DELETE CASCADE)
-- 将所有书的作者全部修改为author_id为1的作者
update tb_book
set author_id=1;
-- 删除author_id为1的作者记录
-- 外键约束的级联操作: ON DELETE CASCADE 级联删除
delete from tb_author where author_id=1;
【注意】当事务开启后,且执行了多个DML语句,在未提交事务之前,如果发生了DDL语句,则会自动提交事务。
alter table tb_book
drop foreign key book_author_fk;
-- 添加tb_book和tb_author的外键约束,支持级联删除和级联更新
alter table tb_book
add constraint book_author_fk foreign key (author_id)
references tb_author(author_id) on delete cascade on update cascade;
-- ON UPDATE CASCADE 级联更新
-- 修改author_id为1的作者编号为110: 如果外键约束中存在级联更新,则直接修改外键字段的值。
update tb_author
set author_id=110
where author_id=1;
-- 外键约束:级联删除置空
alter table tb_book
drop foreign key book_author_fk;
alter table tb_book
add constraint book_author_fk foreign key (author_id)
references tb_author(author_id) on delete set null;
begin;
delete from tb_author where author_id=110;
select book_name, author_id from tb_book;
rollback;
四、DQL语句
DQL(Data Query Language)数据查询语言,主要负责数据的查询,包含单表查询、多表查询、子查询、分组查询、条件查询、模糊查询、分页查询、窗口函数统计等。
官方文档:https://dev.mysql.com/doc/refman/8.0/en/select.html
4.1 select语句
select [distict] 字段名|表达式 [ [AS] 别名] [, 字段|表达式,...]
[ from <表名>|<select 子查询> [ [as] 别名] [, <表名>|<select子查询>,...] ]
[[left|right|] join <表名>|<select 子查询> [[as] 别名] on (连接条件),... ]
[where 子句]
[group by 子句]
[having 子句]
[order by 列名|表达式 [ASC | DESC]]
[limit offset, rows]
4.1.1 select中的字段
在select子句中,可以选择表中的字段,也可以使用表达式(算术、函数、 关系、逻辑)。
如,查看当前时间(current_date、now())
select current_date;
select now();
mysql> select now();
+---------------------+
| now() |
+---------------------+
| 2021-09-03 06:09:03 |
+---------------------+
mysql> select now() 'this time';
+---------------------+
| this time |
+---------------------+
| 2021-09-03 06:09:03 |
+---------------------+
mysql> select current_date as cdate;
+------------+
| cdate |
+------------+
| 2021-09-03 |
+------------+
mysql> select 100>20 as ret;
+-----+
| ret |
+-----+
| 1 |
+-----+
1 row in set (0.00 sec)
mysql> select 100<20 as ret;
+-----+
| ret |
+-----+
| 0 |
+-----+
1 row in set (0.00 sec)
mysql> select 1 and 2 or 0 a123;
+------+
| a123 |
+------+
| 1 |
+------+
mysql> select uuid();
+--------------------------------------+
| uuid() |
+--------------------------------------+
| 8f232ae9-0c80-11ec-96d6-0242ac110002 |
+--------------------------------------+
mysql> select replace(uuid(), '-', '') as USERID;
+----------------------------------+
| USERID |
+----------------------------------+
| bca7edc00c8011ec96d60242ac110002 |
+----------------------------------+
【扩展-面试题】
create table A(year int, month int, amount float);
insert into A values
(1991, 1, 1.1),
(1991,2, 1.2),
(1992,1, 2.1),
(1992,2, 2.2);
问题存在一张A表,内容如下:
+------+-------+--------+
| year | month | amount |
+------+-------+--------+
| 1991 | 1 | 1.1 |
| 1991 | 2 | 1.2 |
| 1992 | 1 | 2.1 |
| 1992 | 2 | 2.2 |
+------+-------+--------+
请写出如下结果的SQL语句:
+------+-------+--------+
| year | m1 | m2 |
+------+-------+--------+
| 1991 | 1.1 | 1.2 |
| 1992 | 2.1 | 2.2 |
最有效的写法-SQL:
select year,
round(max(if(month=1,amount,0.0)),1) as m1,
round(max(if(month=2, amount,0.0)),1) as m2
from A
group by year;
4.1.2 from子句
from 关键字后面可以跟表名或子查询,如果是子查询时,必须提供别名,作为子查询结果的临时表名。 表名的别名,可以在select子句中使用。
from 后跟多个表时,需要用逗号分隔。 如果从多个表中查询数据,必须提供多个表之间的连接条件,否则会出现
笛卡尔集
。
select b.book_name, b.author_id, a.name as author_name
from tb_book b, tb_author a
where b.author_id = a.author_id;
4.2 where子句
where子句主要的作用是限制数据的范围,在select 的where子句中的字段,一般都是index索引字段(加速查询)。
where 子句中支持非常丰富的条件表达式,包含between-and、like、函数表达式、in、is null、not null等。
-- 查看活动价格在10-20之间的所有书的信息
select book_id,book_name,act_price
from tb_book
where act_price between 10 and 20;
-- 查看姓名中带`李`的作者
select * from tb_author
where name like '%李%';
【like说明】like是一种模糊匹配的查询条件,可以使用%
和_
通配符。
- % 代表任意长度的任意字符
- _ 代表一个长度的任意字符
-- 查看姓名中第三个字符为`丽`的作者
select * from tb_author
where name like '__丽%';
-- 查看书编号为1或3或5的书信息
select * from tb_book
where book_id in (1, 3, 5);
-- 查看除作者编号为1和5之外的所有作者信息
select * from tb_author
where author_id not in (1, 5);
4.3 join语句
join语句支持多个表之间的连接,通过on指定表连接的条件。
使用join可以减少where等值连接的子句。
4.3.1 内连接
内连接即表示两个表都具有的数据
mysql> update tb_book set author_id=null where book_id=4;
Query OK, 1 row affected (0.02 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select book_id,book_name, author_id from tb_book;
+---------+---------------+-----------+
| book_id | book_name | author_id |
+---------+---------------+-----------+
| 2 | Python3精通 | 110 |
| 3 | MySQL5.7 | 110 |
| 4 | MySQL8.0 | NULL |
+---------+---------------+-----------+
mysql> select author_id,name from tb_author;
+-----------+--------+
| author_id | name |
+-----------+--------+
| 2 | 小王 |
| 110 | 老李 |
+-----------+--------+
select a.book_id, a.book_name, b.name as author_name,b.author_id
from tb_book a
join tb_author b on (a.author_id = b.author_id);
4.3.2 左外连接
除了内连接的数据之前,也会选择左表的其它数据。
select a.book_id, a.book_name, b.name as author_name,b.author_id
from tb_book a
left join tb_author b on (a.author_id = b.author_id);
4.3.3 右外连接
除了内连接的数据之前,也会选择右表的其它数据。
select a.book_id, a.book_name, b.name as author_name,b.author_id
from tb_book a
right join tb_author b on (a.author_id = b.author_id);
4.3.4 自然连接
根据两个表之间的共同的列名,自动连接。默认为内连接,也可以是左外连接或右外连接。自然连接即省略了on条件。
select a.book_id, a.book_name, b.name as author_name,b.author_id
from tb_book a
natural join tb_author b;
select a.book_id, a.book_name, b.name as author_name,b.author_id
from tb_book a
natural left join tb_author b;
select a.book_id, a.book_name, b.name as author_name,b.author_id
from tb_book a
natural right join tb_author b;
4.4 分组语句
group by 分组子句,可以按列中相同的数据进行分组,分组之后的数据可以聚合计算,聚合计算函数包含最小min()、最大max()、求和sum()、统计数量count和平均avg()。
-- 统计每位作者的书的数量
-- select 1;
select a.author_id,a.name, count(1) as books
from tb_book b
natural join tb_author a
group by a.author_id,a.name;
-- 统计每位作者出书的最大价格、最小价格、平均价格
select author_id,
max(src_price) 最高价,
min(src_price) 最低价,
avg(src_price) 均价
from tb_book
group by author_id;
【扩展-设计书的订单表】
drop table if exists tb_app_user;
drop table if exists tb_order;
-- 设计会员表
create table tb_app_user(
user_id integer primary key auto_increment,
phone char(11) unique,
authentication_string varchar(255) not null,
nick_name varchar(50)
);
insert into tb_app_user(phone, authentication_string) values
('17791692095', sha1('692095')),
('19524662948', sha1('662948')),
('18766541234', sha1('541234')),
('19077653412', sha1('653412'));
-- 订单表
create table tb_order(order_id char(32) primary key,
order_title varchar(50),
book_id integer references tb_book(book_id),
order_price decimal(10, 2),
order_pay_status int default 0,
order_pay_time timestamp,
user_id integer references tb_app_user(user_id)
);
insert into tb_order values
(replace(uuid(), '-',''), '<西安千锋>Python3.7', 2, 39, 1, '2020-12-11 14:35:12', 1),
(replace(uuid(), '-',''), '<西安千锋>Python3.7', 2, 31, 1, '2020-12-11 17:35:12', 2),
(replace(uuid(), '-',''), '<西安千锋>MySQL5.7', 3, 88, 1, '2020-12-11 15:12:12', 1),
(replace(uuid(), '-',''), '<西安千锋>MySQL5.7', 3, 88, 1, '2020-12-11 17:45:12', 2),
(replace(uuid(), '-',''), '<西安千锋>Python3.7', 2, 39, 1, '2020-12-13 10:35:12', 3),
(replace(uuid(), '-',''), '<西安千锋>Python3.7', 2, 39, 1, '2020-12-13 11:35:12', 4),
(replace(uuid(), '-',''), '<西安千锋>MySQL5.7', 3, 88, 1, '2020-12-13 14:10:12', 3);
-- [任务] 统计每一位作者的图书总销量情况
-- 【注意】列的别名,可以用于order by 、group by、having的子句中。
select a.name as author_name, count(1) as books, sum(o.order_price) total_price
from tb_order o
natural join tb_book b
natural join tb_author a
where o.order_pay_status=1
group by author_name
-- having books > 3;
order by total_price DESC, books DESC;
+-------------+-------+--------------------+
| author_name | books | total_price |
+-------------+-------+--------------------+
| 老李 | 4 | 148.00 |
| 小王 | 3 | 264.00 |
+-------------+-------+--------------------+
4.5 排序语句
order by 子句可以按列的值进行升序ASC、降序DESC进行排序。
【注意】order by 可以对多个列进行排序,先左后右。如果左边的值相同的情况下,按右边的列值进行排序。
-- 练习: 查看已支付图书的信息,按支付的时间进行降序排序,如果时间相同,按图书的编号进行升序排序。
-- 时间格式化的函数: date_format(日期时间字符串, ‘%Y-%m-%d %H:%i:%s’)
select order_title,
if(order_pay_status=1,'OK','CANCEL') as pay_status,
book_id,
order_pay_time
from tb_order
order by date_format(order_pay_time,'%Y-%m-%d') desc, book_id;
4.6分页语句
mysql 中分页使用limit 关键字, limit子句在select查询语句的最后位置。
limit 起始行号(从0开始), 记录个数(行数,一般使用10, 20, 50, 100)
-- 练习: 按每页显示5条记录,显示第2页的所有订单数据
-- 提示: offset起始位置: (page-1)*page_size
select order_title, book_id, order_pay_time
from tb_order
limit 5, 5;
4.7 union语句
union 语句实现两个select子查询的数据联合。默认是去重的。
union all 保留所有子查询的结果,不去重。
-- 实现全外连接
select a.book_id, a.book_name, b.name as author_name,b.author_id
from tb_book a
natural left join tb_author b
union
select a.book_id, a.book_name, b.name as author_name,b.author_id
from tb_book a
natural right join tb_author b;
4.8 window语句
mysql 从8.0开始支持窗口语句查询,即分区统计和现在数据进行连接显示(输出 )。一般配合 聚合函数+ over(), over()函数中可以指定分区(分组)的字段,即指定partition by子句。当然也可以使用其它的窗口函数,如rank实现排名。
4.8.1 聚合函数+over
-- 查看每天的图书销售信息,同时显示当天的总销售额。
select order_title, order_pay_time,
sum(order_price) over(partition by date_format(order_pay_time, '%Y-%m-%d')) as day_total_amount
from tb_order;
4.8.2 非聚合函数+over
4.8.2.1 row_number()
计算行的序号,在over()函数中支持的order by子句。
-- 查看作者的信息,并按注册时间的年份编排一个序号
-- 修改作者表,添加一个 join_date 列
alter table tb_author
add join_date date;
update tb_author set join_date='2010-10-12' where author_id=2;
update tb_author set join_date='2011-11-23' where author_id=110;
insert into tb_author values
(1, '老狄', '西安', '19877796155','2010-08-01'),
(3, '老杨', '天水', '19998122112','2011-07-12');
select *,
row_number() over(partition by year(join_date) order by join_date) as number
from tb_author;
4.8.2.2 rank()排名
rank()函数实现名次排序,同时会考虑到相同名次。
-- 查询学生的总成绩并进行排名
-- 查询总成绩第三名的学生信息
select * from (
select s.*, A.total,rank() over(order by total DESC) as rank_number
from (
select sid, sum(score) as total
from SC
group by sid
) A
join Student s on (s.sid=A.sid)
) C
where rank_number=3;
-- 不使用rank()函数时,显示排名
select A1.sid, count(A2.sid)+1 as rank_num
from
(
select sid, sum(score) as total
from SC
group by sid
) A1
left join (
select sid, sum(score) as total
from SC
group by sid
) A2 on (A1.total < A2.total)
group by A1.sid
order by rank_num;
-- 使用 @变量方式
select A.*, (@i:=@i+1) as rank_num
from (
select sid, sum(score) as total
from SC
group by sid
order by total DESC
) A, (select @i:=0) C;
set @crank=0;
select q.sid, total, @crank := @crank +1 as rank1 from(
select sid, sum(score) as total from SC
group by SC.sid
order by total desc)q;
【周未任务】
1. 分析某一个app页面,设计表(符合第三范式),表的数量不低20张。(PowerDesigner设计工具-物理模型)
2. 完成SQL练习.doc文件中至少5题。
五、DTL语句
DTL(Data transaction Language ) 数据事务语言,主要控制当前操作的事务性,包含开启事务、提交事务、回滚事务、保存事务还原点、及事务的隔离级别设置等。
事务具有四个特性(ACID):
- (Atomicity)原子性: 事务中的操作要么全执行,要么全失败。
- (Consistency)一致性: 事务操作的前后,数据保持一致
- (Isolation)隔离性: 事务之间互不影响, 具有四个级别
- 读未提交 read uncommitted
- 读已提交 read committed
- 可重复读 repeatable read
- 串行化 serializable
- (Durability)持久性: 事务提交后,数据将会保存,即使系统崩溃了,也不会影响数据。
5.1 事务的基本操作
5.1.1 开启事务
begin;
5.1.2 提交事务
commit;
5.1.3 回滚事务
rollback;
六、常用函数
6.1 时间函数
6.1.1 获取时间函数
current_date, current_date(),curdate() : 当前日期
now(): 当前日期时间
year(d):获取日期时间中的年
month(d): 获取日期时间中的月份
dayofmonth(d): 获取日期中的 日, 即月中第几天
hour(t): 获取时间的小时
minute(t): 获取时间的分钟
second(t): 获取时间的秒
dayofyear(d): 获取日期中一年中第几天
dayofweek(d): 获取日期中一周的星期几
如, 查看当前日期是一年中第几天?
select dayofyear(now());
6.1.2 时间转换函数
time_to_sec(t): 将时间转化为秒数
sec_to_time(s): 将秒数转化为时间
to_days(d) : 将日期转化为 0000年1月1日的天数
from_days(n): 将距离0000-00-00时间的n天转化为日期
date_format(d, f): 将日期转化为f格式的字符串,
- %Y 四位的年
- %m 2位的月份, 01-12月
- %d 2位的日, 01-31
- %h 12小时制的时
- %H 24小时制的时
- %i 2位分钟,00-59
- %s 2位秒, 00-59
- %M 月份的英文名全写
- %a 星期几的名称简写
- %W 星期几的名称全写
- %u 一年中第几周 00-53, 星期一是一周的第一天。
- %p 上午或下午的表示AM、PM
time_format(t, f): 将时间转化为f格式的字符串
6.1.3 时间间隔
datediff(d1, d2): d1和d2的间隔天数
timestampdiff(时间函数, d1, d2) :计算两个时间的时间函数的间隔数值
adddate(d, n): d日期增加n天后的日期
subdate(d, n): d日期减去n天后的日期
addtime(t, s): t时间增加s秒后的时间
subtime(t, s): t时间减去s秒后的时间
date_add(d, interval n interval_type): 当前时间 间隔 n的 interval_type后的日期时间。interval_type可以是年、月、日、小时、分钟和秒的表示字符:
- YEAR 年
- MONTH 月
- DAY 日
- HOUR 小时
- MINUTE 分钟
- SECOND 秒
【注意】 n可以使用负数,表示之前的,如果是正数表示之后的间隔。
date_sub(d, interval n interval_type) : 当前时间 间隔 n之前的 interval_type的日期或时间。 等同于 data_add(d, interval -n i_type )。
6.1.4 修改数据库时区
修改my.cnf配置文件,在[mysqld]
下增加一行,内容如下:
default-time-zone = '+08:00'
重新启动mysql服务或容器,并进入容器中,通过
select now();
select @@time_zone;
6.2 字符函数
lower(s)/upper(s) 大小字母转化
concat(s1, s2,s3,....sn) : 可以将多个字符串连接成新的字符串
substr(s, start, [len]): 截取s的子字符串, 第一个字符的位置是1。
length(s) :获取字符串的长度(存储的字节大小)
char_length(s) : 获取字符个数
lpad(str, len, padstr): 如果str的长度不足len, 则使用padstr字符在左边填充。
rpad(str, len, padstr): ...
trim(str), ltrim(), rtrim(): 删除字符串中的(左、右)空格。
replace(str, old_str, new_str): 从str中将old_str替换为new_str
6.3 数值函数
abs(n): 绝对值
round(n): 四舍五入
ceil(n): 上行取整
floor(n): 下行取整
sin(n)
cos(n)
tan(n)
atan(n)
asin(n)
acos(n):
sqrt(n): 开平方根
power(n, m): n的m次方/幂
6.4 聚合函数
min()
max()
sum()
avg()
count()
6.5 加密函数
-- 不可逆:只能加密,不能解密
sha1(s): sha1 签名信息, 40位。
sha2(s, 0|224|256|384|512): 比sha1更强大的数据加密算法
md5(s): md5签名信息,32位。
aes_encrypt(str, key): 使用key密钥对str进行AES加密(对称加密-可逆的)
aes_decrypt(encrypt_str, key): 使用key密钥进encrypt_str密文解密。
-----
以上两者加密返回的都是十六进制数据,可以通过cast()函数转换数值
cast(hex_number as unsigned)
七、视图与索引
mysql索引是一种数据结构(btree, hash),在DQL语句的where条件中使用索引字段,则先从索引中查询,达到加速查询效果。
【重点】一张表中的索引个数,原则上没有限制,但索引不能过量创建,否则会影响DML语句的性能。
视图是一种高级子查询,即将查询语句存储,以便之后使用。视图同表一样,存在创建、修改和查询。
7.1 创建索引语法
create [ unique|fulltext|spatial ] index <索引名>
[ using <index_type> ]
on <表名>(<字段名> [ (length)] [ ASC | DESC ])
如,创建订单表的order_pay_time字段的索引,索引值按降序排,索引以以btree结构存储。
create index pay_time_index using btree
on tb_order(order_pay_time DESC);
【注意】另外创建索引还存在两种方式,一种基于alter table , 另一种基于create table。
-- 基于alter table
alter table <表名>
add index <索引名> <index_type> (<字段名> [(长度)] [ASC|DESC] )
| add [fulltext|spatial] index [index_name] ( <字段名> ,...)
| drop index <索引名>;
-- 基于create table
create table [if not exists] <表名>
(
... [字段描述],
index <索引名> [using <index_type> ] (<字段名> [(长度)] [ASC|DESC])
)
7.2 索引视图
创建索引之后,可以通过索引视图查询已存在的索引。
-- INNODB_INDEXES
-- innodb_tables
select a.index_id,a.name idx_name,a.type, b.name tab_name
from INNODB_INDEXES a
join innodb_tables b on (a.table_id=b.table_id)
where b.name like 'bookdb%';
7.3 创建索引案例
7.3.1 创建表时
-- 创建图书的分类表
drop table if exists tb_cate;
create table if not exists tb_cate(
id integer primary key auto_increment,
name varchar(20),
ord int,
constraint unique index name_uniq using btree (name)
);
7.3.2 修改表时
drop table if exists tb_cate;
create table if not exists tb_cate(
id integer primary key auto_increment,
name varchar(20),
ord int
);
alter table tb_cate
add fulltext index cate_name_index (name);
insert into tb_cate(name, ord) values
('教育类',1),
('科普类',2),
('军事类',3),
('生物类',4),
('文学类',5),
('历史类',6);
【注意】select语句避免使用*
而使得索引失效。
select id,name from tb_cate
where name like '%史%';
7.4 删除索引
7.4.1 删除主键和唯一索引
-- 删除唯一或普通的索引
alter table tb_cate
drop index name_uniq;
-- 如果主键是自增字段,在删除主键索引时,必须先去掉auto_increment约束。
alter table tb_cate
modify id integer;
alter table tb_cate
drop primary key;
7.4.2 删除其它索引
-- 查询相关的索引
select a.name idx_name, b.name tb_name
from information_schema.innodb_sys_indexes a
join information_schema.innodb_sys_tables b on (a.table_id = b.table_id);
【字典表】information_schema.tables|views|schema_privileges|table_privileges
alter table <表名>
drop index <索引名>
【注意】使用索引字段作为查询条件时, select关键字选择查询的字段避免使用 *
和发生全表查询的条件。
7.5 视图view
视图用于缓存复杂的sql语句, 查询的用法同表。
视图本质上是一张虚拟的表。
语法
create [or replace] view <视图名> as
<select 语句>
【注释】or replace
可选,表示更新视图。
【示例】
create or replace view account_top10 as
select * from account
where money >= 1000000;
-- 查看百万资产的大佬
select * from account_top10;
-- 创建视图 存储不同学员的总成绩
create or replace view total_score as
select sid, sum(score) as total
from SC
group by sid;
-- 查看每位学员的排名
select sid, rank() over(order by total)
from total_score;
-- 创建图书、作者和订单连接的视图
create or replace view book_author_order as
select c.name author_name, b.book_name, a.order_title,
a.order_price,
a.order_pay_time,
a.order_pay_status
from tb_order a
natural join tb_book b
natural join tb_author c;
删除视图
drop view [if exists] <视图名>;
查看视图的字典表
-- 从information_schema字典库中找到查看视图的字典表
select table_schema, table_name as view_name
from information_schema.views
where table_schema='bookdb';
【练习-50道练习之18题】
-- 查询各科成绩最高分、最低分和平均分及相关比例值
-- 以如下形式显示:课程ID,课程name,最高分,最低分,平均分,及格率(>=60),中等率(70-80),优良率(80-90),优秀率(>=90)
select c.cid,c.name,
max(score) max_score,
min(score) min_score,
round(avg(score),2) avg_score,
concat(round(sum(s1)*100/count(1)),'%') as '及格率',
concat(round(sum(s2)*100/count(1)), '%') as '中等率',
concat(round(sum(s3)*100/count(1)), '%') as '优良率',
concat(round(sum(s4)*100/count(1)),'%') as '优秀率'
from (
select cid,
score,
if(score >= 60, 1, 0) as s1,
if(score >=70 and score <80,1, 0) as s2,
if(score >=80 and score <90, 1, 0 ) as s3,
if(score >=90,1, 0) as s4
from SC
) A
natural join Course c
group by c.cid,c.name;
【扩展-mysql数据备份与恢复】
-- mysqldump 命令备份数据到 sql脚本中
mysqldump -uroot -proot sctc > /etc/mysql/sql/sctc.sql
-- 恢复数据: 1. 创建并打开数据库 2. source /etc/mysql/sql/sctc.sql
八、mysql编程语句
官方文档: https://dev.mysql.com/doc/refman/8.0/en/create-procedure.html
8.1 函数
8.1.1 创建函数
create [definer=user[@host] ] function <函数名> ( [IN |OUT|INOUT] <参数名> <参数类型>, ...)
returns <函数返回数据的类型>
[comment 注释] [deterministic]
<函数的功能>;
函数中的功能语句,一般是一个表达式。IN
表示接收外部传入的值; OUT
表示功能语句中将结果输出; INOUT
即接收外部传入的值,同时在功能语句中将结果输出。默认的模式为IN
。
如, 定义百分比的函数,给定小数点的值,返回ff.ff%
格式的结果
create function pct_format(val float) returns varchar(10) deterministic return concat(round(val * 100), '%');
-- 创建一个加密的函数, s是明文,flag支持0->md5, 1->sha1, 2->sha2(s, 512), other-> 原文
create function crypt(s varchar(255), flag int)
returns varchar(255)
deterministic return if(flag=0, md5(s), if(flag=1, sha1(s), if(flag=2, sha2(s, 512), s)));
-- 创建可逆加密解密函数, flag为0时加密, 为1时解密
-- 只针对数值型加密与解密
create function aes(s varchar(255), k varchar(255), flag int)
returns varchar(255) deterministic return if(flag=0,lower(hex(aes_encrypt(s, k))),cast(aes_decrypt(unhex(s), k) as char));
8.1.2 使用函数
-- 使用pct_format()函数
select c.cid,c.name,
max(score) max_score,
min(score) min_score,
round(avg(score),2) avg_score,
pct_format(sum(s1)/count(1)) as '及格率',
pct_format(sum(s2)/count(1)) as '中等率',
pct_format(sum(s3)/count(1)) as '优良率',
pct_format(sum(s4)/count(1)) as '优秀率'
from (
select cid,
score,
if(score >= 60, 1, 0) as s1,
if(score >=70 and score <80,1, 0) as s2,
if(score >=80 and score <90, 1, 0 ) as s3,
if(score >=90,1, 0) as s4
from SC
) A
natural join Course c
group by c.cid,c.name;
输出结果:
+------+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| cid | name | max_score | min_score | avg_score | 及格率 | 中等率 | 优良率 | 优秀率 |
+------+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 01 | 语文 | 80.0 | 31.0 | 64.50 | 67% | 33% | 33% | 0% |
| 02 | 数学 | 90.0 | 30.0 | 72.67 | 83% | 0% | 50% | 17% |
| 03 | 英语 | 99.0 | 20.0 | 68.50 | 67% | 0% | 33% | 33% |
+------+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
-- 使用加密函数
select crypt('123', 0);
mysql> select aes(123, 'abc', 0);
+----------------------------------+
| aes(123, 'abc', 0) |
+----------------------------------+
| a4bf9afce727dbd2805393a86a24096c |
+----------------------------------+
1 row in set (0.00 sec)
mysql> select aes('a4bf9afce727dbd2805393a86a24096c','abc',1);
+-------------------------------------------------+
| aes('a4bf9afce727dbd2805393a86a24096c','abc',1) |
+-------------------------------------------------+
| 123 |
+-------------------------------------------------+
1 row in set (0.00 sec)
【待解决的问题:aes字符串加密之后的解密】
解决方法:
cast(0xababab as char) 将二进制数据转化为字符串
其它数据转化:
cast('1991-10-12' as date) 将日期格式的字符串转化为日期类型
cast(0xabfe as signed | unsigned ) 将二进制转化为有负号的数值
cast('19.88' as float) 将数据转化为float小数类型
cast(0x123 as unsigned) 将数据转化为无负号数值
8.1.3 删除函数
drop function [if exists] <函数名>;
drop function if exists crypt;
8.2 存储过程
存储过程可以实现比较复杂业务逻辑的SQL语句,SQL语句可以是DML或DQL语句。
8.2.1 创建存储过程
CREATE [DEFINER = user] procedure <存储过程名> ([proc_parameter[,...]])
[characteristic ...]
begin
<存储过程体>
end;
characteristic: {
COMMENT 'string'
| LANGUAGE SQL
| [NOT] DETERMINISTIC
| { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
}
-- 定义存储过程,实现计算某一学生的总成绩和成绩级别(优 >=240,良 >=200,及格>=180和不及格)
-- select * into @rowvar from xxx
-- select id,name into @id,@name from xxx
delimiter &&
create procedure stu_total_level
(sid varchar(50), out sum_score float, out level varchar(10))
begin
select sum(score) ,
case when sum(score)>=240 then '优'
when sum(score)>=200 then '良'
when sum(score)>=180 then '及格'
else '不及格' end into sum_score, level
from SC
group by sid;
end &&
delimiter ;
-- delimiter 声明SQL语句结束符
delimiter &&
drop procedure if exists stu_total_level &&
create procedure stu_total_level
(sid varchar(50))
begin
select sum(score) as total_score,
case when sum(score)>=240 then '优'
when sum(score)>=200 then '良'
when sum(score)>=180 then '及格'
else '不及格' end as level
from SC
group by sid;
end &&
delimiter ;
8.2.2 调用存储过程
使用call调用存储过程,如果存储过程需要变量输出,则直接使用
@变量名
声明
mysql> call stu_total_level('01', @score, @level);
mysql> select @score, @level;
mysql> call stu_total_level('01')
8.2.3 删除存储过程
drop procedure if exists <存储过程名>;
8.3 触发器
trigger存储器是基于行级事件,监测发生或改变的数据。
如,删除用户信息时,在删除之前可以将数据存储到日志文件中(备份)。
8.3.1 创建触发器
CREATE
[DEFINER = user]
TRIGGER trigger_name
trigger_time trigger_event
ON tbl_name FOR EACH ROW
[trigger_order]
trigger_body
trigger_time: { BEFORE | AFTER }
trigger_event: { INSERT | UPDATE | DELETE }
trigger_order: { FOLLOWS | PRECEDES } other_trigger_name
说明: 在触发器的body中,可以使用NEW
表示插入或更新中的数据,OLD
表示之前的数据。
-- 定义tb_app_user表的触发器,监听用户删除的事件,事件发生之前将数据写入到注销用户表中。
-- create table tb_app_user_del select * from tb_app_user where 1>2;
delimiter &&
create trigger app_user_del_copy before delete
on tb_app_user for each row
begin
-- 即将删除的数据 OLD写入到用户注销表中
insert into tb_app_user_del values (OLD.user_id, OLD.phone, '', OLD.nick_name);
end &&
delimiter ;
8.3.2 删除触发器
drop trigger if exists <触发器名>;
drop trigger if exists app_user_del_copy;
九、用户与权限
9.1 数据库用户
数据库的管理用户信息存储在mysql.user表中。
一般用户信息包含user用户名、host主机、authentication_string 口令等。
9.1.1 创建用户
create user <user>@<host>
identified [with mysql_native_password] by <口令>
[password expire [default | never | interval N day] ]
[account lock|unlock]
如, 创建disen用户,用于管理studb数据库,口令插件使用mysql_native_password
,口令设置为disen
,此用户可以在远程的任何机器中连接数据库。
create user 'disen'@'%'
identified with mysql_native_password by 'disen';
说明: 用户创建成功后,可以远程登录 information_schema
库,但没有自己管理的数据库。在没有授权之前,不能创建或管理任何对象。
编写一个python3的脚本,测试数据库的连接
from sys import argv
# 从命令行中读取参数
# 命令行中的参数位置 host port db user pwd
host,port,db,user,pwd = argv[1:]
from pymysql import Connect
conn = Connect(host=host,port=int(port),db=db,user=user,passwd=pwd,charset='utf8')
print('--conn OK---')
如,脚本名为test_conn_mysql.py
,运行脚本如下:
python3 test_conn_mysql.py 127.0.0.1 3307 information_schema disen disen
9.1.2 修改用户
alter user [if exists] <user>@<host>
identified [with mysql_native_password] by <口令>
[password expire [default | never | interval N day] ]
[account lock|unlock]
如,锁定disen用户
alter user 'disen'@'%' account lock;
如,修改disen用户的口令为disen123, 并解锁。
alter user 'disen'@'%' identified with mysql_native_password by 'disen123' account unlock;
9.1.3 删除用户
drop user [if exists] <user>@<host>
如,删除disen用户
drop user 'disen'@'%';
9.2 权限
mysql中存在非常多的权限,以便于为不同的管理员提供不同的权限操作。
9.2.1 管理员权限
参考:https://dev.mysql.com/doc/refman/8.0/en/grant.html
ALL 具有所有权限
CREATE TABLESPACE 创建表空间
CREATE USER 创建用户
FILE 输出文件和加载文件
PROCESS 访问线程信息的权限
RELOAD 重载权限,如具有flush相关的功能(flush-privileges、flush-status)
REPLICATION CLIENT 客户端备份权限
REPLICATION SLAVE 主从备份权限
SHOW DATABASES 显示数据库信息的权限
SHOW VIEW 显示视图的权限
SHUTDOWN 关闭或重启服务的权限,针对mysqladmin shutdown|restart命令。
SUPER 具有超级用户权限,如设置全局变量等
TRIGGER 具有触发器相关权限
UPDATE 具有更新表记录的权限
SELECT 具有查询表的权限
INSERT 具有插入表权限
INDEX 具有索引相关的管理权限
DELETE 具有删除表记录的权限
CREATE View 具有创建视图的权限
CREATE 具有创建数据库和表的权限
DROP 具有删除对象的权限
ALTER 具有修改对象的权限
9.2.2 授权
grant <权限> on <数据库>[.<对象>] to <用户名>@<主机>
所有权限表示: all 或 all privileges
如,授权disen查看和修改studb库的Student表和Teacher
grant select,update on studb.Student to 'disen'@'%';
grant select,update on studb.Teacher to disen;
如,授disen用户在studb库下创建对象的权限
grant create on studb.* to disen;
如,授disen用户在studb库下删除和更新A表的权限
grant drop,alter on studb.A to disen;
9.2.3 查询权限
show grants可以查看某一个用户下所有权限。
show grants;
show grants for <user>@<host> ;
show grants current_user();
9.2.4 撤消权限
参考:https://dev.mysql.com/doc/refman/8.0/en/revoke.html
REVOKE
priv_type [(column_list)]
[, priv_type [(column_list)]] ...
ON [object_type] priv_level
FROM user_or_role [, user_or_role] ...
如,撤消disen用户在studb库下删除A表的权限
revoke drop on studb.A from disen;
十、SQL优化
参考:https://dev.mysql.com/doc/refman/8.0/en/optimization.html
10.1 优化SQL语句
10.2 explain优化查询
十一、Python交互MySQL
Python和mysql进行交互的库: pymysql, mysqlclient, sqlalchemy
11.1 pymysql的curd操作
pip安装: pip install pymysql
11.1.1 连接数据库
from pymysql import Connect
from pymysql.cursors import Cursor, DictCursor
# Cursor: 查询的结果的每一条记录是以tuple方式显示的,没有列名
# DictCursor: 查询的结果每一记录是dict方式显示的,key列名,value数据
conn = Connect(host='10.36.174.50', port=3307,
user='book', passwd='book', db='bookdb',
charset='utf8',
cursorclass=DictCursor)
print('-OK-')
conn对象具有以下功能:
- .cursor(cursor=Cursor|DictCursor|None) 打开游标,返回一个Cursor实例对象
- .commit() 提交事务
- .rollback() 回滚事务
- .begin() 开启事务
- .select_db(db=数据库名) 选择数据库
- .close() 关闭连接
11.1.2 游标对象操作
游标类: pymysql.cursors.Cursor/DictCursor
游标实例职责: 执行有效的SQL语句,包含DDL/DML和DQL等语句。
11.1.2.1 核心方法
__enter__
和__exit__
支持with关键字的使用- execute(sql, args=None|( )|{ } ) 执行sql语句,sql语句中可以使用
%s
或%(name)s
占位符,使用%s
时,args是以tuple元组的方式从左到右依次指定相关数据。如果使用%(name)s
时,args是以dict字典的方式指定不同name的value值。
# 使用%s占位符
c.execute('select %s,%s from %s', args=('id', 'name', 'tb_book'))
# 使用%(name)s 占位符
sql = 'insert into tb_author(name,phone) values(%(name)s, %(phone)s)'
c.execute(sql, args={'name':'disen', 'phone':'16617111129'})
- fetchone() 如果执行的语句是DQL, 此方法只获取第一条记录的数据
- fetchall() 如果执行的语句是DQL, 此方法获取所有查询记录的数据,返回一个list对象。
- callproc(name, args=()) 调用存储过程,如果存储过程有参数时,使用args传参
with conn.cursor() as c:
# 调用存储过程
# root用户为book用户授权: grant all on studb.* to book;
c.callproc('studb.stu_total_level', args=('01',))
print(c.fetchall())
- close() 关闭游标
11.1.2.2 核心属性
- rowcount 如果执行的是DML语句,此属性则获取影响的行数。
with conn.cursor() as c:
ret = c.execute('delete from tb_order')
print('删除语句的结果:', ret, '影响行数:', c.rowcount)
11.1.3 事务处理
pymysql支持事务处理,默认情况下,不提交事务。
一般在确认所有的DML操作结束后,一次性提交事务。
# 正常的DML语句执行后,提交事务
with conn.cursor() as c:
insert_sql = 'insert into tb_author(name, phone,city) values(%s,%s,%s)'
ret = c.execute(insert_sql, args=('九江','18999991100','南昌'))
if ret > 0:
conn.commit() # 手动提交事务
print('插入数据成功')
else:
print('插入数据失败')
# 如果DML语句执行异常,则回滚事务 rollback
with conn.cursor() as c:
insert_sql = 'insert into tb_author(name, phone,city1) values(%s,%s,%s)'
try:
ret = c.execute(insert_sql, args=('九江','18999991100','南昌'))
if ret > 0:
conn.commit() # 手动提交事务
print('插入数据成功')
else:
print('插入数据失败')
except Exception as e:
print('sql语句异常:', e)
conn.rollback()
11.2 封装DB类
- 数据库连接类 在一个连接实例中,可以执行n次数据库操作。
- 数据库操作类,具体针对哪些对象(表)进行DML操作。
11.2.1 封装数据库连接类
from pymysql import Connect
from pymysql.cursors import DictCursor
class Conn():
def __init__(self, **config):
config.setdefault('charset','utf8')
config.setdefault('cursorclass', DictCursor)
try:
# 创建数据库的连接
self.conn = Connect(**config)
except:
raise Exception(f'初始化的参数不正确,正确参数名:host,port,user,passwd,db,charset(utf8); 实际参数:{config}')
def __enter__(self):
return self.conn.cursor()
def __exit__(self, except_type, except_val, except_tb):
# 如果存在异常,except_type是某一个Exception类
if except_type:
self.conn.rollback()
print('数据库操作出现异常:', except_val)
else: # 不存在异常,即except_type为None
self.conn.commit()
# 如果上下文的语句块出现异常之后,返回True表示,自处理,不向解释器抛出
# 返回False,表示异常继续向解释器抛出
return True
def close(self):
self.conn.close()
用法
config = {
'host': '10.36.174.50',
'port':3307,
'user':'book',
'passwd': 'book',
'db': 'bookdb'
}
with Conn(**config) as cursor:
cursor.execute('select * from tb_author')
print(cursor.fetchall())
# 跳出conn对象的上下文管理器 ,会调用它的__exit__()方法
conn = Conn(**config)
with conn as cursor:
cursor.execute('select * from tb_author')
print(*cursor.fetchall(), sep='\n')
with conn as c:
c.execute('select * from tb_order')
print(*c.fetchall(), sep='\n')
conn.close()
11.2.2 封装数据库操作类
操作类主要支持CURD(插入数据、更新数据、查询数据和删除数据),针对不同表的操作,尝试支持多表连接查询。
class DB():
def __init__(self, conn):
self.conn = conn
def save(self,table, **item) -> bool:
sql = 'insert into %s(%s) values(%s)'
cols = ','.join(item)
vals = ','.join([f'%({k})s' for k in item]) # %(name)s,%(age)s
with self.conn as c:
return c.execute(sql % (table, cols, vals), args=item) > 0
def update(self, table, pk='id', **item):
sql = f'update %s set %s where {pk}=%s'
sets = ','.join([f'{k}=%({k})s' for k in item if k!=pk])
with self.conn as c:
return c.execute(sql % (table, sets, f'%({pk})s'), args=item) > 0
def delete(self, table, pk_val, pk='id'):
sql = f'delete from {table} where {pk}=%s'
with self.conn as c:
return c.execute(sql, args=(pk_val,)) > 0
def query(self, table, *columns, where=None, args=None):
sql = 'select %s from %s ' % (','.join(columns) if columns else '*', table)
if where:
sql += where
with self.conn as c:
c.execute(sql, args=args)
return c.fetchall()
用法:
db = DB(conn)
print(*db.query('tb_book','book_id', 'book_name'), sep='\n')
print('--西安的作者---')
print(*db.query('tb_author','author_id', 'name', 'phone',
where='where city=%s', args=('西安',)), sep='\n')
conn.close()
author={
'name': '李哲',
'city': '咸阳',
'phone': '18325563596',
'join_date': '2011-09-09'
}
if db.save('tb_author',**author):
print('--新增作者成功--')
else:
print('--新增作者失败-')
update_author={'author_id': 112, 'name':'小哲哲'}
db.update('tb_author',pk='author_id',**update_author)
print('--西安的作者---')
print(*db.query('tb_author','author_id', 'name', 'phone',
where='where city=%s', args=('咸阳',)), sep='\n')
11.3 基于type元类设计ORM
11.3.1 type类
type类是元类的最基本的父类,用于创建类对象的。即元类是创建类的类。
Python中有两大超类(基类): object, type
用法
# 创建员工类,包含员工的姓名name、工龄years和基本薪资salary, 同时存在加薪add_salary和减薪sub_salary两个方法。
# 一般的写法
class Person:
def __init__(self, name, years, salary):
self.name = name
self.years=years
self.salary = salary
def add_salary(self, salary):
self.salary += salary
def sub_salary(self, salary):
self.salary -= salary
# 使用type创建类
# type(类名, 父类的元组, 类属性attrs字典) 返回类的对象
def add_salary(self, salary):
self.salary += salary
def sub_salary(self, salary):
self.salary -= salary
# 定义Person
Person = type('Person', (), {'age':10})
# 定义工人类,继承Person类
Worker = type('Worker', (Person,), {'name': '', 'years': 1, 'salary': 0,
'add_salary': add_salary,
'sub_salary': sub_salary
})
w1 = Worker()
w1.name = 'disen'
w1.years = 10
w1.age = 20
w1.salary = 20000
w1.add_salary(1000)
print(w1.salary, w1.name, w1.years, w1.age)
w2 = Worker()
print(w2.name, w2.years, w2.salary, w2.age)
11.3.2 自定义type元类
因为元类是创建类的类,在class 语句中定义类时,可以通过metaclass指定创建类对象的元类。
用法:
# 定义一个元类
class PersonMeta(type):
pass
# 指定哪一个元类,来创建当前类
class Person(metaclass=PersonMeta):
pass
# 定义一个元类
class PersonMeta(type):
def __new__(cls, cls_name, bases, attrs):
print(cls_name, bases, attrs)
return type(cls_name, bases, attrs)
# 指定哪一个元类,来创建当前类
class Person(object,metaclass=PersonMeta):
age = 20
sex = '男'
def __init__(self, name):
self.name = name
def hi(self):
print(self.name)
执行以上脚本时,创建Person对象,则PersonMeta的__new__()
方法创建的。
11.3.3 ORM设计思想
ORM( Object Relationship Mapping , 对象关系映射) 是一种简化非数据库人员操作数据库思想,即将类或对象与关系型数据库的表或记录进行映射,对类的创建生成对数据库的表创建的SQL语句,对类实例的操作即是对数据库表的某一条记录操作。
类 -> 表结构
类实例 -> 表的某一条记录
从表中查询的结果 映射成 类实例的列表。
11.3.4 设计ORM相关类
如果想让某一类与表映射,必须存在类的属性与表的字段一一对应。
需要定义一个模型类的元类,获取类中声明的属性,方便之后生成相关的SQL语句。
需要定义在一个模型类的父类,确定对模型的操作(CURD) 从而生成相关的SQL语句并执行。
具体的模型类,声明类对应的表(名)、属性的字段(名)等。
需要定义一个字段类,方便生成不同的数据类型的SQL语句。
11.3.4.1 设计模型类的元类
class ModelMeta(type):
def __new__(cls, name, bases, attrs):
# 提取模型类的成员属性(Field字段类)
# 创建类对象的顺序: 先父类,后当前类
print('-->', name, bases, attrs)
if name == 'BaseModel':
return super().__new__(cls, name, bases, attrs)
# 具体模型类的对象
# 收集所有Field字段类的实例--字段实例与表的字段对应
fields = {attr_name: field for attr_name, field in attrs.items() if isinstance(field, Field)}
# 设置字段的对应的列名
for attr_name, field in fields.items():
if field.column is None:
field.column = attr_name.lower()
table_name = attrs['__table__'] if '__table__' in attrs else name.lower()
# 创建模型类对象时,动态添加两个属性
# table 表名
# fields 字段
attrs['table'] = table_name
attrs['fields'] = fields
return super().__new__(cls, name, bases, attrs)
11.3.4.2 设计模型类和字段类
在BaseModel类中,根据具体模型类的属性生成sql语句并执行。
class BaseModel(metaclass=ModelMeta):
def __init__(self,db_conn,**attrs):
# 支持初始化的方式实例化模型类实例,即向数据表中插入数据
self.db_conn = db_conn
def create(self): # 创建表
# 根据模型类的描述:字段,表名, 生成create table 语句
# 通过数据库的连接执行DDL语句
sql = 'create table %s(%s)'
cols = ','.join([field.to_db() for field in self.fields.values()])
ddl_sql = sql % (self.table, cols)
with self.db_conn as c:
c.execute(ddl_sql)
print('sql执行成功: ', ddl_sql)
def save(self): # 插入或更新数据
sql = 'insert into %s(%s) values(%s)'
cols = ','.join([field.column for field in self.fields.values()
if not field.auto_increment])
vals = ','.join([ f'%({attr_name})s'
for attr_name,field in self.fields.items()
if not field.auto_increment])
item = { attr_name: getattr(self, attr_name)
for attr_name,field in self.fields.items()
if not field.auto_increment}
with self.db_conn as c:
c.execute(sql % (self.table, cols, vals), args=item)
print('新增数据成功!')
def delete(self): # 删除数据
pass
def get_pk_column(self):
for attr, field in self.fields.items():
if field.primary:
return attr, field.column
def get(self, pk):
# 根据主键值获取一条记录并返回类实例
with self.db_conn as c:
c.execute(f'select * from {self.table} where {self.get_pk_column()[1]}=%s',
args=(pk,))
row = c.fetchone()
return self.__to_instance(row)
def __to_instance(self, row): # 将某一行数据查询的数据转化为模型类实例
model_instance = self.__class__(self.db_conn)
for attr_name, field in self.fields.items():
setattr(model_instance, attr_name, row[field.column])
return model_instance
def all(self):
# 查询所有数据
instances = []
with self.db_conn as c:
c.execute(f'select * from {self.table}')
for row in c.fetchall():
instances.append(self.__to_instance(row))
return instances
因为数据库中字段类型比较多,不同的字段提供不同的SQL关键字,如支持varchar、char、float、timestamp等。
# 字段类的父类, 支持某一个属性与表的字段名映射
# 支持数据库中支持的约束: primary key, unique, not null
class Field():
def __init__(self, column=None, primary=False, unique=False, auto_increment=False, null=True):
self.column = column
self.primary=primary
self.unique=unique
self.auto_increment = auto_increment
self.null = null
def dbtype(self):
raise Exception('子类必须重写此方法')
def to_db(self):
return f'{self.column} {self.dbtype()} '\
+ ('primary key ' if self.primary else '')\
+ (' unique ' if self.unique else '') \
+ (' auto_increment ' if self.auto_increment else '') \
+ ('' if self.null else ' not null ')
class CharField(Field):
def __init__(self, max_length, **attrs):
super().__init__(**attrs)
self.max_length=max_length
def dbtype(self):
return f'char({self.max_length})'
class VarcharField(CharField):
def dbtype(self):
return f'varchar({self.max_length})'
class IntegerField(Field):
def dbtype(self):
return 'integer'
class FloatField(Field):
def dbtype(self):
return 'float'
class DecimalField(Field):
def __init__(self, length, precise, **attrs):
super().__init__(**attrs)
self.length=length
self.precise = precise
def dbtype(self):
return f'decimal({self.length}, {self.precise})'
class DateTimeField(Field):
def dbtype(self):
return f'timestamp'
class DateField(Field):
def dbtype(self):
return f'date'
11.3.4.3 具体的模型类
具体描述与数据库表的模型类,继承BaseModel,同时,类属性或字段使用Field的子类。
class OrderReply(BaseModel):
__table__ = 'tb_order_reply' # 类对应的表名
id = IntegerField(primary=True, auto_increment=True)
order_id = IntegerField()
content = VarcharField(max_length=200)
from db_ import DB, Conn
config = {
'host': '10.36.174.50',
'port': 3307,
'user': 'book',
'passwd': 'book',
'db': 'bookdb'
}
conn = Conn(**config)
# 创建表
OrderReply(conn).create()
# 插入数据
reply1 = OrderReply(conn)
reply1.order_id=1
reply1.content='强烈推荐,丰常好!客服服务态度'
reply1.save()
# 查询数据
print(reply1.all())
print(reply1.get(1))
# 删除数据
r1 = OrderReply(conn).get(1)
r1.delete()
# 更新数据
r2= OrderReply(conn).get(2)
r2.order_id=3
r2.save()
# 定义与tb_author表对应的模型类
class Author(BaseModel):
__table__ = 'tb_author'
id = IntegerField(column='author_id', primary=True, auto_increment=True)
name = VarcharField(max_length=50)
phone = CharField(max_length=11, unique=True)
join_date = DateField()
city = VarcharField(20)
# 查询所有作者信息
authors = Author(conn).all()
for author in authors:
print(author.id, author.name, author.city, author.phone)
11.4 sqlalchemy的应用
文档: https://www.sqlalchemy.org/
教程: https://docs.sqlalchemy.org/en/14/tutorial/index.html
sqlalchemy是Python实现ORM设计思想的第三库,支持大多数的关系型数据库,如mysql、maridb、postgresql、 sqlserver、oracle、sqlite3等数据。但是需要不同数据库操作的driver驱动,如mysql对应的驱动库是pymysql或mysqlclient。
11.4.1 安装与连接
11.4.1.1 安装
通过pip库安装:
pip install sqlalchemy
11.4.1.2 普通连接
普通连接,适合原生的SQL语句的执行。
from sqlalchemy import create_engine
# 1. 创建引擎实例
engine = create_engine('mysql+pymysql://book:book@10.36.174.50:3307/bookdb',
encoding='utf8', echo=True)
# 2. 打开数据库的连接
conn = engine.connect()
# 3. 执行sql语句,返回一个游标对象
# sqlalchemy.engine.cursor.LegacyCursorResult
cursor = conn.execute('select * from tb_author')
print(type(cursor), cursor)
# 4.获取游标的数据
ret = cursor.fetchall()
print(*ret, sep='\n')
# 5. 执行dml语句
insert_sql = 'insert into tb_author(name, city, phone) values(%s, %s, %s)'
ret = conn.execute(insert_sql,'小李子2','西安','19818818119')
print('插入成功', ret.rowcount)
insert_sql = 'insert into tb_author(name, city, phone) ' \
'values(%(name)s, %(city)s, %(phone)s)'
ret = conn.execute(insert_sql,name='小李子12',city='西安',phone='19118818119')
print('插入成功', ret.rowcount)
11.4.1.3 会话连接
会话连接适合ORM模型类的操作, 也可以执行原生的SQL语句。
from sqlalchemy import create_engine, text
from sqlalchemy.orm import Session
# 1. 创建引擎实例
engine = create_engine('mysql+pymysql://book:book@10.36.174.50:3307/bookdb',
encoding='utf8', echo=True)
# 2.创建会话对象,使用with
# session = Session(engine)
with Session(engine) as s:
# 3. 会话中执行sql语句
ret = s.execute(text('select * from tb_book where book_id=:id').bindparams(id=2))
print(ret.one())
# 无论是connection连接还是session,在上下文中执行完之后, 则会自动回滚事务
# 如果执行了DML语句,则需要手动提交事务
s.commit()
11.4.2 设计模型类
11.4.2.1 MetaData设计表
from sqlalchemy import create_engine, MetaData,Table,Column,ForeignKey,Integer,String,Date,DateTime,Float,DECIMAL
from sqlalchemy.orm import Session
# 1. 创建引擎实例
engine = create_engine('mysql+pymysql://book:book@10.36.174.50:3307/bookdb',
encoding='utf8', echo=True)
# 2. 创建元数据实例,用于提取表定义类的属性
metadata = MetaData(engine)
# 3. 声明新表模型类
sys_user = Table('tb_sys_user', metadata,
Column('id', Integer, primary_key=True,autoincrement=True),
Column('name', String(30), unique=True))
sys_role = Table('tb_role', metadata,
Column('id', Integer, primary_key=True, autoincrement=True),
Column('name', String(20), comment='角色名称'),
Column('user_id', Integer, ForeignKey('tb_sys_user.id', ondelete='CASCADE')))
# 4. 执行DDL语句
metadata.create_all()
11.4.2.2 MetaData设计类
from sqlalchemy import create_engine, MetaData,Table,Column,ForeignKey,Integer,String,Date,DateTime,Float,DECIMAL
from sqlalchemy.orm import Session, relationship
from sqlalchemy.orm import declarative_base
# 1. 创建引擎实例
engine = create_engine('mysql+pymysql://book:book@10.36.174.50:3307/bookdb',
encoding='utf8', echo=True)
# 2. 创建元数据实例,用于提取表定义类的属性
metadata = MetaData(engine)
# 3.声明模型类的父类
BaseModel = declarative_base(metadata=metadata)
# 4. 声明具体的模型类
class SysRole(BaseModel):
__tablename__ = 'tb_role'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(20), unique=True)
# 如果引用的类是后声明的,则使用字符串,解析时来获取类信息
user = relationship('SysUser', back_populates='role')
def __repr__(self):
return f"<id={self.id}, name={self.name}>"
class SysUser(BaseModel):
__tablename__ = 'tb_sys_user'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(30), unique=True)
role_id = Column(Integer, ForeignKey('tb_role.id', ondelete='set null'))
role = relationship(SysRole, back_populates='user')
# 5. 创建所有表对象
metadata.create_all()
11.4.3 模型类的CURD操作
文档: https://docs.sqlalchemy.org/en/14/tutorial/orm_data_manipulation.html
ORM的宗旨是简化程序对SQL编写,通过类或类实例进行数据的操作,保存实例即向数据库插入记录,查询数据的结果封装成(模型)类实例的列表。
一般情况下,使用Session会话连接对象进行模型类的操作,包含查询、保存、更新、删除以及多表关联查询或操作。
11.4.3.1 保存数据
role1 = SysRole(name='超级系统管理员')
role2 = SysRole(name='商品管理员')
with Session(engine) as s:
# 单个实例保存: s.add(role1)
# 批量实例保存
s.add_all([role1, role2])
# print(s.new) # 未提交到数据库的实例数据
s.flush() # 执行以上的行为(向数据库发送DML操作)
s.commit()
print(role1, role2)
11.4.3.2 修改数据
修改某一实体数据,前提是先从数据库查询出来。
session = Session(engine)
# s指向的是session.__enter__()方法返回的实例对象
with session as s:
# 获取role的id为15的角色信息
role3 = s.get(SysRole, 15)
print(role3)
# 更新角色名为“系统管理员”
role3.name = '系统管理员'
# s.flush()
s.commit()
11.4.3.3 删除数据
删除数据之前,先查询到实体,然后通过session删除。
session = Session(engine, autocommit=True)
# 删除商品管理员角色 ,id为
with session as s:
r = s.get(SysRole, 16)
s.delete(r)
s.flush()
可以通过sqlalchemy提供的delete()快速构造delete
SQL语句
with session as s:
# 通过sqlalchemy.delete()快速删除
result = s.execute(delete(SysRole).where(SysRole.id==15))
print(result.rowcount)
11.4.3.4 查询数据
一般情况下,查询数据,可以使用原生的SQL, 也可以使用更加方便的select()来查询。
from sqlalchemy.engine import LegacyCursorResult
with session as s:
selector = select(SysRole)
raw_sql = 'select * from tb_role'
result: LegacyCursorResult = s.execute(selector)
rs = result.scalars() # .scalar_one() 转化为单个实例对象
for role in rs:
print(role)
说明: result.scalars()
是一个将行数据转化模型类实例的生成器。
【重点】使用Query进行查询数据
with session as s:
# 获取Query查询器
query = s.query(SysUser)
# 显示一条记录 first()或所有记录all()
print(query.first())
with session as s:
# 获取Query查询器
query = s.query(SysUser)
# .filter() 根据模型类的属性(Column类实例)查询的
# 关系的表示: ==,!=, >=, <=
# print(query.filter(SysUser.id<=2).all())
# 模糊查询:.like()
# print(query.filter(SysUser.name.like('%s%')).all())
print(query.filter(SysUser.name.contains('s')).all())
# in查询: .in_((v1, v2,...))
print(query.filter(SysUser.id.in_((1, 2))).all())
# sqlalchemy.and_()/or_()/not_() 表示多个查询条件的逻辑关系
print(query.filter(and_(SysUser.id>=2,
SysUser.name.contains('s'))).all())
# filter()支持带有参数名的条件字符串,然后使用.params()指定条件中的名称值
print(query.filter(text('name = :name')).params(name='disen').all())
# .filter_by() 根据当前模型类的属性进行简化的查询
# 使用了filter()或filter_by()之后,可以使用.all()和.one()
# filter_by()方法中,不能使用or_/and_/not_()逻辑关系函数
print(query.filter_by(id=3, name='boss').all())
# 排序 .desc()/.asc() 默认升序
# 分页 .limit(page_size) / .offset(start_row) start_row从0开始
print(query.order_by(SysUser.id).limit(2).offset(0).all())
# 聚合查询
from engine_ import session
from models import TbBook
from sqlalchemy import func
with session as s:
# 查询原价最高的数据
print(s.query(func.max(TbBook.src_price).label('max_price'),
func.avg(TbBook.src_price).label('mean_price')).filter_by(author_id=110).all())
11.4.3.5 关联数据
11.4.3.5.1 关联保存数据
with session as s:
r1 = s.get(SysRole, 1)
# 指定关联属性的实体(SysRole的实例)
u1 = SysUser(name='disen', role=r1)
s.add(u1)
s.commit()
11.4.3.5.2 关联删除数据
with session as s:
s.delete(s.get(SysRole, 1)) # 级联置空
s.commit()
11.4.3.5.3 关联更新数据
with session as s:
r2 = s.get(SysRole, 2)
u1 = s.get(SysUser,1)
u1.role = r2 # 指定用户1的角色为r2
s.commit()
11.4.3.5.4 关联查询数据
# 在一对多的关系中的一端(角色)
with session as s:
r2 = s.get(SysRole,2)
# 通过role实例的user反查询关联的用户实体
print(r2.user) # 显示所有用户实体(模型类的实例)
# 在多端查询(延迟查询: 用到时再查)
with session as s:
u1 = s.get(SysUser,1)
# 通过role实例的user反查询关联的用户实体
print(u1, '用户角色->', u1.role.name)
11.4.4 逆向生成模型类
可以将已存在的表,生成sqlalchemy的模型类。
安装python的第三方库: pip install sqlacodegen
生成模型类的命令:
sqlacodegen mysql+pymysql://book:book@10.36.174.50:3307/bookdb > models.py
单独创建engine_.py脚本, 内容如下:
from sqlalchemy import create_engine, MetaData
from sqlalchemy.orm import Session
from sqlalchemy.orm import declarative_base
# 1. 创建引擎实例
engine = create_engine('mysql+pymysql://book:book@10.36.174.50:3307/bookdb',
encoding='utf8', echo=True)
# 2. 创建元数据实例,用于提取表定义类的属性
metadata = MetaData(engine)
# 3.创建模型类的父类, 如果是低版本时,可以不用声明
Base = declarative_base(engine, metadata=metadata)
# 4. 创建Session
session = Session(engine)
测试生成的模型类是否可以使用:
from engine_ import session
from models import TbBook
with session as s:
books = s.query(TbBook).all()
for book in books:
print(book.book_id, book.book_name, book.src_price, book.act_price)
在生成的模型类中,需要导入自定义的Base父类, 将原有的Base删除或更改, 主要修改内容如下:
# coding: utf-8
from sqlalchemy import CHAR, Column, DECIMAL, Date, Float, ForeignKey, Integer, String, TIMESTAMP, Table, Text, VARBINARY, text
from sqlalchemy.orm import relationship
# 导入的部分(自定义模块)
from engine_ import Base
class TbAppUser(Base):
__tablename__ = 'tb_app_user'
user_id = Column(Integer, primary_key=True)
phone = Column(CHAR(11), unique=True)
authentication_string = Column(String(255), nullable=False)
nick_name = Column(String(50))
class TbAuthor(Base):
__tablename__ = 'tb_author'
author_id = Column(Integer, primary_key=True)
name = Column(String(20))
city = Column(String(20))
phone = Column(CHAR(11))
join_date = Column(Date)
class TbCate(Base):
__tablename__ = 'tb_cate'
id = Column(Integer, primary_key=True)
name = Column(String(20), index=True)
ord = Column(Integer)
class TbOrder(Base):
__tablename__ = 'tb_order'
order_id = Column(CHAR(32), primary_key=True)
order_title = Column(String(50))
book_id = Column(Integer)
order_price = Column(DECIMAL(10, 2))
order_pay_status = Column(Integer, server_default=text("'0'"))
order_pay_time = Column(TIMESTAMP, index=True)
user_id = Column(Integer)
class TbOrderReply(Base):
__tablename__ = 'tb_order_reply'
reply_id = Column(Integer, primary_key=True)
order_id = Column(Integer)
content = Column(String(200))
class TbRole(Base):
__tablename__ = 'tb_role'
id = Column(Integer, primary_key=True)
name = Column(String(20), unique=True)
class TbBook(Base):
__tablename__ = 'tb_book'
book_id = Column(Integer, primary_key=True)
book_name = Column(String(50), unique=True)
summary = Column(Text)
src_price = Column(DECIMAL(5, 2))
act_price = Column(DECIMAL(5, 2))
cover_url = Column(String(100))
author_id = Column(ForeignKey('tb_author.author_id', ondelete='SET NULL'), index=True)
author = relationship('TbAuthor')
class TbSysUser(Base):
__tablename__ = 'tb_sys_user'
id = Column(Integer, primary_key=True)
name = Column(String(30), unique=True)
role_id = Column(ForeignKey('tb_role.id', ondelete='SET NULL'), index=True)
role = relationship('TbRole')
11.4.4 逆向生成模型类
可以将已存在的表,生成sqlalchemy的模型类。
安装python的第三方库: pip install sqlacodegen
生成模型类的命令:
sqlacodegen mysql+pymysql://book:book@10.36.174.50:3307/bookdb > models.py
单独创建engine_.py脚本, 内容如下:
from sqlalchemy import create_engine, MetaData
from sqlalchemy.orm import Session
from sqlalchemy.orm import declarative_base
# 1. 创建引擎实例
engine = create_engine('mysql+pymysql://book:book@10.36.174.50:3307/bookdb',
encoding='utf8', echo=True)
# 2. 创建元数据实例,用于提取表定义类的属性
metadata = MetaData(engine)
# 3.创建模型类的父类, 如果是低版本时,可以不用声明
Base = declarative_base(engine, metadata=metadata)
# 4. 创建Session
session = Session(engine)
测试生成的模型类是否可以使用:
from engine_ import session
from models import TbBook
with session as s:
books = s.query(TbBook).all()
for book in books:
print(book.book_id, book.book_name, book.src_price, book.act_price)
在生成的模型类中,需要导入自定义的Base父类, 将原有的Base删除或更改, 主要修改内容如下:
# coding: utf-8
from sqlalchemy import CHAR, Column, DECIMAL, Date, Float, ForeignKey, Integer, String, TIMESTAMP, Table, Text, VARBINARY, text
from sqlalchemy.orm import relationship
# 导入的部分(自定义模块)
from engine_ import Base
class TbAppUser(Base):
__tablename__ = 'tb_app_user'
user_id = Column(Integer, primary_key=True)
phone = Column(CHAR(11), unique=True)
authentication_string = Column(String(255), nullable=False)
nick_name = Column(String(50))
class TbAuthor(Base):
__tablename__ = 'tb_author'
author_id = Column(Integer, primary_key=True)
name = Column(String(20))
city = Column(String(20))
phone = Column(CHAR(11))
join_date = Column(Date)
class TbCate(Base):
__tablename__ = 'tb_cate'
id = Column(Integer, primary_key=True)
name = Column(String(20), index=True)
ord = Column(Integer)
class TbOrder(Base):
__tablename__ = 'tb_order'
order_id = Column(CHAR(32), primary_key=True)
order_title = Column(String(50))
book_id = Column(Integer)
order_price = Column(DECIMAL(10, 2))
order_pay_status = Column(Integer, server_default=text("'0'"))
order_pay_time = Column(TIMESTAMP, index=True)
user_id = Column(Integer)
class TbOrderReply(Base):
__tablename__ = 'tb_order_reply'
reply_id = Column(Integer, primary_key=True)
order_id = Column(Integer)
content = Column(String(200))
class TbRole(Base):
__tablename__ = 'tb_role'
id = Column(Integer, primary_key=True)
name = Column(String(20), unique=True)
class TbBook(Base):
__tablename__ = 'tb_book'
book_id = Column(Integer, primary_key=True)
book_name = Column(String(50), unique=True)
summary = Column(Text)
src_price = Column(DECIMAL(5, 2))
act_price = Column(DECIMAL(5, 2))
cover_url = Column(String(100))
author_id = Column(ForeignKey('tb_author.author_id', ondelete='SET NULL'), index=True)
author = relationship('TbAuthor')
class TbSysUser(Base):
__tablename__ = 'tb_sys_user'
id = Column(Integer, primary_key=True)
name = Column(String(30), unique=True)
role_id = Column(ForeignKey('tb_role.id', ondelete='SET NULL'), index=True)
role = relationship('TbRole')