SQL基础
SQL语句主要分为3个类别:
DDL(Data Definition Language)语句:数据定义语句,定义了不同的数据段、数据库、表、列、索引等数据对象。常用的语句关键字包括
create
、drop
、alter
等。DML(Data Manipulation Language)语句:数据操纵语句,用于添加、删除、更新和查询数据库记录,并检查数据完整性。常用的语句关键字包括
insert
、delete
、update
和select
等。DCL(Data Control Language)语句:数据控制语句,用于控制不同数据段的直接的许可和访问级别的语句。这些语句定义了数据库、表、字段、用户的访问权限和安全级别。常用的语句关键字包括
grant
、revoke
等。
DDL语句
创建数据库
1 | CREATE DATABASE dbname; |
要知道系统中都存在哪些数据库:
1 | SHOW DATABASES; |
选择要操作的数据库:
1 | USE dbname; |
删除数据库
1 | DROP DATABASE dbname; |
创建表
1 | CREATE TABLE tablename ( |
表创建完毕后,要查看表的定义:
1 | DESC tablename; |
为了得到更全面的表定义信息,有时需要查看创建表的SQL语句:
1 | SHOW CREATE TABLE tablename; |
删除表
1 | DROP TABLE tablename; |
修改表
修改表类型
1
ALTER TABLE tablename MODIFY [COLUMN] column_definition;
例如,修改表
emp
的ename
字段定义,改为varchar(20)
:1
alter table emp modify ename varchar(20);
添加表字段
1
ALTER TABLE tablename ADD [COLUMN] column_definition;
例如,在表
emp
中新添加字段age
,类型为int(3)
:1
alter table emp add age int(3);
删除表字段
1
ALTER TABLE tablename DROP [COLUMN] col_name;
例如,将字段
age
删除掉:1
alter table emp drop age;
字段改名
1
ALTER TABLE tablename CHANGE [COLUMN] old_col_name column_definition;
例如,将
age
改为age1
,同时修改字段类型为int(4)
:1
alter table emp change age age1 int(4);
change
和modify
都可以修改表的定义,不同的是change
后面需要写两次列名。但change
的优点是可以修改列名称,而modify
不能。修改字段排列顺序(MySQL扩展)
ADD
增加的新字段默认加在表的最后位置,而CHANGE
和MODIFY
默认不改变字段的位置。添加和修改语法都有一个可选项FIRST | AFTER column_name
,可以用来修改字段在表中的位置。例如,将新增的字段
birth date
加载ename
之后:1
alter table emp add birth date after ename;
修改
age
字段,把它放在最前面:1
alter table emp modify age int(3) first;
更改表名
1
ALTER TABLE tablename RENAME [TO] new_tablename;
DML语句
插入记录
1 | INSERT INTO tablename (field1, field2, ..., fieldn) |
也可以不用指定字段名称,但是values
后面的顺序应与字段的排列顺序一致。
含可空的字段、非空但是含有默认值的字段以及自增字段,可以不在insert
后的字段列表中出现,values
后面只写对应字段名称的值。没写的字段自动设置为NULL、默认值、自增的下一个数字。
在 MySQL 中,可以一次性插入多条记录:
1 | INSERT INTO tablename (field1, field2, ..., fieldn) |
更新记录
1 | UPDATE tablename |
在 MySQL 中,update
命令可以同时更新多个表中的数据:
1 | update t1, t2, ..., tn set t1.field1=expr1, ..., tn.fieldn=exprn |
删除记录
1 | DELETE FROM tablename |
在 MySQL 中,可以一次删除多个表的数据:
1 | DELETE t1, t2, ..., tn FROM t1, t2, ..., tn |
如果from
后面的表名用别名,则delete
后面也要用相应的别名,否则会提示语法错误。
查询记录
基本语法:
1 | SELECT * FROM tablename |
查询不重复的记录:
1 | SELECT DISTINCT colname FROM tablename; |
排序与限制:
1 | SELECT * FROM tablename |
DESC
表示降序排列,如果不写排序关键字,默认为ASC
升序排列。每个排序字段可以有不同的排列顺序。
如果排序字段的值一样,则继续按照第二个排序字段进行排序,以此类推。如果后续没有排序字段,则前面的字段相同的记录会无序排列。
对于排序后的记录,如果希望只显示一部分,可以使用LIMIT
关键字来实现:
1 | SELECT ... |
offset_start
表示起始偏移量,row_count
表示显示的行数。默认情况下起始偏移量为0,只需要写行数就可以,表示前n条记录。
聚合
1 | SELECT [field1, field2, ..., fieldn] fun_name |
fun_name
表示聚合函数,常用的有sum
、count
、avg
、max
、min
。GROUP BY
表示要进行分类聚合的字段。WITH ROLLUP
是可选参数,表明对分类聚合后的结果进行再汇总。HAVING
表示对分类后的结果进行条件的过滤。
HAVING
和WHERE
的区别在于,HAVING
是对聚合后的结果进行条件的过滤,而WHERE
是在聚合前就对记录进行过滤。如果逻辑允许,尽可能用WHERE
先过滤记录,因为这样结果集减小,聚合的效率将大大提高,然后再根据逻辑看是否用HAVING
再进行过滤。
表连接
最常用的是内连接。例如:
1 | select ename, deptname from emp, dept |
外连接又分为左连接和右连接,二者可以相互转化:
1 | select ename, deptname from emp left join dept on emp.deptno=dept.deptno; |
子查询
进行查询的时候,需要的条件是另外一个select
语句的结果。用于子查询的关键字主要包括in
、not in
、=
、!=
、exists
、not exists
等。
例如,从emp
表中查询出所以部门在dept
表中的所有记录:
1 | select * from emp where deptnp in ( |
如果子查询记录唯一,可以用=
代替in
。
某些情况下,子查询可以转化为表连接:
1 | select * from emp where deptno in (select deptno from dept); |
记录联合
将多个表的数据按照一定的查询条件查询出来后,将结果合并到一起显示出来,需要用到union
和union all
关键字。
1 | SELECT * FROM t1 |
UNION ALL
是把结果集直接合并到一起,而UNION
是把UNION ALL
后的结果进行一次DISTINCT
即去重。
DCL语句
创建一个用户z1
,具有对sakila
数据库中的所有表的SELECT/INSERT
权限:
1 | grant select, insert on sakila.* |
由于权限变更,收回INSERT
,只能进行SELECT
操作:
1 | revoke insert on sakila.* from 'z1'@'localhost'; |
复杂查询
视图
视图保存的是SELECT
语句,从视图中读取数据时,视图会在内部执行SELECT
语句并创建一张临时表。这样有两个优点:
由于无需保存数据,因此可以节省存储设备的容量。
可以保存频繁使用的
SELECT
语句,不用每次都重新书写了,而且会随着原表的变化自动更新。
1 | CREATE VIEW view_name(col1, col2, ...) |
SELECT
语句中列的排列顺序,要与视图中列的排列顺序相同。
视图的限制:
定义时不能使用
ORDER BY
子句更新有严格限制,只有当定义视图的
SELECT
语句满足以下条件:SELECT
子句中未使用DISTINCT
FROM
子句中只有一张表未使用
GROUP BY
子句未使用
HAVING
子句
删除视图:
1 | DROP VIEW [IF EXISTS] view_name; |
高级处理
窗口函数
语法
窗口函数的通用形式:
1 | <窗口函数> OVER ([ PARTITION BY <列名> ] |
PARTITON BY
子句(可选参数)指示如何将查询行划分为组,类似于GROUP BY
子句的分组功能,但是 PARTITION BY
子句并不具备GROUP BY
子句的汇总功能,并不会改变原始表中记录的行数。
ORDER BY
子句指示如何对每个分区中的行进行排序,即决定窗口内,是按哪种规则(字段)来排序的。
以窗口函数RANK()
为例:
1 | SELECT product_name, product_type, sale_price, |
PARTITION BY
能够设定窗口对象范围。本例中,为了按照商品种类进行排序,我们指定了product_type
。即一个商品种类就是一个小的“窗口”。
ORDER BY
能够指定按照哪一列、何种顺序进行排序。为了按照销售单价的升序进行排列,我们指定了sale_price
。
专用窗口函数
常用的有:
RANK()
:计算排序时,如果存在相同位次的记录,则会跳过之后的位次。例如有3个记录在第1位:1 1 1 4 …DENSE_RANK()
:计算排序,即使存在相同位次的记录,也不会跳过之后的位次。例如有3个记录在第1位:1 1 1 2 …ROW_NUMBER()
:赋予唯一的连续位次。例如有3个记录在第1位:1 2 3 4 …
1 | SELECT product_name, product_type, sale_price, |
作为窗口函数使用的聚合函数
语法与专用窗口函数相同。但不像RANK()
那样括号中的内容为空,而是与之前一样需要指定列。
1 | SELECT product_id, product_name, sale_price, |
计算移动平均
窗口函数就是将表以窗口为单位进行分割,并在其中排序或聚合的函数。实际上,还可以指定更加详细的汇总范围,称为框架 (frame)。
1 | <窗口函数> OVER (ORDER BY <排序用列名> |
PRECEDING
将框架指定为“截止到之前 n 行”,加上自身行;
FOLLOWING
将框架指定为“截止到之后 m 行”,加上自身行。
1 | SELECT product_id, product_name, sale_price, |
1 | SELECT product_id, product_name, sale_price, |
框架指定为 “之前1行” + “之后1行” + “自身”。
注意事项
- 原则上,窗口函数只能在SELECT子句中使用。
- 窗口函数
OVER
中的ORDER BY
子句并不会影响最终结果的排序,只是用来决定窗口函数按何种顺序计算。
存储过程
基本语法
1 | [delimiter //]($$,可以是其他特殊字符,为了替换; ) |
这些语句被用来创建一个存储例程(或函数)。也就是说,指定的例程被服务器知道了。默认情况下,一个存储例程与默认数据库相关联。要将该例程明确地与一个给定的数据库相关联,需要在创建该例程时将其名称指定为 db_name.sp_name
。
使用 CALL
语句调用一个存储过程。而要调用一个存储的函数时,则要在表达式中引用它。在表达式计算期间,该函数返回一个值。
routine_body
由一个有效的SQL例程语句组成。它可以是一个简单的语句,如 SELECT
或 INSERT
,或一个使用 BEGIN
和 END
编写的复合语句。复合语句可以包含声明、循环和其他控制结构语句。在实践中,存储函数倾向于使用复合语句,除非例程主体由一个 RETURN
语句组成。
参数介绍
存储过程和函数的参数有三类,分别是:IN
,OUT
,INOUT
,其中:
IN
是入参。每个参数默认都是一个IN
参数。如需设定一个参数为其他类型参数,请在参数名称前使用关键字OUT
或INOUT
。一个IN参数将一个值传递给一个过程。存储过程可能会修改这个值,但是当存储过程返回时,调用者不会看到这个修改。OUT
是出参。一个OUT
参数将一个值从过程中传回给调用者。它的初始值在过程中是NULL
,当过程返回时,调用者可以看到它的值。INOUT
:一个INOUT
参数由调用者初始化,可以被存储过程修改,当存储过程返回时,调用者可以看到存储过程的任何改变。
对于每个 OUT
或 INOUT
参数,在调用过程的 CALL
语句中传递一个用户定义的变量,以便在过程返回时可以获得其值。如果你是在另一个存储过程或函数中调用存储过程,你也可以将一个常规参数或本地常规变量作为 OUT
或 INOUT
参数传递。如果从一个触发器中调用存储过程,也可以将 NEW.col_name
作为一个 OUT
或 INOUT
参数传递。
应用示例
给定一个国家代码,计算在 world
数据库的城市表中出现的该国家的城市数量。使用 IN
参数传递国家代码,使用 OUT
参数返回城市计数:
1 | mysql> DELIMITER // |
创建表:
1 | mysql> use world; |
插入数据:
1 | CREATE DEFINER=`root`@`localhost` PROCEDURE `insert_product_test`() |