MySQL 多表查询学习笔记
目录
多表查询
内连接(INNER JOIN)
-- 基础内连接
SELECT 字段列表 FROM 表1, 表2 WHERE 表1.sid = 表2.sid;
-- 三表连接
SELECT s_name, marks, c_name FROM student_info, marks_info, class_info
WHERE marks_info.s_id = student_info.s_id AND marks_info.c_id = class_info.c_id;
-- 使用别名简化
SELECT s.s_id, s_name, c_name, marks FROM student_info s, marks_info m, class_info c
WHERE s.s_id = m.s_id AND m.c_id = c.c_id;
-- INNER JOIN 语法
SELECT s_name, c_name, marks FROM student_info
INNER JOIN marks_info ON student_info.s_id = marks_info.s_id
INNER JOIN class_info ON marks_info.c_id = class_info.c_id;
外连接
左连接(LEFT JOIN):以左表为标准,右表无匹配填 NULL
SELECT s_name, marks FROM student_info s
LEFT JOIN marks_info m ON s.s_id = m.s_id;
右连接(RIGHT JOIN):以右表为标准,左表无匹配填 NULL
SELECT s_name, marks FROM marks_info m
RIGHT JOIN student_info s ON s.s_id = m.s_id;
全连接:MySQL 不支持 FULL JOIN,使用 UNION 实现
SELECT s_name, marks FROM student_info s LEFT JOIN marks_info m ON s.s_id = m.s_id
UNION
SELECT s_name, marks FROM marks_info m RIGHT JOIN student_info s ON s.s_id = m.s_id;
交叉连接
SELECT 字段列表 FROM 表1 CROSS JOIN 表2;
SELECT s_name, mark FROM student_info CROSS JOIN marks;
自连接
自己和自己的表连接,用于层级关系等。
常用查询技巧
去重和过滤
-- DISTINCT 去重
SELECT DISTINCT area FROM student_info;
-- GROUP BY 去重
SELECT area FROM student_info GROUP BY area;
-- IN 查询散列数据
WHERE id IN (1, 3, 4);
-- BETWEEN 查询范围
WHERE id BETWEEN 1 AND 3;
-- NOT IN
WHERE id NOT IN (1, 3, 4);
-- LIKE 模糊查询(避免 %d% 开头匹配)
WHERE name LIKE '_d_'; -- _ 单个字符
WHERE name LIKE '%d'; -- % 多个字符
-- NULL 检查
IS NULL -- 系统 NULL
IS NOT NULL
WHERE field = '' -- 空字符串
WHERE field = 'null' -- 字符串 'null'
别名
-- 表别名
SELECT s_name FROM student_info AS s;
SELECT s_name FROM student_info s;
-- 字段别名
SELECT s_id, s_name AS name FROM student_info;
-- 常量字段
SELECT '编号', s_id, s_name FROM student_info;
子查询
SELECT 字段列表 FROM 表1
WHERE 字段 IN|>|<|>=|<=|!=|ANY|ALL|EXISTS (SELECT 子查询);
-- IN 子查询
SELECT s_name FROM student_info
WHERE s_id IN (SELECT s_id FROM marks WHERE mark > 60);
-- 比较运算符
ANY -- 任何一个符合条件
ALL -- 所有结果都成立
EXISTS -- 子查询有结果返回则主查询执行
视图
虚拟表,可查询可修改,不能增删。
-- 创建视图
CREATE VIEW 视图名 AS 查询语句;
-- 删除视图
DROP VIEW 视图名;
-- 使用视图与普通表相同
SELECT * FROM 视图名;
索引
在记录上建立的虚拟目录,加快查询。
索引分类
- 普通索引:任何字段,标示符 MUL
- 唯一索引:不能重复,标示符 UNI
- 主键:一表一个,记录唯一且非 NULL,标示符 PRI
创建索引
-- 已存在的表
CREATE INDEX 索引名 ON 表名(字段名);
CREATE INDEX id_index ON student_info(s_id);
CREATE UNIQUE INDEX id_index ON student_info(s_id);
-- 修改表添加索引
ALTER TABLE 表名 ADD INDEX 索引名(字段);
ALTER TABLE 表名 ADD UNIQUE 索引名(字段);
-- 创建表时指定索引
CREATE TABLE person (
pid INT(4),
pname CHAR(30),
INDEX 索引名(字段)
);
-- 多列索引
CREATE INDEX 索引名 ON 表名(字段1, 字段2);
-- 短索引(前缀)
CREATE INDEX index1 ON news(title(10));
查看和删除
-- 查看索引
SHOW INDEX FROM 表名;
-- 删除索引
DROP INDEX 索引名 ON 表名;
ALTER TABLE 表名 DROP INDEX 索引名;
存储过程
MySQL 自定义函数,有自己的控制结构。
基础语法
-- 定义界定符
DELIMITER //
-- 创建存储过程
CREATE PROCEDURE 名称(IN|OUT|INOUT 参数 类型)
BEGIN
-- 过程体
END //
-- 调用
CALL 过程名();
-- 恢复默认界定符
DELIMITER ;
参数类型
- IN:传入参数(形参)
- OUT:返回参数(return)
- INOUT:传入传出参数
例子
DELIMITER //
-- 简单过程
CREATE PROCEDURE stu_pro()
BEGIN
SELECT * FROM student_info;
END //
-- 带参数的过程
CREATE PROCEDURE stu_id_pro(IN sid INT(4))
BEGIN
SELECT * FROM student_info WHERE s_id = sid;
END //
-- 返回参数
CREATE PROCEDURE get_age(IN sid INT(4), OUT age1 INT(4))
BEGIN
SELECT age INTO age1 FROM student_info WHERE s_id = sid;
END //
CALL get_age(3, @a) //
SELECT @a //
-- INOUT 参数
CREATE PROCEDURE add_one(INOUT a INT(4))
BEGIN
SET a = a + 1;
END //
SET @p = 100 //
CALL add_one(@p) //
DELIMITER ;
控制结构:IF
DELIMITER //
CREATE PROCEDURE if_pro(IN tag INT(4))
BEGIN
IF tag = 1 THEN
SELECT * FROM student_info;
ELSE
SELECT * FROM class;
END IF;
END //
DELIMITER ;
控制结构:CASE
DELIMITER //
CREATE PROCEDURE case_pro(IN tag INT(4))
BEGIN
CASE tag
WHEN 1 THEN SELECT * FROM student_info;
WHEN 2 THEN SELECT * FROM class;
WHEN 3 THEN SELECT * FROM people;
ELSE SELECT * FROM person;
END CASE;
END //
DELIMITER ;
循环:WHILE
DELIMITER //
CREATE PROCEDURE sum_while()
BEGIN
DECLARE tag INT(4) DEFAULT 0;
DECLARE num INT(4) DEFAULT 0;
WHILE num <= 10 DO
SET tag = num + tag;
SET num = num + 1;
END WHILE;
SELECT tag;
END //
DELIMITER ;
循环:REPEAT
DELIMITER //
CREATE PROCEDURE sum_repeat()
BEGIN
DECLARE tag INT(4) DEFAULT 0;
DECLARE num INT(4) DEFAULT 0;
REPEAT
SET num = num + 1;
SET tag = num + tag;
UNTIL num >= 10
END REPEAT;
SELECT tag;
END //
DELIMITER ;
循环:LOOP
DELIMITER //
CREATE PROCEDURE sum_loop()
BEGIN
DECLARE tag INT(4) DEFAULT 0;
DECLARE num INT(4) DEFAULT 0;
loop1: LOOP
SET num = num + 1;
SET tag = num + tag;
IF num >= 10 THEN
LEAVE loop1;
ELSE
ITERATE loop1;
END IF;
END LOOP loop1;
SELECT tag;
END //
DELIMITER ;
游标和错误处理
DELIMITER //
CREATE PROCEDURE cursor_pro()
BEGIN
DECLARE a INT(4) DEFAULT 0;
DECLARE b INT(4) DEFAULT 0;
DECLARE tag INT(4) DEFAULT 0;
DECLARE cur1 CURSOR FOR SELECT sid, marks FROM stu;
DECLARE CONTINUE HANDLER FOR SQLSTATE "02000" SET tag = 1;
OPEN cur1;
REPEAT
FETCH cur1 INTO a, b;
IF b < 60 THEN
UPDATE stu SET money = 500 WHERE sid = a;
ELSEIF b < 70 THEN
UPDATE stu SET money = 1000 WHERE sid = a;
ELSEIF b < 80 THEN
UPDATE stu SET money = 2000 WHERE sid = a;
ELSEIF b < 90 THEN
UPDATE stu SET money = 3000 WHERE sid = a;
ELSE
UPDATE stu SET money = 4000 WHERE sid = a;
END IF;
UNTIL tag
END REPEAT;
CLOSE cur1;
END //
DELIMITER ;
错误类型:
01000- SQL 错误02000- 数据未发现
触发器
用户执行某操作后,MySQL 自动执行的程序逻辑。
CREATE TRIGGER 触发器名
BEFORE|AFTER
INSERT|UPDATE|DELETE
ON 表名
FOR EACH ROW
BEGIN
触发器 SQL 语句
END
-- 新增示例
DELIMITER //
CREATE TRIGGER sum
BEFORE INSERT ON a
FOR EACH ROW
BEGIN
SET new.num = new.num + 1;
END //
DELIMITER ;
关键字:
NEW:插入或更新后的新数据OLD:更新或删除前的原数据
数据库优化
设计优化
- 统一编码(客户端、服务器、浏览器)
- 不出现表中有表的现象
- 单表字段不宜过多
- 单库表数量不超过 1000
- 字段尽量用较小的类型(CHAR < VARCHAR,INT < DOUBLE)
- 用存储过程替代频繁的 PHP 数据库访问
- 只在必要时建立索引,避免过多浪费空间
- 数据库设计符合第三范式
- 字段设为 NOT NULL
查询优化
- 不查询不必要的字段
- 避免 LIKE ‘%pattern%‘,改用 LIKE ‘pattern%’
- 在合理字段上建立索引
- 用 JOIN 代替普通多表连接,若无索引且子查询结果少则反之
- 多个字段常同时出现在 WHERE 则建立多列索引
- UNION 优先使用 UNION ALL(避免去重开销)
- 聚合函数避免 WHERE,改用 HAVING
- GROUP BY 和 ORDER BY 后字段顺序保持一致
- 建立短索引和唯一索引
- INSERT 明确字段列表而非 *
- 频繁多表查询时建立视图
- WHERE 多条件时,筛选多的条件放前面
- 删除不必要的括号和条件
插入优化
-- 单条插入最慢
INSERT INTO table VALUES (...);
-- 多条值插入较快
INSERT INTO table VALUES (...), (...), (...);
-- LOAD 插入最快(批量)
LOAD DATA INFILE '/path/to/file' INTO TABLE table;