本系列包含:
%
通配符_
通配符[]
通配符在学习 MySQL 的基础操作之前,我们应该首先了解一下 MySQL 中的语句执行顺序。
(8) SELECT (9) DISTINCT column,… # (8)选择字段 、(9)去重
(6) AGG_FUNC(column or expression),… # (6)聚合函数
(1) FROM [left_table] # (1)选择表
(3) <join_type> JOIN <right_table> # (3)链接
(2) ON <join_condition> # (2)链接条件
(4) WHERE <where_condition> # (4)条件过滤
(5) GROUP BY <group_by_list> # (5)分组
(7) HAVING <having_condition> # (7)分组过滤
(10) ORDER BY <order_by_list> # (10)排序
(11) LIMIT count OFFSET count; # (11)分页
select name from student
select name, age, class from student
select * from student
即利用 distinct
去重。
select distinct class from student
使用 LIMIT
子句。
select * from student limit 10 offset 1
offset
基数为 0,所以 offset
1 代表从第 2 行开始若使用下面这个语法,逗号之前的值对应 OFFSET
,逗号之后的值对应 LIMIT
。
select * from student limit 1, 10
# 注释 1
-- 注释 2
/* 注释 3
注释 3
注释 3 */
在指定一条 ORDER BY
子句时,应该保证它是 SELECT
语句中最后一条子句。
select prod_name
from Products
order by prod_name
select prod_id, prod_price, prod_name
from Products
order by prod_price, prod_name
select prod_id, prod_price, prod_name
from Products
order by 2, 3
默认排序是 ASC
,所以一般升序的时候不需指定,降序的关键字是 DESC
。如果想在多个列上进行降序排序,必须对每一列指定 DESC
关键字。
select prod_id, prod_price, prod_name
from Products
order by prod_price desc
select prod_id, prod_price, prod_name
from Products
order by prod_price desc, prod_name
# 找到学号为 1 的学生。
select * from student
where number = 1
说明 | 操作符 |
---|---|
等于 | = |
不等于 | <> |
不等于 | != |
小于 | < |
小于等于 | <= |
不小于 | !< |
大于 | > |
大于等于 | >= |
不大于 | !> |
在指定的两个值之间 | BETWEEN |
为 NULL 值 | IS NULL |
# 找到学号为在 [1, 10] 的学生(闭区间)
select * from student
where number between 1 and 10
# 找到未设置电子邮箱的学生,注意不能使用 =
select * from student
where email is null
# 找到一班中大于 23 岁的学生
select * from student
where class_id = 1 and age > 23
# 找到一班或者大于 23 岁的学生
select * from student
where class_id = 1 or age > 22
# 找到一班与二班的学生
select * from student
where class_id in (1, 2)
WHERE
子句中的 NOT
操作符有且只有一个功能,那就是否定其后所跟的任何条件。
# 找到不是一班二班的学生
select * from student
where class_id not in (1, 2)
%
通配符# 所有以词 Fish 起头的产品
select prod_id, prod_name
from Products
where prod_name like 'Fish%'
_
通配符下划线的用途与 %
一样,但它只匹配单个字符,而不是多个字符。
select prod_id, prod_name
from Products
where prod_name like '__ inch teddy bear'
[]
通配符用来指定一个字符集,它必须匹配指定位置(通配符的位置)的一个字符,此通配符可以用前缀字符 ^
(脱字号)来否定。
# 找出所有名字以 J 或 M 起头的联系人
select cust_contact
from Customers
where cust_contact LIKE '[JM]%'
order by cust_contact
select concat(name, '(', age, ')') as nameWithAge
from student;
select concat('hello', 'world') as helloworld
select age - 18 as relativeAge from student;
SELECT prod_id, quantity, item_price, quantity * item_price AS expanded_price
FROM OrderItems
WHERE order_num = 20008:
与 SQL 语句不一样,SQL 函数不是可移植的。
函数 | 语法 |
---|---|
提取字符串的组成部分 | DB2、Oracle、PostgreSQL 和 SQLite 使用 SUBSTR() ;MariaDB、MySQL 和 SQL Server 使用 SUBSTRING() |
数据类型转换 | Oracle 使用多个函数,每种类型的转换有一个函数;DB2 和 PostgreSQL 使用 CAST() 。MariaDB、MySQL 和 SQL Server 使用CONVERT() |
提取当前日期 | DB2 和 PostgreSQL 使用 CURRENT_DATE ;MariaDB 和 MySQL 使用 CURDATE() 。Oracle 使用 SYSDATE ;SQL Server 使用 GETDATE() ;SQLite 使用 DATE() |
SQL 中,关键字与函数名不区分字母的大小写。
函数 | 说明 |
---|---|
LEFT(str, length) |
从左边开始截取 str ,length 是截取的长度 |
RIGHT(str, length) |
从右边开始截取 str ,length 是截取的长度 |
LENGTH(str) |
计算字符串 str 的长度 |
LOCATE(substr, str) |
返回子串 substr 在字符串 str 中第一次出现的位置,如果字符 substr 在字符串 str 中不存在,则返回 0 |
POSITION(substr IN str) |
返回子串 substr 在字符串 str 中第一次出现的位置,如果字符 substr 在字符串 str 中不存在,与 LOCATE 函数作用相同 |
SUBSTRING(str, n, m) |
返回字符串 str 从 第 n 个字符截取,共截取 m 个字符 |
SUBSTRING_INDEX(str, substr, n) |
返回字符 substr 在 str 中第 n 次出现位置之前的字符串 |
REPLACE(str, n, m) |
将字符串 str 中的 n 字符替换成 m 字符 |
TRIM() |
去掉字符串左右两边的空格 |
RTRIM() |
去掉字符串右边的空格 |
LTRIM() |
去掉字符串左边的空格 |
LOWER() |
将字符串转换为小写 |
UPPER() |
将字符串转换为大写 |
函数 | 说明 |
---|---|
YEAR() |
从日期中提取年份 |
MONTH() |
从日期中提取月份 |
DAY() |
从日期中提取天 |
TIMESTAMPDIFF(interval,datetime1,datetime2) |
datetime2 - datetime1 的时间差, 比较的单位 interval 可以为:FRAC_SECOND 、SECOND 、MINUTE 、HOUR 、DAY 、WEEK 、MONTH 、QUARTER 、YEAR |
DATEDIFF(datepart,startdate,enddate) |
department 可为:year 、quarter 、month 、 week 、day 、hour 、minute 、second 、millisecond |
UNIX_TIMESTAMP() |
返回时间戳 |
select order_num
from Orders
where YEAR(order_date) = 2020
# 获取月
MONTH(date);
# 或者
date_format(date,'%m');
# 获取天
DAY(date);
# 或者
date_format(date,'%d');
# 获取两个时间之间相差多少分钟
(UNIX_TIMESTAMP(submit_time) - UNIX_TIMESTAMP(start_time)) / 60
函数 | 说明 |
---|---|
ABS() |
返回一个数的绝对值 |
EXP() |
返回一个数的指数值 |
SQRT() |
返回一个数的平方根 |
聚集函数,一些对数据进行汇总的函数,常见有 COUNT
, MIN
, MAX
, AVG
, SUM
五种。
# 统计1班人数
select count(*) from student
where class_id = 1;
使用 group by
进行数据分组,可以使用聚合函数对分组数据进行汇总,使用 having
对分组数据进行筛选。group by
子句必须出现在 where
子句之后,order by
子句之前。
# 按照班级进行分组并统计各班人数
select class_id, count(*)
from student
group by class_id
# 列出大于三个学生的班级
select class_id, count(*) as cnt
from student
group by class_id
having cnt > 3
作为子查询的 SELECT
语句只能查询单个列。
# 列出软件工程班级中的学生
select * from student
where class_id in (
select id
from class
where class_id = '软件工程'
);
作为计算字段使用子查询
SELECT cust_name, cust_state, (
SELECT COUNT(*)
FROM Orders
WHERE Orders.cust_id = Customers.cust_id) AS orders
FROM Customers
ORDER BY cust_name;
要保证所有联结都有 WHERE
子句,否则 DBMS 将返回比想要的数据多得多的数据。同理,要保证 WHERE
子句的正确性。
# 列出软件工程班级中的学生
select *
from student, class
where student.class_id = class.id and class.name = '软件工程';
虽然两个表拥有公共字段便可以创建联结,但是使用外键可以更好地保证数据完整性。比如当对一个学生插入一条不存在的班级的时候,便会插入失败。一般来说,联结比子查询拥有更好的性能。
目前为止使用的联结称为等值联结,它基于两个表之间的相等测试。这种联结也称为内联结 inner join
。其实,可以对这种联结使用稍微不同的语法,明确指定联结的类型。下面的 SELECT
语句返回与前面例子完全相同的数据。
# 列出软件工程班级中的学生
select *
from student
inner join class on student.class_id = class.id
where class.name = '软件工程';
假如要给与 Jim Jones 同一公司的所有顾客发送一封信件。这个查询要求首先找出 Jim Jones 工作的公司,然后找出在该公司工作的顾客。
# 列出与张三同一班级的学生
SELECT cust_id, cust_name, cust_contact
FROM Customers
WHERE cust_name = (
SELECT cust_name
FROM Customers
WHERE cust_contact ='Jim Jones'
)
使用自联结
SELECT cl.cust_id, cl.cust_name, cl.cust_contact
FROM Customers AS cl, Customers AS c2
WHERE cl.cust_name = c2.cust_name AND c2.cust_contact = 'Jim Jones'
此查询中需要的两个表实际上是相同的表,因此 Customers 表在 FROM 子句中出现了两次。虽然这是完全合法的,但对 Customers 的引用具有歧义性,因为 DBMS 不知道你引用的是哪个 Customers 表。
无论何时对表进行联结,应该至少有一列不止出现在一个表中(被联结的列)。标准的联结(前一课中介绍的内联结)返回所有数据,相同的列甚至多次出现。自然联结排除多次出现,使每一列只返回一次。
怎样完成这项工作呢?答案是,系统不完成这项工作,由你自己完成它。自然联结要求你只能选择那些唯一的列,一般通过对一个表使用通配符 SELECT *
,而对其他表的列使用明确的子集来完成。
SELECT C. *, O.order_num, O.order_date, OI.prod_id, OI. quantity, OI.item_price
FROM Customers AS C, Orders AS O, OrderItems AS OI
WHERE C.cust_id = O.cust_id AND OI.order_num = 0.order_num AND prod_id = 'RGANO1'
许多联结将一个表中的行与另一个表中的行相关联,但有时候需要包含没有关联行的那些行。例如,可能需要使用联结完成以下工作:
在上述例子中,联结包含了那些在相关表中没有关联行的行。这种联结称为外联结。外连接只限制一张表中的数据必须满足连接条件,而另一张表中数据可以不满足连接条件。语法格式为:
SELECT ...
FROM 表1
LEFT | RIGHT [OUTER] JOIN 表2
ON <连接条件>
left join
:包含左表的所有行,对应的右表行可能为空right join
:包含右表的所有行,对应的左表行可能为空full join
:左右表格的行全部都有,左右表格判断一致的在同一行,不一致的单独一行-- 列出每个学生的班级,若没有班级则为 null
select name, class.name
from student
left join class on student.class_id = class.id;
假如需要 Illinois、Indiana 和 Michigan 等美国几个州的所有顾客的报表,还想包括不管位于哪个州的所有的 Fun4All。
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
UNION
从查询结果集中自动去除了重复的行;如果想返回所有的匹配行,可使用 UNION ALL
。
SELECT
语句的输出用 ORDER BY
子句排序。在用 UNION
组合查询时,只能使用一条 ORDER BY
子句,它必须位于最后一条 SELECT
语句之后。
插入单条语句。
INSERT INTO table_name ( field1, field2,...fieldN ) VALUES ( value1, value2,...valueN );
可以采用以下方法插入一条数据。
insert into student values (8, '陆小凤', 24, 1, 3);
不过上面的做法严重依赖表中列的顺序关系,推荐指定列名插入数据,并且可以插入部分列。
insert into student(name, age, sex, class_id) values(9, '花无缺', 25, 1, 3);
插入多条语句。
INSERT INTO table_name (a,b,c) VALUES (1,2,3), (4,5,6), (7,8,9);
带更新的插入
REPLACE INTO table_name VALUES (value1, value2, ...)
有一种数据插入不使用 INSERT
语句。要将一个表的内容复制到一个全新的表(运行中创建的表),可以使用 CREATE SELECT
语句。
CREATE TABLE CustCopy AS SELECT * FROM Customers;
# 修改张三的班级
update student
set class_id = 2
where name = '张三'
# 删除张三的数据
delete from student where name = '张三'
# 删除表中所有数据
delete from student
# 更快地删除表中所有数据
truncate table student
# 创建学生表
create table student (
id int(11) not null auto_increment,
name varchar(50) not null,
age smallint default 20,
sex enum('male', 'famale'),
score tinyint comment '入学成绩',
class_id int(11),
createTime timestamp default current_timestamp,
primary key (id),
foreign key (class_id) references class (id)
);
根据旧表创建新表
create table student_copy as select * from student;
# 删除 age 列
alter table student
drop column age;
# 添加 age 列
alter table student
add column age smallint
# 在某列前插入列
alter table table_name
add column column_name 数据类型 AFTER 列名
# 修改某列名字和数据类型
alter table table_name
change 原来的列名 新列名 数据类型
# 为某列添加默认值
alter table table_name
modify coulmn 列名 数据类型 default 默认值
# 修改表名
alter table 表名 rename (to) 新表名
# 将某一列放到第一列
alter table 表名 modify column 列名 类型 first
drop table if exists student
视图是一种虚拟的表,便于更好地在多个表中检索数据,视图也可以作写操作,不过最好作为只读。在需要多个表联结的时候可以使用视图。
create view v_student_with_classname as
select student.name name, class.name class_name
from student
left join class
where student.class_id = class.id;
select *
from v_student_with_classname
因为视图不包含数据,所以每次使用视图时,都必须处理查询执行时需要的所有检索。如果你用多个联结和过滤创建了复杂的视图或者嵌套了视图,性能可能会下降得很厉害。因此,在部署使用了大量视图的应用前,应该进行测试。
存储过程可以视为一个函数,根据输入执行一系列的 sql 语句。存储过程也可以看做对一系列数据库操作的封装,一定程度上可以提高数据库的安全性。
# 创建存储过程
create procedure create_student(name varchar(50))
begin
insert into students(name) values (name);
end;
# 调用存储过程
call create_student('shanyue');
表中任意列只要满足以下条件,都可以用于主键。
NULL
值)。任意两行绝对没有相同的主键,且任一行不会有两个主键且主键绝不为空。使用主键可以加快索引。
# primiry key
alter table student add constraint primary key (id);
外键可以保证数据的完整性,有助防止意外删除。
alter table student add constraint
foreign key (class_id) references class (id);
唯一约束用来保证一列(或一组列)中的数据是唯一的。它们类似于主键,但存在以下重要区别。
NULL
值。alter table student add constraint unique key (name);
检查约束可以使列满足特定的条件,如果学生表中所有的人的年龄都应该大于 0。不过很可惜 mysql 不支持,可以使用触发器代替。
alter table student add constraint check (age > 0);
索引用来排序数据以加快搜索和排序操作的速度。
create index index_on_student_name on student (name);
alter table student add constraint key(name);
例:现有一张试卷信息表 examination_info
,要求如下。
duration
列创建普通索引 idx_duration
exam_id
列创建唯一性索引 uniq_idx_exam_id
tag
列创建全文索引 full_idx_tag
-- 普通索引
ALTER TABLE examination_info
ADD INDEX idx_duration(duration);
-- 唯一索引
ALTER TABLE examination_info
ADD UNIQUE INDEX uniq_idx_exam_id(exam_id);
-- 全文索引
ALTER TABLE examination_info
ADD FULLTEXT INDEX full_idx_tag(tag);
或
create index idx_duration on examination_info(duration);
create unique index uniq_idx_exam_id on examination_info(exam_id);
create fulltext index full_idx_tag on examination_info(tag);
删除索引
drop index 索引名 on 表名
alter table 表名 drop index 索引名
触发器是特殊的存储过程。可以在插入,更新,删除行的时候触发事件。
与存储过程不一样(存储过程只是简单的存储 SQL 语句),触发器与单个的表相关联。与 Orders 表上的 INSERT
操作相关联的触发器只在 Orders 表中插入行时执行。类似地,Customers 表上的 INSERT
和 UPDATE
操作的触发器只在表上出现这些操作时执行。
触发器内的代码具有以下数据的访问权。
INSERT
操作中的所有新数据;UPDATE
操作中的所有新数据和旧数据;DELETE
操作中删除的数据。下面是触发器的一些常见用途。
INSERT
或 UPDATE
操作中将所有州名转换为大写。# 创建触发器
# 比如 mysql 中没有 check 约束,可以使用创建触发器,当插入数据小于 0 时,置为 0。
create trigger reset_age before insert on student for each row
begin
if NEW.age < 0 then set NEW.age = 0;
end if;
end;
# 打印触发器列表
show triggers;
一般来说,约束的处理比触发器快,因此在可能的时候,应该尽量使用约束。
[1]《SQL必知必会》(第5版) 作者:本·福达
[2] 8000字《SQL必知必会》精华笔记