1. Introduction
本文属于新闻推荐实战—数据层—构建物料池之
MySQL
。MySQL
数据库在该项目中会用来存储结构化的数据(用户、新闻特征),作为算法工程师需要了解常用的
MySQL
语法(比如增删改查,排序等),因为在实际的工作经常会用来统计相关数据或者抽取相关特征。本着这个目的,本文对
MySQL
常见的语法及 Python
操作
MySQL
进行了总结,方便大家快速了解。
2. MySQL in a nutshell
2.1 MySQL简介
MySQL
是一个关系型数据库管理系统,由瑞典 MySQL AB
公司开发,属于 Oracle
旗下产品。MySQL
是最流行的关系型数据库管理系统之一,在 WEB
应用方面,MySQL是最好的 RDBMS
(Relational Database
Management System,关系数据库管理系统) 应用软件之一。
MySQL
在过去由于性能高、成本低、可靠性好,已经成为最流行的开源数据库,因此被广泛地应用在
Internet
上的中小型网站中。随着 MySQL
的不断成熟,它也逐渐用于更多大规模网站和应用,比如维基百科、Google
和 Facebook
等网站。非常流行的开源软件组合
LAMP
中的 M
指的就是 MySQL
。
2.2 Ubuntu下安装MySQL
安装教程是在Ubuntu20.04
下进行的,安装的
MySQL
版本为 8.0.27
。
2.2.1 安装
1 | sudo apt install mysql-server mysql-client |
注:在输入之后可能会出现如下报错:
1
Unable to locate package mysql-server
这是因为更换软件源头之后,没有
update
,可以通过执行下面的命令进行更新:1
sudo apt-get updata
After updating, try it again:
1 | sudo apt install mysql-server mysql-client |
在输入密码后,再输入yes
即可开始安装。安装完成后,通过运行命令mysql -V
查看版本号:
1 | $ mysql -V |
验证MySQL服务正在运行,命令行下输入:
1 | sudo service mysql status |
如果正在运行,则会显示:
1 | ● mysql.service - MySQL Community Server |
2.2.2 配置MySQL的安全性
接下来,我们进行 MySQL
的安全性设置:
首先,运行命令
mysql_secure_installation
:1
sudo mysql_secure_installation
VALIDATE PASSWORD COMPONENT
设置验证密码插件。它被用来测试
MySQL
用户的密码强度,并且提高安全性。如果想设置验证密码插件,请输入y
:1
2
3
4
5
6
7
8Connecting to MySQL using a blank password.
VALIDATE PASSWORD COMPONENT can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD component?
Press y|Y for Yes, any other key for No: y接下来,将进行密码验证等级设置,根据数字设置对应等级,这里设置为0:
1
2
3
4
5
6
7There are three levels of password validation policy:
LOW Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters and dictionary file
Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 0设置密码
为
MySQL root
用户设置密码,设置过程中密码不会显示。如果设置了验证密码插件,将会显示密码的强度。1
2
3
4
5
6
7Please set the password for root here.
New password:
Re-enter new password:
Estimated strength of the password: 25
Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y移除匿名用户
默认情况下,
MySQL
安装有一个匿名用户,允许任何人登录MySQL
,而不必为他们创建用户帐户。输入y
进行删除:1
2
3
4
5
6
7
8
9By default, a MySQL installation has an anonymous user,
allowing anyone to log into MySQL without having to have
a user account created for them. This is intended only for
testing, and to make the installation go a bit smoother.
You should remove them before moving into a production
environment.
Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Success.禁止远程root用户登录
输入
y
后按enter
,将会禁止root
用户登录。1
2
3
4
5
6Normally, root should only be allowed to connect from
'localhost'. This ensures that someone cannot guess at
the root password from the network.
Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y
Success.删除测试库
输入
y
后按enter
,将会删除测试库。1
2
3
4
5
6
7
8
9By default, MySQL comes with a database named 'test' that
anyone can access. This is also intended only for testing,
and should be removed before moving into a production
environment.
Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
- Dropping test database...
Success.重新加载特权表
输入
y
后按enter
,将会重新加载特权表。1
2
3
4
5
6
7
8
9
10- Removing privileges on test database...
Success.
Reloading the privilege tables will ensure that all changes
made so far will take effect immediately.
Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
Success.
All done!至此,配置完成。
2.2.3 以 root 用户登录
在 MySQL 8.0
上,root 用户默认通过
auth_socket
插件授权。auth_socket
插件通过
Unix socket 文件来验证所有连接到 localhost
的用户。
这意味着你不能通过提供密码,验证为 root。此时,输入
mysql -uroot -p
可能会被拒绝访问:
1 | $ mysql -uroot -p |
若要以 root
用户身份登录 MySQL
服务器,输入sudo mysql
,如下:
1 | # 登录密码为linux系统用户的root密码 |
退出 MySQL
,请输入exit
命令:
1 | mysql> exit |
如果你想以 root 身份登录 MySQL
服务器,便于使用其他的程序。可以将验证方法从 auth_socket
修改成 mysql_native_password
。
Modify the privilige of root
可以通过运行下面的命令实现,语法中的 '你的密码' 指的是你自己设置的登录密码,可设置为字母数字组合:
1
2ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';
FLUSH PRIVILEGES;在
mysql
下,将密码设置为mysql123
,示例:1
2ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'mysql123';
Query OK, 0 rows affected (0.00 sec)刷新系统权限:
1
2FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.01 sec)退出:
1
2mysql> exit
Bye现在便可以通过
mysql -uroot -p
登录,登录密码为前面设置的mysql123
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17$ mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 57
Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
mysql> exit
Bye同时,命令
sudo mysql
会被拒绝访问:1
2lyons@ubuntu:~$ sudo mysql
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)当然,若要再次修改回
sudo mysql
的方式来登录root
用户,方法类似:1
2
3ALTER USER 'root'@'localhost' IDENTIFIED WITH auth_socket BY '你的密码';
FLUSH PRIVILEGES;Create a new account
【推荐选项】创建一个新的独立管理用户,拥有所有数据库的访问权限:
1
2
3
4
5# 创建用户
CREATE USER '用户名'@'localhost' identified by '你的密码'
# 赋予admin用户全部的权限,你也可以只授予部分权限
GRANT ALL PRIVILEGES ON *.* TO '用户名'@'localhost';示例:
1
create user 'admin'@'localhost' identified by 'mysql123';
注: 上述代码可能遇到如下错误:
1
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
通过查阅资料发现,是因为代码不符合当前的安全策略要求。为了解决这种问题,可以修改几个关于密码验证的设置的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14show variables like 'validate_password%';
+--------------------------------------+--------+
| Variable_name | Value |
+--------------------------------------+--------+
| validate_password.check_user_name | ON |
| validate_password.dictionary_file | |
| validate_password.length | 8 |
| validate_password.mixed_case_count | 1 |
| validate_password.number_count | 1 |
| validate_password.policy | MEDIUM |
| validate_password.special_char_count | 1 |
+--------------------------------------+--------+
7 rows in set (0.00 sec)可以发现,密码长度要求8位,验证策略是
MEDIUM
,就是长度,数字,大小写,特殊字符都得验证,因此出现如此所示的错误,就很正常了。我们可以修改validate_password_policy=0
,这样就是只检查长度。另外我们觉着 8 位太长了,我们可以改为4。这样可以设置root
密码为root
。1
2
3
4
5set global validate_password.policy=0;
Query OK, 0 rows affected (0.00 sec)
set global validate_password.length=4;
Query OK, 0 rows affected (0.00 sec)
问题解决,接下来继续创建新用户,首先创建名为
admin
的用户,密码为mysql123
:1
2create user 'admin'@'localhost' identified by 'mysql123';
Query OK, 0 rows affected (0.01 sec)将访问所有 database 以及表的权利授权用户
admin
,with gran option
表示该用户可给其它用户赋予权限,但不可能超过该用户已有的权限1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20grant all privileges on *.* to 'admin'@'localhost' with grant option;
Query OK, 0 rows affected (0.00 sec)
# 刷新
FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)
# 查看已有的用户
select user, host from mysql.user;
+------------------+-----------+
| user | host |
+------------------+-----------+
| admin | localhost |
| debian-sys-maint | localhost |
| mysql.infoschema | localhost |
| mysql.session | localhost |
| mysql.sys | localhost |
| root | localhost |
+------------------+-----------+
6 rows in set (0.00 sec)退出
root
用户登录,登录admin
用户,输入密码mysql123
即可登录成功:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17mysql> exit
Bye
# 登录admin用户,输入密码mysql123即可登录成功
lyons@ubuntu:~$ mysql -uadmin -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 16
Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.说明:
'admin'@'localhost'
中,localhost
指本地才可连接,可以将其换成%
指任意ip
都能连接,也可以指定ip
连接。
2.2.4 修改密码
将用户admin
的登录密码修改为mysql321
:
1 | ALTER USER 'admin'@'localhost' IDENTIFIED WITH mysql_native_password BY 'mysql321'; |
2.2.5 撤销用户授权
1 | # 查看用户的权限 |
2.2.6 删除用户
1 | drop user 'admin'@'localhost'; |
注:MySQL 8.0版本和5.0部分命令有所改掉,上述语法都是在8.0版本下运行通过的;请务必检查自己的MySQL版本号。
2.3 MySQL预备知识
在正式学习MySQL之前,我们先来了解一下 SQL 语句的书写规范以及命名规则等。
2.3.1 SQL书写规范
在写SQL语句时,要求按照如下规范进行:
SQL 语句要以分号(;)结尾
SQL 不区分关键字的大小写 ,这对于表名和列名同样适用。
插入到表中的数据是区分大小写的。例如,数据 Computer、COMPUTER 或 computer,三者是不一样的。
常数的书写方式是固定的,在 SQL 语句中直接书写的字符串、日期或者数字等称为常数。常数的书写方式如下所示。
- SQL 语句中含有字符串的时候,需要像 'abc' 这样,使用单引号(')将字符串括起来,用来标识这是一个字符串。
- SQL 语句中含有日期的时候,同样需要使用单引号将其括起来。日期的格式有很多种( '26 Jan 2010' 或者 '10/01/26' 等)。
- 在 SQL 语句中书写数字的时候,不需要使用任何符号标识,直接写成 1000 这样的数字即可。
单词之间需要用半角空格或者换行来分隔。
SQL 中的注释主要采用
--
和/* ... */
的方式,第二种方式可以换行。在 MySQL 下,还可以通过#
来进行注释。命名规则:
- 在数据库中,只能使用半角英文字母、数字、下划线(_)作为数据库、表和列的名称 。
- 名称必须以半角英文字母作为开头。
- 名称不能重复,同一个数据库下不能有 2 张相同的表。
2.3.2 数据类型
MySQL
支持所有标准 SQL
数值数据类型,包括:
(1)数值类型
数值包含的类型如下:
整型数据:
TINYINT
、INTEGER
、SMALLINT
、MEDIUMINT
、DECIMAL
、NUMERIC
和BIGINT
。浮点型数据:
DECIMAL
、FLOAT
、REAL
和DOUBLE PRECISION
)。
其中,关键字INT
是INTEGER
的同义词,关键字DEC是的同义词。
不同关键字的主要区别就是表示的范围或精度不一样。具体如下表:
类型 | 大小 | 范围(有符号) | 范围(无符号) | 用途 |
---|---|---|---|---|
TINYINT | 1 Bytes | (-128,127) | (0,255) | 小整数值 |
SMALLINT | 2 Bytes | (-32 768,32 767) | (0,65 535) | 大整数值 |
MEDIUMINT | 3 Bytes | (-8 388 608,8 388 607) | (0,16 777 215) | 大整数值 |
INT或INTEGER | 4 Bytes | (-2 147 483 648,2 147 483 647) | (0,4 294 967 295) | 大整数值 |
BIGINT | 8 Bytes | (-9,223,372,036,854,775,808,9 223 372 036 854 775 807) | (0,18 446 744 073 709 551 615) | 极大整数值 |
FLOAT | 4 Bytes | (-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) | 0,(1.175 494 351 E-38,3.402 823 466 E+38) | 单精度 浮点数值 |
DOUBLE | 8 Bytes | (-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 双精度 浮点数值 |
DECIMAL | 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 | 依赖于M和D的值 | 依赖于 M 和 D 的值 | 小数值 |
(2)日期和时间类型
表示时间值的日期和时间类型为DATETIME
、DATE
、TIMESTAMP
、TIME
和 YEAR
。具体如下表:
类型 | 大小 ( bytes) | 范围 | 格式 | 用途 |
---|---|---|---|---|
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 | 混合日期和时间值,时间戳 |
(3)字符串类型
字符串类型指
CHAR
、VARCHAR
、BINARY
、VARBINARY
、BLOB
、TEXT
、ENUM
和 SET
。具体如下表:
类型 | 大小 | 用途 |
---|---|---|
CHAR | 0-255 bytes | 定长字符串 |
VARCHAR | 0-65535 bytes | 变长字符串 |
TINYBLOB | 0-255 bytes | 不超过 255 个字符的二进制字符串 |
TINYTEXT | 0-255 bytes | 短文本字符串 |
BLOB | 0-65 535 bytes | 二进制形式的长文本数据 |
TEXT | 0-65 535 bytes | 长文本数据 |
MEDIUMBLOB | 0-16 777 215 bytes | 二进制形式的中等长度文本数据 |
MEDIUMTEXT | 0-16 777 215 bytes | 中等长度文本数据 |
LONGBLOB | 0-4 294 967 295 bytes | 二进制形式的极大文本数据 |
LONGTEXT | 0-4 294 967 295 bytes | 极大文本数据 |
char
声明的是定长字符串。若实际中字符串长度不足,则会在末尾使用空格进行填充至声明的长度。varchar
声明的是可变长字符串。存储过程中,只会按照字符串的实际长度来存储,但会多占用一位来存放实际字节的长度。
2.3.3 数据库的基本操作
首先,我们来学习在 MySQL下如何操作数据库。
数据库的创建
通过
CREATE
命令,可以创建指定名称的数据库,语法结构如下:1
CREATE DATABASE [IF NOT EXISTS] <数据库名称>;
如创建名为shop的数据库
1
CREATE DATABASE shop;
注:
MySQL
的数据存储区将以目录方式表示MySQL
数据库,因此数据库名称必须符合操作系统的文件夹命名规则,不能以数字开头,尽量要有实际意义。MySQL
下不运行存在两个相同名字的数据库,否则会报错。如果使用IF NOT EXISTS
(可选项),可以避免此类错误。
数据库的查看
查看所有存在的数据库
1
SHOW DATABASES [LIKE '数据库名'];;
LIKE
从句是可选项,用于匹配指定的数据库名称。LIKE
从句可以部分匹配,也可以完全匹配。示例:
1
SHOW DATABASES [like 'shop'];
结果如下:
1
2
3
4
5
6+-----------------+
| Database (shop) |
+-----------------+
| shop |
+-----------------+
1 row in set (0.00 sec)1
2
3
4
5
6
7
8
9
10
11-- %表示任意0个或多个字符,可匹配任意类型和长度的字符。
SHOW DATABASES LIKE 'S%';
-- 结果如下
+---------------+
| Database (s%) |
+---------------+
| shop |
| sys |
+---------------+
2 rows in set (0.00 sec)查看创建的数据库
1
SHOW CREATE DATABASE <数据库名>;
示例:
1
2
3
4SHOW CREATE DATABASE shop;
-- 或者
SHOW CREATE DATABASE shop \G结果如下:
1
2
3
4*************************** 1. row ***************************
Database: shop
Create Database: CREATE DATABASE `shop` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */
1 row in set (0.00 sec)CHARACTER SET utf8mb4
表示编码字符集为utf8mb4
。
选择数据库
在操作数据库前,必须指定所要操作的数据库。通过
USE
命令,可以切换到对应的数据库下。1
USE <数据库名>
示例:
1
2
3
4
5-- 切换到数据库shop下。
USE shop;
-- 结果如下
Database changed删除数据库
通过
DROP
命令,可以将相应数据库进行删除。1
DROP DATABASE [IF EXISTS] <数据库名>
其中,
IF EXISTS
为可选性,用于防止数据库不存在时报错。示例:
1
2
3
4
5
6
7
8
9
10
11## 1.
DROP DATABASE shop;
# Results:
Query OK, 0 rows affected (0.01 sec)
SHOW DATABASES; # the shop database has beed deleted.
## 2.
drop database if exists ep;
# Results:
Query OK, 0 rows affected, 1 warning (0.00 sec)考虑到后面表的操作都是
shop
数据库下,在实验完DROP
删除数据库命令后,请从新创建数据库shop
并通过USE
命令切换到该数据库下。
2.4 表的基本操作
表相当于文件,表中的一条记录就相当于文件的一行内容,不同的是,表中的一条记录有对应的标题,称为表的字段。
2.4.1 表的创建
创建表的语法结构如下:
1 | CREATE TABLE <表名> (<字段1> <数据类型> <该列所需约束>, |
示例:
1 | -- 创建一个名为Product的表 |
在第二章中,我们介绍过不同的数据类型:
CHAR
为定长字符,这里CHAR
旁边括号里的数字表示该字段最长为多少字符,少于该数字将会使用空格进行填充。VARCHAR
表示变长字符,括号里的数字表示该字段最长为多少字符,存储时只会按照字符的实际长度来存储,但会使用额外的1-2字节来存储值长度。
简单介绍一下该语句中出现的约束条件,约束条件在后面会详细介绍:
PRIMARY KEY
:主键,表示该字段对应的内容唯一且不能为空。NOT NULL
:在NULL
之前加上了表示否定的NOT
,表示该字段不能输入空白。
通过 SHOW TABLES
命令来查看当前数据库下的所有的表名:
1 | SHOW TABLES; |
通过DESC <表名>
来查看表的结构:
1 | DESC Product; |
2.4.2 表的删除
删除表的语法结构如下:
1 | DROP TABLE <表名>; |
说明:通过 DROP
删除的表示无法恢复的,在删除表的时候请谨慎。
2.4.3 表的更新
通过 ALTER TABLE
语句,我们可以对表字段进行不同的操作,下面通过示例来具体学习用法。
示例:
创建一张名为
Student
的表1
2
3
4
5
6CREATE TABLE Student(
id INT PRIMARY KEY,
name CHAR(15)
);
DESC Student;Results:
1
2
3
4
5
6
7+-------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| name | char(15) | YES | | NULL | |
+-------+----------+------+-----+---------+-------+
2 rows in set (0.00 sec)更改表名
通过
RENAME
命令,将表名从 Student => Students。1
ALTER TABLE Student RENAME Students;
Results:
1
Query OK, 0 rows affected (0.02 sec)
插入新的字段
通过
ADD
命令,新增字段 sex 和 age。1
2-- 不同的字段通过逗号分开
ALTER TABLE Students ADD sex CHAR(1), ADD age INT;Results:
1
2Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0其它插入技巧:
通过
FIRST
在表首插入字段stu_num
:1
ALTER TABLE Students ADD stu_num INT FIRST;
Results:
1
2Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0指定在字段sex后插入字段height
1
ALTER TABLE Students ADD height INT AFTER sex;
Results:
1
2Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0最后,查看当前
Students
的数据类型:1
DESC Students;
结果如下:
1
2
3
4
5
6
7
8
9
10
11+---------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+----------+------+-----+---------+-------+
| stu_num | int | YES | | NULL | |
| id | int | NO | PRI | NULL | |
| name | char(15) | YES | | NULL | |
| sex | char(1) | YES | | NULL | |
| height | int | YES | | NULL | |
| age | int | YES | | NULL | |
+---------+----------+------+-----+---------+-------+
6 rows in set (0.00 sec)
字段的删除
通过
DROP
命令,可以对不在需要的字段进行删除。如删除字段stu_num
:1
ALTER TABLE Students DROP stu_num;
Results:
1
2Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0字段的修改
通过
MODIFY
修改字段的数据类型。如修改字段age
的数据类型1
ALTER TABLE Students MODIFY age CHAR(3);
通过
CHANGE
命令,修改字段名或类型。如修改字段name
为stu_name
,但是不修改数据类型:1
ALTER TABLE Students CHANGE name stu_name CHAR(15);
以及修改字段
sex
为stu_sex
,数据类型修改为int
:1
ALTER TABLE Students CHANGE sex stu_sex INT;
最后,查看更改后的数据类型:
1
2
3
4
5
6
7
8
9
10
11
12
13DESC Students;
-- 结果如下
+----------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+----------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| stu_name | char(20) | YES | | NULL | |
| stu_sex | int | YES | | NULL | |
| height | int | YES | | NULL | |
| age | char(3) | YES | | NULL | |
+----------+----------+------+-----+---------+-------+
5 rows in set (0.00 sec)
2.4.4 表的查询
通过
SELECT
语句,可以从表中取出所要查看的字段的内容:
1 | SELECT <字段名>, …… |
如要直接查询表的全部字段:
1 | SELECT * |
其中,__星号(*)__ 代表全部字段的意思。
建表并插入数据
在 MySQL 中,我们通过
INSERT
语句往表中插入数据,该语句在后面会详细介绍,该小节的重点是学会使用SELECT
。向
Product
表中插入数据:1
2
3
4
5
6
7
8
9
10INSERT INTO Product VALUES
('0001', 'T恤衫', '衣服', 1000, 500, '2009-09-20'),
('0002', '打孔器', '办公用品', 500, 320, '2009-09-11'),
('0003', '运动T恤', '衣服', 4000, 2800, NULL),
('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'),
('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'),
('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20'),
('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28'),
('0008', '圆珠笔', '办公用品', 100, NULL,'2009-11-11')
;Results:
1
2Query OK, 8 rows affected (0.00 sec)
Records: 8 Duplicates: 0 Warnings: 0查看表的内容
查看 全部内容
1
SELECT * FROM Product;
Results:
1
2
3
4
5
6
7
8
9
10
11
12
13+------------+--------------+--------------+------------+----------------+-------------+
| product_id | product_name | product_type | sale_price | purchase_price | regist_date |
+------------+--------------+--------------+------------+----------------+-------------+
| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 |
| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 |
| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL |
| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 |
| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 |
| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 |
| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 |
| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 |
+------------+--------------+--------------+------------+----------------+-------------+
8 rows in set (0.00 sec)查看 部分字段 包含的内容
1
select product_id, product_name, sale_price from Product;
Results:
1
2
3
4
5
6
7
8
9
10
11
12
13+------------+--------------+------------+
| product_id | product_name | sale_price |
+------------+--------------+------------+
| 0001 | T恤衫 | 1000 |
| 0002 | 打孔器 | 500 |
| 0003 | 运动T恤 | 4000 |
| 0004 | 菜刀 | 3000 |
| 0005 | 高压锅 | 6800 |
| 0006 | 叉子 | 500 |
| 0007 | 擦菜板 | 880 |
| 0008 | 圆珠笔 | 100 |
+------------+--------------+------------+
8 rows in set (0.00 sec)
对查看的字段建立别名
通过
AS
语句对展示的字段另起别名,这不会修改表内字段的名字。1
2
3
4SELECT
product_id AS ID,
product_type AS TYPE
FROM Product;Results:
1
2
3
4
5
6
7
8
9
10
11
12
13+------+--------------+
| ID | TYPE |
+------+--------------+
| 0001 | 衣服 |
| 0002 | 办公用品 |
| 0003 | 衣服 |
| 0004 | 厨房用具 |
| 0005 | 厨房用具 |
| 0006 | 厨房用具 |
| 0007 | 厨房用具 |
| 0008 | 办公用品 |
+------+--------------+
8 rows in set (0.00 sec)设定汉语别名时需要使用双引号
"
或单引号'
括起来,英文字符则不需要。1
2
3
4SELECT
product_id AS "产品编号",
product_type AS "产品类型"
FROM Product;常数的查询
SELECT
子句中,除了可以写字段外,还可以写常数。1
2
3
4
5
6SELECT
'商品' AS string,
'2009-05-24' AS date,
product_id,
product_name
FROM Product;Results:
1
2
3
4
5
6
7
8
9
10
11
12
13+--------+------------+------------+--------------+
| string | date | product_id | product_name |
+--------+------------+------------+--------------+
| 商品 | 2009-05-24 | 0001 | T恤衫 |
| 商品 | 2009-05-24 | 0002 | 打孔器 |
| 商品 | 2009-05-24 | 0003 | 运动T恤 |
| 商品 | 2009-05-24 | 0004 | 菜刀 |
| 商品 | 2009-05-24 | 0005 | 高压锅 |
| 商品 | 2009-05-24 | 0006 | 叉子 |
| 商品 | 2009-05-24 | 0007 | 擦菜板 |
| 商品 | 2009-05-24 | 0008 | 圆珠笔 |
+--------+------------+------------+--------------+
8 rows in set (0.00 sec)删除重复行
在
SELECT
语句中使用DISTINCT
可以去除重复行。单列
1
2
3SELECT
DISTINCT regist_date
FROM Product;Results:
1
2
3
4
5
6
7
8
9
10
11+-------------+
| regist_date |
+-------------+
| 2009-09-20 |
| 2009-09-11 |
| NULL |
| 2009-01-15 |
| 2008-04-28 |
| 2009-11-11 |
+-------------+
6 rows in set (0.01 sec)在使用
DISTINCT
时,NULL
也被视为一类数据。NULL
存在于多行中时,会被合并为一条NULL
数据。组合列
还可以通过组合使用,来去除列组合重复的数据。
DISTINCT
关键字只能用在第一个列名之前。1
2
3SELECT
DISTINCT product_type, regist_date
FROM Product;Results:
1
2
3
4
5
6
7
8
9
10
11
12+--------------+-------------+
| product_type | regist_date |
+--------------+-------------+
| 衣服 | 2009-09-20 |
| 办公用品 | 2009-09-11 |
| 衣服 | NULL |
| 厨房用具 | 2009-09-20 |
| 厨房用具 | 2009-01-15 |
| 厨房用具 | 2008-04-28 |
| 办公用品 | 2009-11-11 |
+--------------+-------------+
7 rows in set (0.00 sec)
指定查询条件
首先通过
WHERE
子句查询出符合指定条件的记录,然后再选取出SELECT
语句指定的列,语法结构如下:1
2
3SELECT <字段名>, ……
FROM <表名>
WHERE <条件表达式>;示例:
1
2
3SELECT product_name
FROM Product
WHERE product_type = '衣服';Results:
1
2
3
4
5
6
7+--------------+
| product_name |
+--------------+
| T恤衫 |
| 运动T恤 |
+--------------+
2 rows in set (0.01 sec)注意,
WHERE
子句要紧跟在FROM
子句之后。
2.4.5 表的复制
表的复制可以将表结构与表中的数据全部复制,或者只复制表的结构。
复制整个表
1
2
3
4
5CREATE TABLE Product_COPY1
SELECT * FROM Product;
-- Show
SELECT * FROM Product_COPY1;Resutls:
1
2
3
4
5
6
7
8
9
10
11
12
13+------------+--------------+--------------+------------+----------------+-------------+
| product_id | product_name | product_type | sale_price | purchase_price | regist_date |
+------------+--------------+--------------+------------+----------------+-------------+
| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 |
| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 |
| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL |
| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 |
| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 |
| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 |
| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 |
| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 |
+------------+--------------+--------------+------------+----------------+-------------+
8 rows in set (0.00 sec)复制表格结构
可以借助
LIKE
关键字复制表结构:1
2
3
4CREATE TABLE Product_COPY2
LIKE Product;
SELECT * FROM Product_COPY2;Results:
1
Empty set (0.00 sec)
可以发现此时表格是空的。接下来,查看表格结构:
1
DESC Product_COPY2;
Results:
1
2
3
4
5
6
7
8
9
10
11+----------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+-------+
| product_id | char(4) | NO | PRI | NULL | |
| product_name | varchar(100) | NO | | NULL | |
| product_type | varchar(32) | NO | | NULL | |
| sale_price | int | YES | | 0 | |
| purchase_price | int | YES | | NULL | |
| regist_date | date | YES | | NULL | |
+----------------+--------------+------+-----+---------+-------+
6 rows in set (0.01 sec)可以发现,虽然表格没有具体的值,但是表结构已复制成功。
2.5 运算符
2.5.1 算术运算符
我们可以在SELECT
语句中使用计算表达式:
1 | SELECT |
Results:
1 | +--------------+------------+---------------+ |
四则运算所使用的运算符
+
、-
、*
和/
称为算术运算符。在运算表达式中,也可以使用
()
,括号中的运算表达式优先级会得到提升。NULL
的计算结果,仍然还是NULL
。
2.5.2 比较运算符
在 WHERE
子句中通过使用比较运算符可以组合出各种各样的条件表达式。
1 | SELECT product_name, product_type |
Results:
1 | +--------------+--------------+ |
常见比较运算符如下表:
运算符 | 含义 |
---|---|
= | 相等 |
<> | 不相等 |
>= | 大于等于 |
> | 大于 |
<= | 小于等于 |
< | 小于 |
不能对
NULL
使用任何比较运算符,只能通过IS NULL
语句来判断:1
2
3
4
5SELECT
product_name,
purchase_price
FROM Product
WHERE purchase_price IS NULL;Results:
1
2
3
4
5
6
7+--------------+----------------+
| product_name | purchase_price |
+--------------+----------------+
| 叉子 | NULL |
| 圆珠笔 | NULL |
+--------------+----------------+
2 rows in set (0.00 sec)希望选取不是 `NULL` 的记录时,需要使用`IS NOT NULL`运算符。
对字符串使用比较符
MySQL
中字符串的排序与数字不同,典型的规则就是按照字典顺序进行比较,也就是像姓名那样,按照条目在字典中出现的顺序来进行排序。例如:1
'1' < '10' < '11' < '2' < '222' < '3'
2.5.3 逻辑运算符
使用
NOT
否认某一条件:1
2
3
4
5
6SELECT
product_name,
product_type,
sale_price
FROM Product
WHERE NOT sale_price >= 1000;Results:
1
2
3
4
5
6
7
8
9+--------------+--------------+------------+
| product_name | product_type | sale_price |
+--------------+--------------+------------+
| 叉子 | 厨房用具 | 500 |
| 擦菜板 | 厨房用具 | 880 |
| 圆珠笔 | 办公用品 | 100 |
| 打孔器 | 办公用品 | 500 |
+--------------+--------------+------------+
4 rows in set (0.00 sec)AND
运算符1
2
3
4SELECT product_type, sale_price
FROM Product
WHERE product_type = '厨房用具'
AND sale_price >= 3000;Resuls:
1
2
3
4
5
6
7+--------------+------------+
| product_type | sale_price |
+--------------+------------+
| 厨房用具 | 3000 |
| 厨房用具 | 6800 |
+--------------+------------+
2 rows in set (0.01 sec)OR
运算符1
2
3
4SELECT product_type, sale_price
FROM Product
WHERE product_type = '厨房用具'
OR sale_price >= 3000;Results:
1
2
3
4
5
6
7
8
9
10+--------------+------------+
| product_type | sale_price |
+--------------+------------+
| 衣服 | 4000 |
| 厨房用具 | 3000 |
| 厨房用具 | 6800 |
| 厨房用具 | 500 |
| 厨房用具 | 880 |
+--------------+------------+
5 rows in set (0.00 sec)逻辑运算符和真值
符
NOT
、AND
和OR
称为逻辑运算符;真值就是值为真
TRUE
或 假FALSE
;在查询
NULL
时,SQL中存在第三种真值,不确定UNKNOWN
,NULL
和任何值做逻辑运算结果都是不确定;考虑
NULL
时的条件判断也会变得异常复杂,因此尽量给字段加上NOT NULL
的约束。
2.6 分组查询
2.6.1 聚合函数
通过 SQL 对数据进行某种操作或计算时需要使用函数。
COUNT
:计算表中的记录数(行数)SUM
: 计算表中数值列中数据的合计值AVG
: 计算表中数值列中数据的平均值MAX
: 求出表中任意列中数据的最大值MIN
: 求出表中任意列中数据的最小值
示例:
计数
计算全部数据的行数
1
SELECT COUNT(*) FROM Product;
Results:
1
2
3
4
5
6+----------+
| COUNT(*) |
+----------+
| 8 |
+----------+
1 row in set (0.00 sec)Note 1: 除了
COUNT
可以将*
作为参数,其它的函数均不可以。最大值
计算最高的销售价格
1
SELECT MAX(sale_price) FROM Product;
Results:
1
2
3
4
5
6+-----------------+
| MAX(sale_price) |
+-----------------+
| 680000 |
+-----------------+
1 row in set (0.00 sec)Note 2: 当将字段名作为参数传递给函数时,只会计算不包含
NULL
的行。Example
1
2--
SELECT purchase_price FROM Product;Results:
1
2
3
4
5
6
7
8
9
10
11
12
13+----------------+
| purchase_price |
+----------------+
| 500 |
| 320 |
| 2800 |
| 700 |
| 1250 |
| NULL |
| 198 |
| NULL |
+----------------+
8 rows in set (0.00 sec)可以发现
purchase_price
字段是包含NULL
值的,且共有8
个值。首先,以
*
为参数传递给COUNT
函数1
SELECT COUNT(*) FROM Product;
Results:
1
2
3
4
5
6+----------+
| COUNT(*) |
+----------+
| 8 |
+----------+
1 row in set (0.00 sec)其次,以
purchase_price
为参数传递给COUNT
函数1
SELECT COUNT(purchase_price) FROM Product;
Results
1
2
3
4
5
6+-----------------------+
| COUNT(purchase_price) |
+-----------------------+
| 6 |
+-----------------------+
1 row in set (0.00 sec)可以看到两次结果结果并不一样,函数忽略了值为
NULL
的行。
Note 3:
SUM
,AVG
函数在计算时也会进行同样的操作,并不会将NULL
当做0
来处理! 特别注意AVG
函数,计算时分母也不会算上NULL
行。Note 4:
MAX/MIN
函数几乎适用于所有数据类型的列,包括字符和日期。SUM/AVG
函数只适用于数值类型的列。计算唯一值数
在聚合函数删除重复值:
1
2SELECT COUNT(DISTINCT product_type)
FROM Product;Results:
1
2
3
4
5
6+------------------------------+
| COUNT(DISTINCT product_type) |
+------------------------------+
| 3 |
+------------------------------+
1 row in set (0.01 sec)Note 5:
DISTINCT
必须写在括号中。这是因为必须要在计算行数之前删除 product_type 字段中的重复数据。
2.6.2 对表分组
如果对 Pandas
熟悉,那么应该很了解 groupby
函数,借助该函数可以根据指定的列名,对对表进行分组。在
MySQL
中,也存在同样作用的函数,即
GROUP BY
。
语法结构如下:
1 | SELECT <列名1>, <列名2>, <列名3>, …… |
按照 product_type
字段进行分组,并统计各组的数量:
1 | SELECT product_type, COUNT(*) |
Results:
1 | +--------------+----------+ |
在该语句中,我们首先通过 GROUP BY
函数对指定的字段
product_type
进行分组。分组时,product_type
字段中具有相同值的行会汇聚到同一组。最后,通过 COUNT
函数,统计不同分组的包含的行数。
简单来理解:
例如做操时,老师将不同身高的同学进行分组,相同身高的同学会被分到同一组,分组后我们又统计了每个小组的学生数。
将这里的同学可以理解为表中的一行数据,身高理解为表的某一字段。
分组操作就是
GROUP BY
,GROUP BY
后面接的字段等价于按照身高分组,统计学生数就等价于在SELECT
后用了COUNT(*)
函数。
Note: GROUP BY
子句的位置一定要写在
FROM
语句之后(如果有 WHERE
子句的话需要写在
WHERE
子句之后)
1 | 1. SELECT → 2. FROM → 3. WHERE → 4. GROUP BY |
当被聚合的键中,包含 NULL
时,在结果中会以
不确定 行(空行)的形式表现出来,也就是字段中为
NULL
的数据会被聚合为一组。
2.6.3 使用 WHERE
语句
在对表进行分组之前,也可以是先使用 WHERE
对表进行条件过滤,然后再进行分组处理。语法结构如下:
1 | SELECT <列名1>, <列名2>, <列名3>, …… |
我们先通过 WHERE
语句将表中类型为衣服的行筛选出来,然后再按照 purchase_price
来进行分组:
1 | SELECT purchase_price, COUNT(*) |
Results:
1 | +----------------+----------+ |
Note: 该语法实际的执行顺序为:
1 | FROM → WHERE → GROUP BY → SELECT |
此外,
使用
GROUP BY
子句时,SELECT
子句中不能出现聚合键之外的字段名(因为SELECT
语句是在GROUP BY
语句之后执行的);即,若
GROUP BY
选中purchase_price
字段进行分组,则在SELECT
语句中只能选中purchase_price
字段,其它字段如product_id
等均不行。WHERE
语句中,不可以使用聚合函数。WHERE
子句只能指定记录(行)的条件,而不能用来指定组的条件。即WHERE MAX(purchase_price) > 1000
这样的语句是非法的。
2.6.4 为聚合结果指定条件
前面提到了 WHERE
语句中不能使用聚合函数,但是实际操作时需要通过聚合函数来进行过滤怎么办呢?这就要用到
HAVING
语句了。语法结构如下:
1 | SELECT <列名1>, <列名2>, <列名3>, …… |
在 HAVING
的子句中能够使用的 3 种要素如下所示:
- 常数
- 聚合函数
GROUP BY
子句中指定的字段名(即聚合键)
不使用
HAVING
语句1
2
3SELECT product_type, AVG(sale_price)
FROM Product
GROUP BY product_type;Results:
1
2
3
4
5
6
7
8+--------------+-----------------+
| product_type | AVG(sale_price) |
+--------------+-----------------+
| 衣服 | 2500.0000 |
| 厨房用具 | 2795.0000 |
| 办公用品 | 300.0000 |
+--------------+-----------------+
3 rows in set (0.00 sec)使用
HAVING
语句通过
HAVING
语句选择销售平均价格大于或等于2500的数据:1
2
3
4SELECT product_type, AVG(sale_price)
FROM Product
GROUP BY product_type
HAVING AVG(sale_price) >= 2500;Results:
1
2
3
4
5
6
7+--------------+-----------------+
| product_type | AVG(sale_price) |
+--------------+-----------------+
| 衣服 | 2500.0000 |
| 厨房用具 | 2795.0000 |
+--------------+-----------------+
2 rows in set (0.00 sec)
可以看到使用HAVING
语句后,输出的结果有所变化。大致流程如下:
- 首先,
FROM
语句会选中表Product
; - 然后,
GROUP BY
语句会选中字段product_type
进行分组; - 之后,通过
HAVING
语句将销售平均价格大于等于2500
的组保留下来; - 最后,通过
SELECT
语句将保留下的组的产品类型和平均价格显示出来;
如果是对表的行进行条件指定,WHERE
和HAVING
都可以生效。下面两条语句执行结果一致:
HAVING
1
2
3
4SELECT product_type, COUNT(*)
FROM Product
GROUP BY product_type
HAVING product_type = '衣服';WHERE
1
2
3
4SELECT product_type, COUNT(*)
FROM Product
WHERE product_type = '衣服'
GROUP BY product_type;
Results:
1 | +--------------+----------+ |
Note:
一般而言如果是对表的行进行条件指定,最好还是使用 WHERE
语句,因为 WHERE
的执行速度更快。
2.6.5 对表的查询结果进行排序
如果希望对表的查询结果根据某指定的字段进行排序,可以使用ORDER BY
语句。语法结构如下:
1 | SELECT <列名1>, <列名2>, <列名3>, …… |
示例:
1 | SELECT product_id, product_name, sale_price, purchase_price |
根据字段
sale_price
的值进行升序排序1
2
3SELECT product_id, product_name, sale_price, purchase_price
FROM Product
ORDER BY sale_price;Results:
1
2
3
4
5
6
7
8
9
10
11
12
13+------------+--------------+------------+----------------+
| product_id | product_name | sale_price | purchase_price |
+------------+--------------+------------+----------------+
| 0008 | 圆珠笔 | 100 | NULL |
| 0002 | 打孔器 | 500 | 320 |
| 0001 | T恤衫 | 1000 | 500 |
| 0003 | 运动T恤 | 4000 | 2800 |
| 0006 | 叉子 | 50000 | NULL |
| 0007 | 擦菜板 | 88000 | 198 |
| 0004 | 菜刀 | 300000 | 700 |
| 0005 | 高压锅 | 680000 | 1250 |
+------------+--------------+------------+----------------+
8 rows in set (0.00 sec)可以看到
ORDER BY
默认是按照升序的方式进行排序的,正式的书写方式应该是在字段后加上关键字ASC
,即ORDER BY sale_price ASC
。降序排列
如果我们希望按照降序的方式,可以通过
DESC
关键词进行指定。1
2
3SELECT product_id, product_name, sale_price, purchase_price
FROM Product
ORDER BY sale_price DESC;Results:
1
2
3
4
5
6
7
8
9
10
11
12
13+------------+--------------+------------+----------------+
| product_id | product_name | sale_price | purchase_price |
+------------+--------------+------------+----------------+
| 0005 | 高压锅 | 680000 | 1250 |
| 0004 | 菜刀 | 300000 | 700 |
| 0007 | 擦菜板 | 88000 | 198 |
| 0006 | 叉子 | 50000 | NULL |
| 0003 | 运动T恤 | 4000 | 2800 |
| 0001 | T恤衫 | 1000 | 500 |
| 0002 | 打孔器 | 500 | 320 |
| 0008 | 圆珠笔 | 100 | NULL |
+------------+--------------+------------+----------------+
8 rows in set (0.00 sec)组合排序
前面展示了指定一个字段来对表进行排序,实际上我们可以指定多个字段来进行排序。
示例:
1
2
3SELECT regist_date, product_id, sale_price, purchase_price
FROM Product
ORDER BY regist_date, product_id;1
2
3
4
5
6
7
8
9
10
11
12+-------------+------------+------------+----------------+
| regist_date | product_id | sale_price | purchase_price |
+-------------+------------+------------+----------------+
| 2009-10-10 | 0002 | 500 | 320 |
| 2009-10-10 | 0003 | 4000 | 2800 |
| 2009-10-10 | 0004 | 300000 | 700 |
| 2009-10-10 | 0005 | 680000 | 1250 |
| 2009-10-10 | 0006 | 50000 | NULL |
| 2009-10-10 | 0007 | 88000 | 198 |
| 2009-10-10 | 0008 | 100 | NULL |
| 2021-10-30 | 0001 | 1000 | 500 |
+-------------+------------+------------+----------------+可以看到先按照
regist_date
的大小进行排序,在字段regist_date
中具有相同的值的行,接着会按照product_id
进行排序。Note: 使用含有
NULL
的列作为排序键时,NULL
会在结果的开头或末尾汇总显示。定义别名
在
ORDER BY
子句中可以使用SELECT
子句中定义的别名。1
2
3
4-- 将product_id命名为ID,然后按照ID进行排序
SELECT product_id as ID, product_name, sale_price, purchase_price
FROM Product
ORDER BY ID;Results:
1
2
3
4
5
6
7
8
9
10
11
12
13+------+--------------+------------+----------------+
| ID | product_name | sale_price | purchase_price |
+------+--------------+------------+----------------+
| 0001 | T恤衫 | 1000 | 500 |
| 0002 | 打孔器 | 500 | 320 |
| 0003 | 运动T恤 | 4000 | 2800 |
| 0004 | 菜刀 | 300000 | 700 |
| 0005 | 高压锅 | 680000 | 1250 |
| 0006 | 叉子 | 50000 | NULL |
| 0007 | 擦菜板 | 88000 | 198 |
| 0008 | 圆珠笔 | 100 | NULL |
+------+--------------+------------+----------------+
8 rows in set (0.00 sec)思考: 为什么
ORDER BY
中可以使用SELECT
定义的别名呢?这是因为在
MySQL
中,ORDER BY
的执行次序在SELECT
之后。
2.7 数据的插入及更新
2.7.1 数据的插入
通过命令INSERT
,可以向表中插入数据:
往表中插入一行数据
1
INSERT INTO <表名> (字段1, 字段2, 字段3, ……) VALUES (值1, 值2, 值3, ……);
往表中插入多行数据
1
2
3
4
5INSERT INTO <表名> (字段1, 字段2, 字段3, ……) VALUES
(值1, 值2, 值3, ……),
(值1, 值2, 值3, ……),
...
;
示例:
创建表
1
2
3
4
5
6
7
8CREATE TABLE ProductIns
(product_id CHAR(4) NOT NULL,
product_name VARCHAR(100) NOT NULL,
product_type VARCHAR(32) NOT NULL,
sale_price INTEGER DEFAULT 0, -- DEFAULT 0:表示将字段sale_price的默认值设为0
purchase_price INT ,
regist_date DATE ,
PRIMARY KEY (product_id));Results:
1
Query OK, 0 rows affected (0.03 sec)
通过单行方式插入数据
1
2
3INSERT INTO
ProductIns(product_id, product_name, product_type, sale_price, purchase_price, regist_date)
VALUES ('0001', '打孔器', '办公用品', 500, 320, '2009-09-11');Results:
1
Query OK, 0 rows affected (0.03 sec)
Note: 当对表插入全字段时,可以省略表后的字段清单
1
INSERT INTO ProductIns VALUES('0002', '高压锅', '厨房用具', 6800, 5000, '2009-01-15');
通过多行方式插入
1
2
3
4
5
6INSERT INTO ProductIns VALUES
('0003', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'),
('0004', '订书机', '办公用品', 100, 50, '2009-09-11'),
('0005', '裙子', '衣服', 4100, 3200, '2009-01-23'),
('0006', '运动T恤', '衣服', 4000, 2800, NULL),
('0007', '牙刷', '日用品', 20, 10, '2010-03-22');Results:
1
2Query OK, 5 rows affected (0.01 sec)
Records: 5 Duplicates: 0 Warnings: 0查看数据
1
SELECT * FROM ProductIns;
Results:
1
2
3
4
5
6
7
8
9
10
11
12+------------+--------------+--------------+------------+----------------+-------------+
| product_id | product_name | product_type | sale_price | purchase_price | regist_date |
+------------+--------------+--------------+------------+----------------+-------------+
| 0001 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 |
| 0002 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 |
| 0003 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 |
| 0004 | 订书机 | 办公用品 | 100 | 50 | 2009-09-11 |
| 0005 | 裙子 | 衣服 | 4100 | 3200 | 2009-01-23 |
| 0006 | 运动T恤 | 衣服 | 4000 | 2800 | NULL |
| 0007 | 牙刷 | 日用品 | 20 | 10 | 2010-03-22 |
+------------+--------------+--------------+------------+----------------+-------------+
7 rows in set (0.00 sec)插入
NULL
INSERT
语句中想给某一列赋予NULL
值时,可以直接在VALUES
子句的值清单中写入NULL
。1
INSERT INTO ProductIns VALUES ('0008', '叉子', '厨房用具', 500, NULL, '2009-09-20');
Results:
1
Query OK, 1 row affected (0.00 sec)
插入默认值
在前面我们创建表时,字段
sale_price
包含了一条约束条件,默认为0
。我们在插入数据时,可以直接用DEFAULT
对该字段赋值。前提是,该字段被指定了默认值。通过显式方法设定默认值
1
2
3INSERT INTO
ProductIns (product_id, product_name, product_type, sale_price, purchase_price, regist_date)
VALUES ('0009', '擦菜板', '厨房用具', DEFAULT, 790, '2009-04-28');通过隐式方法插入默认值
1
2
3INSERT INTO
ProductIns (product_id, product_name, product_type, purchase_price, regist_date)
VALUES ('0010', '擦菜板', '厨房用具', 790, '2009-04-28');
Results:
1
Query OK, 1 row affected (0.00 sec)
2.7.2 数据的删除
通过 DROP TABLE
或者 DELETE
语句,可以对表进行删除,但二者存在一定的区别。
DROP TABLE
语句可以将表完全删除。语法结构为:
1
DROP <表名>;
DELETE
语句会留下表结构,而删除表中的全部数据,即DELETE
语句的删除对象并不是表或者列,而是记录(行)。语法结构如下,记得要加
FROM
:1
DELETE FROM <表名>;
同时,也可以通过
WHERE
语句来指定删除的条件:1
2DELETE FROM <表名>
WHERE <条件>;Note: 无论通过哪种方式删除,数据都是难以恢复的。
Case:查看数据:
1
SELECT * FROM Product;
Results:
1
2
3
4
5
6
7
8
9
10
11
12
13+------------+--------------+--------------+------------+----------------+-------------+
| product_id | product_name | product_type | sale_price | purchase_price | regist_date |
+------------+--------------+--------------+------------+----------------+-------------+
| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 |
| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 |
| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL |
| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 |
| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 |
| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 |
| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 |
| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 |
+------------+--------------+--------------+------------+----------------+-------------+
8 rows in set (0.00 sec)删除销售价格大于等于4000的行:
1
2
3
4DELETE FROM Product
WHERE sale_price >= 4000;
SELECT * FROM Product;Results:
1
2
3
4
5
6
7
8
9
10
11+------------+--------------+--------------+------------+----------------+-------------+
| product_id | product_name | product_type | sale_price | purchase_price | regist_date |
+------------+--------------+--------------+------------+----------------+-------------+
| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 |
| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 |
| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 |
| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 |
| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 |
| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 |
+------------+--------------+--------------+------------+----------------+-------------+
6 rows in set (0.00 sec)
通过
TRUNCATE
进行删除在
MySQL
中,还存在一种删除表的方式,就是利用TRUNCATE
语句。它的功能和DROP
类似,但是不能通过WHERE
指定条件,优点是速度比DROP
快得多。1
TRUNCATE Product;
Results:
1
Empty set (0.00 sec)
查看数据
1
DESC Product;
Results:
1
2
3
4
5
6
7
8
9
10
11+----------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+-------+
| product_id | char(4) | NO | PRI | NULL | |
| product_name | varchar(100) | NO | | NULL | |
| product_type | varchar(32) | NO | | NULL | |
| sale_price | int | YES | | NULL | |
| purchase_price | int | YES | | NULL | |
| regist_date | date | YES | | NULL | |
+----------------+--------------+------+-----+---------+-------+
6 rows in set (0.00 sec)
2.7.3 数据的更新
当我们使用 INSERT
语句插入错误的数据后,若我们不想删除后重新插入,那就要使用到
UPDATE
语句。
UPDATE
的语法结构如下:
1 | UPDATE <表名> |
数据准备
Note: 由于前面演示删除语句时,表
Product
的内容已清空,所以,这里重新进行数据插入):1
2
3
4
5
6
7
8
9
10INSERT INTO Product VALUES
('0001', 'T恤衫', '衣服', 1000, 500, '2009-09-20'),
('0002', '打孔器', '办公用品', 500, 320, '2009-09-11'),
('0003', '运动T恤', '衣服', 4000, 2800, NULL),
('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'),
('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'),
('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20'),
('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28'),
('0008', '圆珠笔', '办公用品', 100, NULL,'2009-11-11')
;修改表中所有行
regist_date
的值1
2
3
4UPDATE Product
SET regist_date = '2009-10-10';
SELECT * FROM Product;Results:
1
2
3
4
5
6
7
8
9
10
11
12
13+------------+--------------+--------------+------------+----------------+-------------+
| product_id | product_name | product_type | sale_price | purchase_price | regist_date |
+------------+--------------+--------------+------------+----------------+-------------+
| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-10-10 |
| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-10-10 |
| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | 2009-10-10 |
| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-10-10 |
| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-10-10 |
| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-10-10 |
| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2009-10-10 |
| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-10-10 |
+------------+--------------+--------------+------------+----------------+-------------+
8 rows in set (0.00 sec)指定条件:
语法结构为:
1
2
3UPDATE <表名>
SET <列名> = <表达式>
WHERE <条件>;指定
product_id
为0001
:1
2
3UPDATE Product
SET regist_date = '2021-10-30'
WHERE product_id = '0001';Note: 也可使用
NULL
对表进行更新,不过更新的字段必须满足没有 主键 和NOT NULL
的约束条件。多列更新
多列更新只需要用逗号(,)连接更改的字段即可。
1
2
3
4
5UPDATE Product
SET
sale_price = sale_price * 10,
purchase_price = purchase_price / 2
WHERE product_type = '厨房用具';
2.8 Pymysql
的使用
在正式介绍pymysql
的用法之前,我们先思考一件事,我们希望借助pymysql
完成什么事情?
之前,我们在命令行下,通过输入SQL语句来完成对数据库和表的增删改查。那么,我们也希望能够在Python下能够完成同样的操作,并且能够返回相应的反馈。具体任务包括:
- 登陆并连接到MySQL下的用户;
- 切换到相应的数据库下;
- 完成对表的增删改查;
接下来的内容将围绕这3部分来介绍。
2.8.1 安装pymysql
通过pip
,我们可以完成对pymysql
的安装:
1 | python3 -m pip install PyMySQL |
2.8.2 连接数据库
如果希望在 Python
中操作 MySQL
数据库,那么首先就要登陆到 MySQL
下的用户。
我们通过创建库 pymysql
下的类 connect
的一个实例来登陆到数据库。
示例:
1 | import pymysql |
参数解释:
host
:数据库服务器地址,默认localhost
;user
:所要登陆的用户名;password
:用户的登录密码;database
:所要连接的数据库库名;charset
:使用的字符类型;cursorclass
:定义游标使用的类型,通过指定游标使用的类型,在返回输出的结果时将按照指定的类型进行返回。例如,这里设置为字典游标。
Results:
1 | pymysql.err.OperationalError: (1044, "Access denied for user 'admin'@'localhost' to database 'shop'") |
这说明给定的权限不足,需要修改相应权限,或赋予相应权限。退出
python
环境,进入 mysql
环境:
1 | grant all on *.* to 'admin'@'localhost'; |
2.8.3 创建游标
关于游标,可以理解为在命令行中的光标。在命令行中,我们是在光标处键入语句的。这里游标的起到类似作用。
1 | # 创建游标 |
实际上,除了在初始化connect
的实例时指定游标类型,我们在初始化游标时也可以指定游标类型,默认为元组类型。
1 | cursor = db.cursor(cursor=pymysql.cursors.DictCursor) |
cursors
共支持四类游标:
Cursor
: 默认,元组类型DictCursor
: 字典类型SSCursor
: 无缓冲元组类型SSDictCursor
: 无缓冲字典类型
2.8.4 类方法
初始化完类connect
和cursor
的实例后,我们先来了解一下这两个类下包含的方法。了解这些方法有利于我们后面在python下操作mysql:
connect
下的类方法:close()
:在完成操作后,需要关闭与数据库之间的连接;commit()
:如果执行语句中发生了数据更改,需要提交更改到稳定的存储器;cursor(cursor=None)
:创建一个游标,前面我们在初始化connect
类是指定了游标类型,通过cursor
初始化游标时,也可以进行游标类型指定;rollback()
:事务回滚;
pymysql.cursors
下的类方法:close()
:结束时,关闭游标;execute()
:通过游标执行语句;executemany()
:通过游标执行多条语句;fetchone()
:获取单条数据;fetchmany(size=None)
:获取size条数据;fetchall()
:获取单条数据;scroll(value, mode)
:数据的查询操作都是基于游标,可以通过scroll
控制游标的位置。mode=absolute
:绝对位置移动,控制游标位置到上一次查询的第value
条数据,最小值为0
;mode=relative
:相对位置移动,基于当前位置,跳过value
条数据;
2.8.5 实战
在此次实战中,我们使用 Ubuntu
平台来进行实战演练,由于未使用图形界面,因此,我们先进行前置工作,安装
vim
来编写 python
脚本文件。
安装
Git
首先我们先安装
git
工具。Git
是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。命令行安装:1
sudo apt-get install git
安装
vim
Vim
是从vi
发展出来的一个文本编辑器。代码补完、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。和Emacs
并列成为类Unix
系统用户最喜欢的编辑器。 安装步骤:(1) 首先将vim的源码克隆下来,这里因为github可能很慢,使用码云的镜像
1
git clone https://gitee.com/mirrors/vim.git
(2) 安装gcc(有则不必安装)和各依赖库
1
2
3sudo apt-get install gcc
sudo apt-get install libncurses5-dev python-dev python3-dev libatk1.0-dev libcairo2-dev libx11-dev libxpm-dev libxt-dev(3) 配置与安装(
vim
目录)1
sudo ./configure --with-features=huge --enable-multibyte --enable-rubyinterp --enable-pythoninterp --enable-python3interp --enable-luainterp --enable-cscope --enable-gui=gtk3 --enable-perlinterp --with-python-config-dir=/usr/lib/python2.7/config-x86_64-linux-gnu/ --with-python3-config-dir=/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu/ --prefix=/usr/local/vim8
- with-features=huge:支持最大特性
- enable-rubyinterp:打开对 ruby 编写的插件的支持
- enable-pythoninterp:打开对 python 编写的插件的支持
- enable-python3interp:打开对 python3 编写的插件的支持
- enable-luainterp:打开对 lua 编写的插件的支持
- enable-perlinterp:打开对 perl 编写的插件的支持
- enable-multibyte:打开多字节支持,可以在 Vim 中输入中文
- enable-cscope:打开对cscope的支持
- enable-gui=gtk3 表示生成采用 GNOME3 风格的 gvim
- with-python-config-dir=/usr/lib/python2.7/config-x86_64-linux-gnu/ 指定 python 路径
- with-python3-config-dir=/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu/ 指定 python3路径(这里可以根据自己的版本做更改)
- prefix=/usr/local/vim8:指定将要安装到的路径
Note: 更多有关
Linux
和Vim
的相关操作,请查看本文博文 Linux notes,此处不再进行赘述。
Case Operation:
在这个示例中,我们将做两件事情:创建表和插入数据。
创建
sql_test1.py
文件1
touch sql_test1.py
使用
vim
进行编辑1
vim sql_test1.py
输入
python
代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49import pymysql
# 以admin身份连接到数据库shop
connection = pymysql.connect(
host='localhost',
user='admin',
password='mysql123',
database='shop',
charset='utf8mb4',
)
# 创建游标
cursor = connection.cursor(cursor=pymysql.cursors.DictCursor)
# 防止再次运行代码时报错,我们加一个删除表格的操作,如果时第一运行改代码,comment 下面两行
sql = 'DROP TABLE Employee'
cursor.execute(sql)
# 1. 创建了一个表
sql = """
CREATE TABLE Employee(
id INT PRIMARY KEY,
name CHAR(15) NOT NULL
)
"""
# 提交执行
cursor.execute(sql)
# 2. 往表中插入数据
sql = "INSERT INTO Employee (id, name) VALUES (%s, %s)"
values = [(1, 'XiaoBai'),
(2, 'XiaoHei'),
(3, 'XiaoHong'),
(4, 'XiaoMei'),
(5, 'XiaoLi')]
try:
# 通过executemany可以插入多条数据
cursor.executemany(sql, values)
# 提交事务
connection.commit()
except:
connection.rollback()
# 3. 关闭光标及连接
cursor.close()
connection.close()运行
sql_test1.py
1
python3 sql_test1.py
进行查询
接下来,继续执行查询工作。首先创建
sql_test2.py
并使用vim
进行编辑:1
2
3touch sql_test2.py
vim sql_test2.py编写代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41import pymysql
# 以admin身份连接到数据库shop
connection = pymysql.connect(
host='localhost',
user='admin',
password='mysql123',
database='shop',
charset='utf8mb4',
)
with connection:
# 创建游标
cursor = connection.cursor(cursor=pymysql.cursors.DictCursor)
# 1. 通过fetchone只查询一条
cursor.execute("SHOW CREATE TABLE Employee")
result = cursor.fetchone()
print(f'查询结果1: \n{result}')
# 2. 通过fetchmany查询size条
cursor.execute("DESC Employee")
result = cursor.fetchmany(size=2)
print(f'查询结果2: \n{result}')
# 3. 通过fetchall查询所有
cursor.execute("SELECT * FROM Employee")
result = cursor.fetchall()
print(f'查询结果3: \n{result}')
# 4. 通过scroll回滚到第0条进行查询
cursor.scroll(0, mode='absolute')
result = cursor.fetchone()
print(f'查询结果4: \n{result}')
# 5. 通过scroll跳过2条进行查询
cursor.scroll(2, mode='relative')
result = cursor.fetchone()
print(f'查询结果5: \n{result}')
cursor.close()运行脚本文件:
1
python3 sql_test2.py
Results:
1
2
3
4
5
6
7
8
9
10查询结果1:
{'Table': 'Employee', 'Create Table': 'CREATE TABLE `Employee` (\n `id` int NOT NULL,\n `name` char(15) NOT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci'}
查询结果2:
[{'Field': 'id', 'Type': 'int', 'Null': 'NO', 'Key': 'PRI', 'Default': None, 'Extra': ''}, {'Field': 'name', 'Type': 'char(15)', 'Null': 'NO', 'Key': '', 'Default': None, 'Extra': ''}]
查询结果3:
[{'id': 1, 'name': 'XiaoBai'}, {'id': 2, 'name': 'XiaoHei'}, {'id': 3, 'name': 'XiaoHong'}, {'id': 4, 'name': 'XiaoMei'}, {'id': 5, 'name': 'XiaoLi'}]
查询结果4:
{'id': 1, 'name': 'XiaoBai'}
查询结果5:
{'id': 4, 'name': 'XiaoMei'}插入数据
接下来使用
sql_test3.py
脚本演示SQL
注入的问题。首先生成脚本文件并进入vim
编辑模式:1
2
3touch sql_test3.py
vim sql_test3.py先建立一个表并插入数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38import pymysql
# 以admin身份连接到数据库shop
connection = pymysql.connect(
host='localhost',
user='admin',
password='mysql123',
database='shop',
charset='utf8mb4',
)
# 创建游标
cursor = connection.cursor(cursor=pymysql.cursors.DictCursor)
# 防止再次运行代码时报错,我们加一个删除表格的操作,如果时第一运行改代码,comment 下面两行
sql = 'DROP TABLE UserInfo'
cursor.execute(sql)
sql = """
CREATE TABLE UserInfo(
id INT PRIMARY KEY,
name VARCHAR(15),
password CHAR(15) NOT NULL
)
"""
cursor.execute(sql)
sql = "INSERT INTO UserInfo (id, name, password) VALUES (%s, %s, %s)"
values = [(1, 'XiaoBai', '123'),
(2, 'XiaoHei', '234'),
(3, 'XiaoHong', '567'),
(4, 'XiaoMei', '321'),
(5, 'XiaoLi', '789')]
cursor.executemany(sql, values)
connection.commit()
cursor.close()读取信息进行登录验证
接下来,我们再写一个程序,根据刚刚插入的数据来判定账号信息是否匹配。
1
2
3touch sql_test4.py
vim sql_test4.py编辑脚本进行账户验证:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28import pymysql
# 以admin身份连接到数据库shop
connection = pymysql.connect(
host='localhost',
user='admin',
password='mysql123',
database='shop',
charset='utf8mb4',
)
# 创建游标
cursor = connection.cursor(cursor=pymysql.cursors.DictCursor)
while True:
user = input("输入用户:").strip()
password = input("输入密码:").strip()
sql = "select name, password from UserInfo where name='%s' and password='%s' " % (user, password)
cursor.execute(sql)
# 打印用户和密码
result=cursor.fetchone()
print(result)
if result:
print("成功登陆\n")
else:
print("登陆失败\n")在控制台下,我们进行了三组用户和密码的验证:
1
2
3
4
5
6
7
8
9
10
11
12
13
14输入用户:XiaoBai
输入密码:123
{'name': 'XiaoBai', 'password': '123'}
成功登陆
输入用户:XiaoBai
输入密码:321
None
登陆失败
输入用户:XiaoBai' -- dsd'
输入密码:321
{'name': 'XiaoBai', 'password': '123'}
成功登陆可以看出,第1组和第2组验证正常,但是第三组出现了异常,输入错误的密码却可以正确登陆。
这是因为在MySQL中
--
的含义是注释,如果通过字符串进行拼接:1
select name, password from UserInfo where name='XiaoBai' -- dsd' and password='321'
实际等价于:
1
sselect name, password from UserInfo where name='XiaoBai'
解决办法:通过
execute
或者executemany
来进行拼接。将语句:1
2sql = "select name, password from UserInfo where name='%s' and password='%s' " % (user, password)
cursor.execute(sql)改为:
1
2sql = "select name, password from UserInfo where name=%s and password=%s"
cursor.execute(sql, (user, password))