sql笔记
SQL关键字总是大写,以示突出,表名和列名均使用小写。
# 1 关系模型
表的每一行称为记录(Record),记录是一个逻辑意义上的数据。
表的每一列称为字段(Column),同一个表的每一行记录都拥有相同的若干字段。
在关系数据库中,关系是通过主键和外键来维护的。
# 1.1 主键
主键是关系表中记录的唯一标识。主键的选取非常重要:主键不要带有业务含义,而应该使用BIGINT自增AUTO_INCREMENT或者GUID类型。主键也不应该允许NULL
。
id BIGINT NOT NULL AUTO_INCREMENT
PRIMARY KEY (id)
# 1.2 外键
外键
可以把数据与另一张表关联起来
外键并不是通过列名实现的,而是通过定义外键约束实现的
ALTER TABLE students
ADD CONSTRAINT fk_class_id
FOREIGN KEY (class_id)
REFERENCES classes (id);
#其中,外键约束的名称fk_class_id可以任意,FOREIGN KEY (class_id)指定了class_id作为外键,REFERENCES classes (id)指定了这个外键将关联到classes表的id列(即classes表的主键)。
#要删除一个外键约束,也是通过ALTER TABLE实现的:
ALTER TABLE students
DROP FOREIGN KEY fk_class_id;
# 1.3 索引
索引是关系数据库中对某一列或多个列的值进行预排序的数据结构。通过使用索引,可以让数据库系统不必扫描整个表,而是直接定位到符合条件的记录,这样就大大加快了查询速度。
ALTER TABLE students
ADD INDEX idx_score (score);
可以对一张表创建多个索引。索引的优点是提高了查询效率,缺点是在插入、更新和删除记录时,需要同时修改索引,因此,索引越多,插入、更新和删除记录的速度就越慢。
对于主键,关系数据库会自动对其创建主键索引。使用主键索引的效率是最高的,因为主键会保证绝对唯一。
# 1.4 唯一索引
我们假设students
表的name
不能重复:
ALTER TABLE students
ADD UNIQUE INDEX uni_name (name);
通过UNIQUE
关键字我们就添加了一个唯一索引。
也可以只对某一列添加一个唯一约束而不创建唯一索引:
ALTER TABLE students
ADD CONSTRAINT uni_name UNIQUE (name);
这种情况下,name
列没有索引,但仍然具有唯一性保证。
# 2 查询数据
# 2.1 基本查询
#----基本查询----#
SELECT * FROM <表名> # *表示所有列
SELECT 1 #测试数据库连接,不带FROM子句的SELECT语句有一个有用的用途,就是用来判断当前到数据库的连接是否有效。
# 2.2 条件查询
#----条件查询----#
#----SELECT语句可以通过WHERE条件来设定查询条件,查询结果是满足查询条件的记录----#
SELECT * FROM <表名> WHERE <条件表达式> #条件表达式可以用<条件1> AND <条件2>表达满足条件1并且满足条件2 OR <> NOT
## 使用LIKE判断相似 name LIKE 'ab%' name LIKE '%bc%' %表示任意字符,例如'ab%'将匹配'ab','abc','abcd'
# 2.3 投影查询
##如果我们只希望返回某些列的数据,而不是所有列的数据,我们可以用`SELECT 列1, 列2, 列3 FROM ...`,让结果集仅包含指定列。这种操作称为投影查询。
##使用`SELECT 列1, 列2, 列3 FROM ...`时,还可以给每一列起个别名,这样,结果集的列名就可以与原表的列名不同。它的语法是`SELECT 列1 别名1, 列2 别名2, 列3 别名3 FROM ...`。
SELECT id, score points, name FROM students;
# 2.4 排序
##使用ORDER BY可以对结果集进行排序,默认从低到高进行排序
##从高到底排序,我们可以加上DESC表示“倒序”
SELECT id, name, gender, score
FROM students
ORDER BY score DESC;
# 2.5 分页查询
##使用LIMIT <M> OFFSET <N>可以对结果集进行分页,每次查询返回结果集的一部分;分页查询需要先确定每页的数量和当前页数,然后确定LIMIT和OFFSET的值。
##注意SQL记录集的索引从0开始。
分页查询的关键在于,首先要确定每页需要显示的结果数量pageSize
(这里是3),然后根据当前页的索引pageIndex
(从1开始),确定LIMIT
和OFFSET
应该设定的值:
LIMIT
总是设定为pageSize
;OFFSET
计算公式为pageSize * (pageIndex - 1)
。
# 2.6 聚合查询
SELECT COUNT(*) FROM students; #计算所有行的总数
SELECT COUNT(*) num FROM students; #使用聚合查询并设置结果集的列名为num
# SUM 计算某一列的合计值,该列必须为数值类型
# AVG 计算某一列的平均值,该列必须为数值类型
# MAX 计算某一列的最大值
# MIN 计算某一列的最小值
SELECT AVG(score) average FROM students WHERE gender = 'M';
#如果聚合查询的WHERE条件没有匹配到任何行,COUNT()会返回0,而SUM()、AVG()、MAX()和MIN()会返回NULL:
SELECT AVG(score) average FROM students WHERE gender = 'X'; #NULL
##分组
SELECT class_id, COUNT(*) num FROM students GROUP BY class_id;
SELECT class_id, gender, COUNT(*) num FROM students GROUP BY class_id, gender; #按class_id, gender分组
# 2.7 多表查询
SELECT
s.id sid,
s.name,
s.gender,
s.score,
c.id cid,
c.name cname
FROM students s, classes c
WHERE s.gender = 'M' AND c.id = 1; #起别名
# 2.8 连接查询
## 连接查询是另一种类型的多表查询。连接查询对多个表进行JOIN运算,简单地说,就是先确定一个主表作为结果集,然后,把其他表的行有选择性地“连接”在主表结果集上。
注意INNER JOIN(内连接)查询的写法是:
- 先确定主表,仍然使用
FROM <表1>
的语法; - 再确定需要连接的表,使用
INNER JOIN <表2>
的语法; - 然后确定连接条件,使用
ON <条件...>
,这里的条件是s.class_id = c.id
,表示students
表的class_id
列与classes
表的id
列相同的行需要连接; - 可选:加上
WHERE
子句、ORDER BY
等子句。
RIGHT OUTER JOIN,LEFT OUTER JOIN,以及FULL OUTER JOIN的区别是:
INNER JOIN只返回同时存在于两张表的行数据,由于
students
表的class_id
包含1,2,3,classes
表的id
包含1,2,3,4,所以,INNER JOIN根据条件s.class_id = c.id
返回的结果集仅包含1,2,3。RIGHT OUTER JOIN返回右表都存在的行。如果某一行仅在右表存在,那么结果集就会以
NULL
填充剩下的字段。LEFT OUTER JOIN则返回左表都存在的行。如果我们给students表增加一行,并添加class_id=5,由于classes表并不存在id=5的行,所以,LEFT OUTER JOIN的结果会增加一行,对应的
class_name
是NULL
FULL OUTER JOIN,它会把两张表的所有记录全部选择出来,并且,自动把对方不存在的列填充为NULL
# 3 修改数据
# 3.1 INSERT
# INSERT语句的基本语法是:
INSERT INTO <表名> (字段1, 字段2, ...) VALUES (值1, 值2, ...);
INSERT INTO students (class_id, name, gender, score) VALUES
(1, '大宝', 'M', 87),
(2, '二宝', 'M', 81);
# 3.2 UPDATE
# UPDATE语句的基本语法是:
UPDATE <表名> SET 字段1=值1, 字段2=值2, ... WHERE ...;
#更新id=1的记录
UPDATE students SET name='大牛', score=66 WHERE id=1;
#查询并观察结果:
SELECT * FROM students WHERE id=1;
# 3.3 DELETE
# DELETE语句的基本语法是:
DELETE FROM <表名> WHERE ...;
#删除id=1的记录
DELETE FROM students WHERE id=1;
#查询并观察结果:
SELECT * FROM students;
# 4 管理MySQL
# 4.1 数据库
#列出所有数据库,使用命令:
mysql> SHOW DATABASES;
#要创建一个新数据库,使用命令:
mysql> CREATE DATABASE test;
#要删除一个数据库,使用命令:
mysql> DROP DATABASE test;
#对一个数据库进行操作时,要首先将其切换为当前数据库:
mysql> USE test;
# 4.2 表
#列出当前数据库的所有表,使用命令:
mysql> SHOW TABLES;
#要查看一个表的结构,使用命令:
mysql> DESC students;
#查看创建表的SQL语句:
mysql> SHOW CREATE TABLE students;
#删除表使用DROP TABLE语句:
mysql> DROP TABLE students;
#修改表比较复杂,如果要给students表新增一列birth,使用:
ALTER TABLE students ADD COLUMN birth VARCHAR(10) NOT NULL;
#要修改birth列,例如把列名改为birthday,类型改为VARCHAR(20):
ALTER TABLE students CHANGE COLUMN birth birthday VARCHAR(20) NOT NULL;
#要删除列,使用:
ALTER TABLE students DROP COLUMN birthday;
# 5 使用SQL语句
# 5.1 插入或替换
如果我们希望插入一条新记录(INSERT),但如果记录已经存在,就先删除原记录,再插入新记录。此时,可以使用REPLACE
语句,这样就不必先查询,再决定是否先删除再插入:
REPLACE INTO students (id, class_id, name, gender, score) VALUES (1, 1, '小明', 'F', 99);
若id=1
的记录不存在,REPLACE
语句将插入新记录,否则,当前id=1
的记录将被删除,然后再插入新记录。
# 5.2 插入或更新
如果我们希望插入一条新记录(INSERT),但如果记录已经存在,就更新该记录,此时,可以使用INSERT INTO ... ON DUPLICATE KEY UPDATE ...
语句:
INSERT INTO students (id, class_id, name, gender, score) VALUES (1, 1, '小明', 'F', 99) ON DUPLICATE KEY UPDATE name='小明', gender='F', score=99;
若id=1
的记录不存在,INSERT
语句将插入新记录,否则,当前id=1
的记录将被更新,更新的字段由UPDATE
指定。
# 5.3 插入或忽略
如果我们希望插入一条新记录(INSERT),但如果记录已经存在,就啥事也不干直接忽略,此时,可以使用INSERT IGNORE INTO ...
语句:
INSERT IGNORE INTO students (id, class_id, name, gender, score) VALUES (1, 1, '小明', 'F', 99);
若id=1
的记录不存在,INSERT
语句将插入新记录,否则,不执行任何操作。
# 5.4 快照
如果想要对一个表进行快照,即复制一份当前表的数据到一个新表,可以结合CREATE TABLE
和SELECT
:
-- 对class_id=1的记录进行快照,并存储为新表students_of_class1:
CREATE TABLE students_of_class1 SELECT * FROM students WHERE class_id=1;
新创建的表结构和SELECT
使用的表结构完全一致。
# 5.5 写入查询结果集
如果查询结果集需要写入到表中,可以结合INSERT
和SELECT
,将SELECT
语句的结果集直接插入到指定表中。
例如,创建一个统计成绩的表statistics
,记录各班的平均成绩:
CREATE TABLE statistics (
id BIGINT NOT NULL AUTO_INCREMENT,
class_id BIGINT NOT NULL,
average DOUBLE NOT NULL,
PRIMARY KEY (id)
);
然后,我们就可以用一条语句写入各班的平均成绩:
INSERT INTO statistics (class_id, average) SELECT class_id, AVG(score) FROM students GROUP BY class_id;
确保INSERT
语句的列和SELECT
语句的列能一一对应,就可以在statistics
表中直接保存查询的结果。
# 5.6 强制使用指定索引
在查询的时候,数据库系统会自动分析查询语句,并选择一个最合适的索引。但是很多时候,数据库系统的查询优化器并不一定总是能使用最优索引。如果我们知道如何选择索引,可以使用FORCE INDEX
强制查询使用指定的索引。例如:
> SELECT * FROM students FORCE INDEX (idx_class_id) WHERE class_id = 1 ORDER BY id DESC;
指定索引的前提是索引idx_class_id
必须存在。
# 6 事务
数据库事务具有ACID特性,用来保证多条SQL的全部执行。
- A:Atomic,原子性,将所有SQL作为原子工作单元执行,要么全部执行,要么全部不执行;
- C:Consistent,一致性,事务完成后,所有数据的状态都是一致的,即A账户只要减去了100,B账户则必定加上了100;
- I:Isolation,隔离性,如果有多个事务并发执行,每个事务作出的修改必须与其他事务隔离;
- D:Duration,持久性,即事务完成后,对数据库数据的修改被持久化存储。
要手动把多条SQL语句作为一个事务执行,使用BEGIN
开启一个事务,使用COMMIT
提交一个事务,这种事务被称为显式事务,例如,把上述的转账操作作为一个显式事务:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
很显然多条SQL语句要想作为一个事务执行,就必须使用显式事务。
COMMIT
是指提交事务,即试图把事务内的所有SQL所做的修改永久保存。如果COMMIT
语句执行失败了,整个事务也会失败。
有些时候,我们希望主动让事务失败,这时,可以用ROLLBACK
回滚事务,整个事务会失败:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
ROLLBACK;
对于两个并发执行的事务,如果涉及到操作同一条记录的时候,可能会发生问题。因为并发操作会带来数据的不一致性,包括脏读、不可重复读、幻读等。数据库系统提供了隔离级别来让我们有针对性地选择事务的隔离级别,避免数据不一致的问题。
SQL标准定义了4种隔离级别,分别对应可能出现的数据不一致的情况:
Isolation Level | 脏读(Dirty Read) | 不可重复读(Non Repeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
Read Uncommitted | Yes | Yes | Yes |
Read Committed | - | Yes | Yes |
Repeatable Read | - | - | Yes |
Serializable | - | - | - |
# 6.1 Read Uncommitted
Read Uncommitted是隔离级别最低的一种事务级别。在这种隔离级别下,一个事务会读到另一个事务更新后但未提交的数据,如果另一个事务回滚,那么当前事务读到的数据就是脏数据,这就是脏读(Dirty Read)。
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
# 6.2 Read Committed
在Read Committed隔离级别下,一个事务可能会遇到不可重复读(Non Repeatable Read)的问题。不可重复读是指,在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
# 6.3 Repeatable Read
在Repeatable Read隔离级别下,一个事务可能会遇到幻读(Phantom Read)的问题。幻读是指,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了。
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
# 6.4 Serializable
Serializable是最严格的隔离级别。在Serializable隔离级别下,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。虽然Serializable隔离级别下的事务具有最高的安全性,但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用Serializable隔离级别。
如果没有指定隔离级别,数据库就会使用默认的隔离级别。在MySQL中,如果使用InnoDB,默认的隔离级别是Repeatable Read。
-- 如果test数据库不存在,就创建test数据库:
CREATE DATABASE IF NOT EXISTS test;
-- 切换到test数据库
USE test;
-- 删除classes表和students表(如果存在):
DROP TABLE IF EXISTS classes;
DROP TABLE IF EXISTS students;
-- 创建classes表:
CREATE TABLE classes (
id BIGINT NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 创建students表:
CREATE TABLE students (
id BIGINT NOT NULL AUTO_INCREMENT,
class_id BIGINT NOT NULL,
name VARCHAR(100) NOT NULL,
gender VARCHAR(1) NOT NULL,
score INT NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 插入classes记录:
INSERT INTO classes(id, name) VALUES (1, '一班');
INSERT INTO classes(id, name) VALUES (2, '二班');
INSERT INTO classes(id, name) VALUES (3, '三班');
INSERT INTO classes(id, name) VALUES (4, '四班');
-- 插入students记录:
INSERT INTO students (id, class_id, name, gender, score) VALUES (1, 1, '小明', 'M', 90);
INSERT INTO students (id, class_id, name, gender, score) VALUES (2, 1, '小红', 'F', 95);
INSERT INTO students (id, class_id, name, gender, score) VALUES (3, 1, '小军', 'M', 88);
INSERT INTO students (id, class_id, name, gender, score) VALUES (4, 1, '小米', 'F', 73);
INSERT INTO students (id, class_id, name, gender, score) VALUES (5, 2, '小白', 'F', 81);
INSERT INTO students (id, class_id, name, gender, score) VALUES (6, 2, '小兵', 'M', 55);
INSERT INTO students (id, class_id, name, gender, score) VALUES (7, 2, '小林', 'M', 85);
INSERT INTO students (id, class_id, name, gender, score) VALUES (8, 3, '小新', 'F', 91);
INSERT INTO students (id, class_id, name, gender, score) VALUES (9, 3, '小王', 'M', 89);
INSERT INTO students (id, class_id, name, gender, score) VALUES (10, 3, '小丽', 'F', 85);
-- OK:
SELECT 'ok' as 'result:';