本文链接
http://alex-my.xyz/books/database/MySQL必知必会
http://blog.csdn.net/alex_my/article/details/72357498
1 基础知识
1 主键
- 唯一标识表中每行的这个列(这组列)称为主键。
- 应该总是定义主键,虽然并不总是需要主键。
- 任意两行都不具有相同的主键值。
- 每一行都必须具有一个主键值,不可为NULL。
2 常用命令
- SHOW DATABASES;
- SHOW TABLES;
- SHOW COLUMNS FROM table;
- SHOW STATUS;
- SHOW CREATE DATABASE database;
- SHOW CREATE TABLE table;
2 检索数据
除非确实需要绝大部分列或全部列,否则最好不使用通配符* 来获取所有的列。检索不需要的列通常会降低搜索和应用程序的性能。
1 DISTINCT和GROUP BY
SELECT DISTINCT `role_id` FROM user;
SELECT `role_id` FROM user GROUP BY `role_id`;
- 二者都可以达到去重的效果。
- DISTINCT把列中的全部内容存储到内存中,可以理解为一个hash,最后的到hash中的key就可以得到结果。比较耗内存。
- GROUP BY先将列排序,然后去重。排序比较耗时间。
2 LIMIT
从0开始算
SELECT `role_id` FROM user LIMIT 5 OFFSET 3;
SELECT `role_id` FROM user LIMIT 3, 5;
以上两条命令都表示从3开始的5行。
3 排序数据
默认为升序 ASC
。
SELECT `role_id`, `account_id` FROM user_pay ORDER BY `account_id`, `role_id` DESC LIMIT 20;
以上语句中, account_id默认为升序排列,也可以写上ASC。
4 过滤数据
1 IN操作符
SELECT * FROM user WHERE role_id IN (1000001, 1000002);
2 NOT操作符
SELECT * FROM user WHERE role_id IN (1000001, 1000002) LIMIT 10;
3 LIKE操作符
SELECT * FROM user WHERE name LIKE 't%';
SELECT * FROM user WHERE name LIKE 't_'; -- t1, t2
SELECT * FROM user WHERE name LIKE 't__'; -- t123, t34
- 以上两句都是模糊匹配用户名以t开头。
- %: 匹配任意0个或者多个字符。
- _: 一个_匹配1个任意字符,且必须有一个。
5 正则表达式搜索
MYSQL仅支持多数正则表达式实现的一个很小的子集。
1 基本字符匹配
SELECT name FROM user WHERE name REGEXP 't'; -- t, t1, t2
SELECT name FROM user WHERE name REGEXP 't.'; -- t1, t2
.
表示匹配任意一个字符。- LIKE和REGEXP区别:
- LIKE 要求整个列匹配(使用通配符除外), REGEXP只要列中某个片段匹配即可。
- 假设有用户名为s123。则以下例子中, LIKE没有得到结果。
SELECT name FROM user WHERE name LIKE 's1'; -- 没有结果
SELECT name FROM user WHERE name REGEXP 's1'; -- s123
2 OR匹配
SELECT name FROM user WHERE name REGEXP 's1|s2' ORDER BY name; -- s123, s2, s234
使用|功能上类似于SELECT中的OR语句。多个OR语句可以使用正则表达式替代,更简洁。
3 匹配几个字符之一
SELECT name FROM user WHERE name REGEXP 's[1238]' ORDER BY name; -- s123, s2, s234, s89
相当于
SELECT name FROM user WHERE name REGEXP 's1|s2|s3|s8' ORDER BY name;
也可以添加^,来匹配除指定以外的内容
SELECT name FROM user WHERE name REGEXP 's[^1238]' ORDER BY name; -- s4, s5
4 匹配范围
SELECT name FROM user WHERE name REGEXP 's[1-8]' ORDER BY name; -- s123, s2, s89..
SELECT name FROM user WHERE name REGEXP '[a-z][1-8]' ORDER BY name; -- a1, b2, c3
5 匹配特殊字符
为了匹配特殊字符,必须用\\
为前导。
SELECT name FROM user WHERE name REGEXP 's\\-' ORDER BY name; -- s-5
\\
也用来引用具有特殊含义的字符
特殊字符 | 含义 |
---|---|
\\f | 换页 |
\\n | 换行 |
\\r | 回车 |
\\t | 制表 |
\\v | 纵向制表 |
多数正则表达式使用\
转义特殊字符,以便能使用这些字符本身。但MySQL要求用\\
。
MySQL解释一个,正则表达式解释另外一个。
6 匹配字符类
为了方便工作,可以使用预定义的字符集
类 | 说明 |
---|---|
[:alnum:] | 任意字母和数字 ([a-zA-Z0-9]) |
[:alpha:] | 任意字符 ([a-zA-Z]) |
[:blank:] | 空格和指标 (\\t) |
[:cntrl:] | ASCII控制字符 (ASCII 0~31, 127) |
[:digit:] | 任意数字 ([0-9]) |
[:graph:] | 与[:print:]相同,但不包括空格 |
[:lower:] | 任意小写字母 ([a-z]) |
[:print:] | 任意可打印的数字 |
[:punct:] | 同时不在[:alnum:][:cntrl:]中的任意字符 |
[:space:] | 包括空格在内的任意空白字符 ([\\t\\n\\r\\t\\v]) |
[:upper:] | 任意大写字母 ([A-Z]) |
[:xdigit:] | 任意十六进制数字 ([a-fA-F0-9]) |
示例:
SELECT name FROM user WHERE name REGEXP '[[:alpha:]]1' ORDER BY name; -- h1, m1, s123
7 匹配多个实例
字符 | 说明 |
---|---|
* | 0个或多个匹配 |
+ | 1个或多个匹配 ({1, }) |
? | 0个或1个匹配 ({0, 1}) |
{n} | 指定数目的匹配 |
{n, } | 不少于指定数目的匹配 |
{n, m} | 匹配数目范围, m 不超过255 |
SELECT name FROM user WHERE name REGEXP '[[:digit:]]{4}' ORDER BY name; -- s4444, 21111
8 定位符
元字符 | 说明 |
---|---|
^ | 文本的开始 |
$ | 文本的结尾 |
[[:<:]] | 词的开始 |
[[:>:]] | 词的结尾 |
* 示例1
假设要找到以字母开头的用户名
SELECT name FROM user WHERE name REGEXP '[a-zA-Z]';
以上语句将会在文本任意位置进行查找匹配,并不符合以字母开头这依规定. 这里可以使用^
SELECT name FROM user WHERE name REGEXP '^[a-zA-Z]';
6 创建计算字段
存储在表中的数据不一定是应用程序所需要的。我们可以直接从数据库中检索出转换,计算或格式化过的数据。而不是检索出原始数据然后在应用程序中重新格式化。
1 拼接 CONCAT
表中含有role_id, name字段,应用程序需要这样的格式 role_name(role_id)
SELECT CONCAT(name, '(', role_id, ')') FROM user LIMIT 1; -- s123 (1000001)
SELECT CONCAT(RTRIM(name), '(', role_id, ')') FROM user LIMIT 1; -- s123(1000001)
- RTRIM()函数去掉了值右边的所有空格。其余有LTRIM(), TRIM()
2 别名 AS
拼接处的结果没有名字,应用程序没法引用。可以使用别名解决这个问题。
SELECT CONCAT(name, '(', role_id, ')') AS info FROM user LIMIT 1;
这样,应用程序就可以使用info这个列,就像它本来就存在于表中一样。
3 执行算术计算
假设用户充值了money(元),每元可以换成10个代币,这里通过计算直接得出获得的总代币。
SELECT role_id, money, money * 10 AS total_gold FROM user LIMIT 10;
7 使用数据处理函数
1 字符串函数
函数 | 说明 | 示例 | 结果 |
---|---|---|---|
CHAR_LENGTH(S) | 返回字符串s字符数 | SELECT CHAR_LENGTH(‘abc 你好’); | 6 |
LENGTH(S) | 返回字符串s的长度 | SELECT LENGTH(‘abc 你好’); | 10 |
CONCAT(S1,S2,…) | 合并为一个字符串 | SELECT CONCAT(‘hello’, ’ abc’); | hello abc |
CONCAT_WS(x, s1, s2,…) | 同CONCAT,但会加上x | SELECT CONCAT_WS(‘+’, ‘1’, ‘2’, ‘3’); | 1+2+3 |
INSERT(s1, x, length, s2) | 将字符串s2替换s1的x位置开始长度为length的字符串 | SELECT INSERT(‘abcdefg’, 2, 3, ‘123’); | a123efg |
UPPER(s) | 将字符串s的所有字母变成大写字母 | SELECT UPPER(‘abcd’); | ABCD |
LOWER(s) | 将字符串s的所有字母变成小写字母 | SELECT LOWER(‘ABCD’); | abcd |
LEFT(s, n) | 返回字符串s的前n个字符 | SELECT LEFT(‘abcdef’, 3); | abc |
RIGHT(s, n) | 返回字符串s的后n个字符 | SELECT RIGHT(‘abcdef’, 3); | def |
LPAD(s1, length, s2) | 字符串s2来填充s1的开始处,使字符串长度达到length | SELECT LPAD(‘abc’, 8, ‘123’); | 12312abc |
RPAD(s1, length, s2) | 字符串s2来填充s1的结尾处,使字符串的长度达到length | SELECT RPAD(‘abc’, 8, ‘123’); | abc12312 |
LTRIM(s) | 去掉字符串s开始处的空格 | SELECT LTRIM(’ abc ‘); | ‘abc ‘ |
RTRIM(s) | 去掉字符串s结尾处的空格 | SELECT RTRIM(’ abc ‘); | ’ abc’ |
TRIM(s) | 去掉字符串s开始和结尾处的空格 | SELECT TRIM(’ abc ‘); | ‘abc’ |
TRIM(s1 FROM s) | 去掉字符串s中开始处和结尾处的字符串s1 | SELECT TRIM(‘-’ FROM ‘—hello–’); | hello |
REPEAT(s, n) | 将字符串s重复n次 | SELECT REPEAT(‘abc’, 3); | abcabcabc |
SPACE(n) | 返回n个空格 | SELECT SPACE(3); | ‘ ’ |
REPLACE(s, s1, s2) | 将字符串s2替代字符串s中的字符串s1 | SELECT REPLACE(‘abcdef’, ‘abc’, ‘12’); | 12def |
STRCMP(s1, s2) | 比较字符串s1和s2 | SELECT STRCMP(‘abc’, ‘abc’); | 0 |
STRCMP(s1, s2) | 比较字符串s1和s2 | SELECT STRCMP(‘abc’, ‘abcd’); | -1 |
STRCMP(s1, s2) | 比较字符串s1和s2 | SELECT STRCMP(‘abc’, ‘ab’); | 1 |
SUBSTRING(s, n, length) | 获取从字符串s中的第n个位置开始长度为length的字符串 | SELECT SUBSTRING(‘abcdefg’, 2, 3); | bcd |
MID(s, n, length) | 同SUBSTRING | SELECT MID(‘abcdefg’, 3, 2); | cd |
LOCATE(s1, s) | 从字符串s中获取s1的开始位置 | SELECT LOCATE(‘de’, ‘abcdefg’); | 4 |
POSITION(s1, s) | 从字符串s中获取s1的开始位置 | SELECT POSITION(‘de’ IN ‘abcdefg’); | 4 |
INSTR(s, s1) | 从字符串s中获取s1的开始位置 | SELECT INSTR(‘abcdefg’, ‘de’); | 4 |
REVERSE(s) | 将字符串s的顺序反过来 | SELECT REVERSE(‘a,b,c,d,e,f’); | f,e,d,c,b,a |
ELT(n, s1, s2, …) | 返回第n个字符串 | SELECT ELT(3, ‘abc’, ‘def’, ‘ghi’, ‘jkl’); | ghi |
EXPORT_SET(…) | 见示例 | SELECT EXPORT_SET(6, ‘y’, ‘n’, ‘_’, 3); | n_y_y |
FIELD(s, s1, s2, …) | 返回第一个与字符串s匹配的字符串位置 | SELECT FIELD(‘b’, ‘a’, ‘b’, ‘c’); | 2 |
FIND_IN_SET(str, str_list) | 见示例 | SELECT FIND_IN_SET(‘4’, ‘6,5,4,3,2,1’); | 3 |
2 数学函数
函数 | 说明 | 示例 | 结果 |
---|---|---|---|
ABS(x) | 返回x的绝对值 | ||
CEIL(x) | 返回大于或等于x的最小整数 | ||
CEILING(x) | 返回大于或等于x的最小整数 | ||
FLOOR(x) | 返回小于或等于x的最大整数 | ||
RAND() | 返回0->1的随机数 | ||
RAND(x) | 返回0->1的随机数,x值相同时返回的随机数相同 | ||
SIGN(x) | 返回x的符号,x是负数、0、正数分别返回-1、0和1 | ||
PI() | 返回圆周率(3.141593) | ||
TRUNCATE(x, y) | 返回数值x保留到小数点后y位的值(不会四舍五入) | ||
ROUND(x) | 返回离x最近的整数 | ||
ROUND(x, y) | 保留x小数点后y位的值(四舍五入) | ||
POW(x, y) | 返回x的y次方 | ||
POWER(x, y) | 返回x的y次方 | ||
SQRT(x) | 返回x的平方根 | ||
EXP(x) | 返回e的x次方 | ||
MOD(x, y) | 返回x除以y以后的余数 | ||
LOG(x) | 返回自然对数(以e为底的对数) | ||
LOG10(x) | 返回以10为底的对数 | ||
RADIANS(x) | 将角度转换为弧度 | ||
DEGREES(x) | 将弧度转换为角度 | ||
SIN(x) | 求正弦值(参数是弧度) | ||
ASIN(x) | 求反正弦值(参数是弧度) | ||
COS(x) | 求余弦值(参数是弧度) | ||
ACOS(x) | 求反余弦值(参数是弧度) | ||
TAN(x) | 求正切值(参数是弧度) | ||
ATAN(), ATAN2() | 求反正切值(参数是弧度) | ||
COT() | 求余切值(参数是弧度) |
3 日期时间函数
函数 | 说明 | 示例 | 结果 |
---|---|---|---|
CURDATE(), CURRENT_DATE() | 返回当前日期 | SELECT CURRENT_DATE(); | 2017-05-11 |
CURTIME(), CURRENT_TIME | 返回当前时间 | SELECT CURRENT_TIME(); | 19:01:11 |
NOW() | 返回当前日期和时间 | SELECT NOW(); | 2017-05-11 19:01:30 |
CURRENT_TIMESTAMP() | 返回当前日期和时间 | 同上 | |
LOCALTIME() | 返回当前日期和时间 | 同上 | |
SYSDATE() | 返回当前日期和时间 | 同上 | |
LOCALTIMESTAMP() | 返回当前日期和时间 | 同上 | |
UNIX_TIMESTAMP() | 以UNIX时间戳的形式返回当前时间 | SELECT UNIX_TIMESTAMP(); | 1494500521 |
UNIX_TIMESTAMP(d) | 将时间d以UNIX时间戳的形式返回 | SELECT UNIX_TIMESTAMP(‘2017-05-11 19:02:01’); | 1494500521 |
FROM_UNIXTIME(d) | 将UNIX时间戳的时间转换为普通格式的时间 | SELECT FROM_UNIXTIME(1494500521); | 2017-05-11 19:02:01 |
UTC_DATE() | 返回UTC日期 | SELECT UTC_DATE(); | 2017-05-11 |
UTC_TIME() | 返回UTC时间 | SELECT UTC_TIME(); | 11:06:13 |
MONTH(d) | 返回日期d中的月份值,1->12 | SELECT MONTH(‘2017-05-11’); | 5 |
MONTHNAME(d) | 返回日期当中的月份名称 | SELECT MONTHNAME(‘2017-05-11’); | May |
DAYNAME(d) | 返回日期d是星期几 | SELECT DAYNAME(‘2017-05-11 19:07:12’); | Thursday |
DAYOFWEEK(d) | 日期d今天是星期几,1星期日,2星期一 | SELECT DAYOFWEEK(‘2017-05-11’); | 5 |
WEEKDAY(d) | 日期d今天是星期几,0表示星期一,1表示星期二 | SELECT WEEKDAY(‘2017-05-11’); | 3 |
WEEK(d),WEEKOFYEAR(d) | 计算日期d是本年的第几个星期,范围是0->53 | SELECT WEEK(‘2017-05-11’); | 19 |
DAYOFYEAR(d) | 计算日期d是本年的第几天 | SELECT DAYOFYEAR(‘2017-05-11’); | 131 |
DAYOFMONTH(d) | 计算日期d是本月的第几天 | SELECT DAYOFMONTH(‘2017-05-11’); | 11 |
QUARTER(d) | 返回日期d是第几季节,返回1->4 | SELECT QUARTER(‘2017-05-11’); | 2 |
HOUR(t) | 返回t中的小时值 | SELECT HOUR(‘2017-05-11 19:11:23’); | 19 |
MINUTE(t) | 返回t中的分钟值 | SELECT MINUTE(‘2017-05-11 19:11:23’); | 11 |
SECOND(t) | 返回t中的秒钟值 | SELECT SECOND(‘2017-05-11 19:11:23’); | 23 |
EXTRACT(type FROM d) | 从日期d中获取指定的值,type指定返回的值(见下文) | SELECT EXTRACT(WEEK FROM ‘2017-05-11 19:11:23’); | 19 |
TIME_TO_SEC(t) | 将时间t转换为秒 | SELECT TIME_TO_SEC(‘19:11:23’); | 69083 |
SEC_TO_TIME(s) | 将以秒为单位的时间s转换为时分秒的格式 | SELECT SEC_TO_TIME(69083); | 19:11:23 |
TO_DAYS(d) | 计算日期d距离0000年1月1日的天数 | SELECT TO_DAYS(‘2017-05-11 19:11:23’); | 736825 |
FROM_DAYS(n) | 计算从0000年1月1日开始n天后的日期 | SELECT FROM_DAYS(736825); | 2017-05-11 |
DATEDIFF(d1,d2) | 计算日期d1->d2之间相隔的天数 | SELECT DATEDIFF(‘2017-05-11’, ‘2017-05-12’); | -1 |
ADDDATE(d,n) | 计算其实日期d加上n天的日期 | SELECT ADDDATE(‘2017-05-11 19:11:23’, 3); | 2017-05-14 19:11:23 |
ADDDATE(d,INTERVAL expr type) | 计算起始日期d加上一个时间段后的日期 | SELECT ADDDATE(‘2017-05-11 19:11:23’, INTERVAL 3 HOUR); | 2017-05-11 22:11:23 |
DATE_ADD(d,INTERVAL expr type) | 同上 | SELECT DATE_ADD(‘2017-05-11 19:11:23’, INTERVAL 10 HOUR); | 2017-05-12 05:11:23 |
SUBDATE(d,n) | 日期d减去n天后的日期 | SELECT SUBDATE(‘2017-05-12 05:11:23’, 13); | 2017-04-29 05:11:23 |
SUBDATE(d,INTERVAL expr type) | 日期d减去一个时间段后的日期 | SELECT SUBDATE(‘2017-04-29 05:11:23’, INTERVAL 10 MINUTE); | 2017-04-29 05:01:23 |
ADDTIME(t,n) | 时间t加上n秒的时间 | SELECT ADDTIME(‘2017-04-29 05:01:23’, 30); | 2017-04-29 05:01:53 |
SUBTIME(t,n) | 时间t减去n秒的时间 | SELECT SUBTIME(‘2017-04-29 05:01:53’, 30); | 2017-04-29 05:01:23 |
DATE_FORMAT(d,f) | 按表达式f的要求显示日期d | SELECT DATE_FORMAT(‘2017-04-29 05:01:23’, ‘%Y-%m-%d’); | 2017-04-29 |
TIME_FORMAT(t,f) | 按表达式f的要求显示时间t | SELECT TIME_FORMAT(‘2017-04-29 05:01:23’, ‘%r’); | 05:01:23 AM |
* type的值可以为:
MICROSECOND
SECOND
MINUTE
HOUR
DAY
WEEK
MONTH
QUARTER
YEAR
SECOND_MICROSECOND
MINUTE_MICROSECOND
MINUTE_SECOND
HOUR_MICROSECOND
HOUR_SECOND
HOUR_MINUTE
DAY_MICROSECOND
DAY_SECOND
DAY_MINUTE
DAY_HOUR
YEAR_MONTH
4 条件判断函数
- IF (expr, v1, v2);
SELECT IF (1 > 0, 'Y', 'N'); -- Y
- IFNULL(v1, v2);
如果v1不为NULL, 返回v1,否则返回v2SELECT IFNULL('a', 'b'); -- a
5 系统信息函数
函数 | 说明 | 示例 | 结果 |
---|---|---|---|
VERSION() | 返回数据库的版本号 | SELECT VERSION(); | 5.7.11 |
CONNECTION_ID() | 返回服务器的连接数 | SELECT CONNECTION_ID(); | 13 |
DATABASE() | 返回当前数据库名 | SELECT DATABASE(); | database-learn |
USER() | 返回当前用户 | SELECT USER(); | root@localhost |
CHARSET(s) | 返回字符串s的字符集 | SELECT CHARSET(“123”); | utf8 |
COLLATION(s) | 返回字符串s的字符排列方式 | SELECT COLLATION(“a123”); | utf8_general_ci |
LAST_INSERT_ID() | 返回最近生成的AUTO_INCREMENT值 | SELECT LAST_INSERT_ID(); | 0 |
8 分组数据
1 数据分组
假设要获取用户的充值次数,最低充值额度,最高充值额度,平均充值额度,可以用以下命令:
SELECT role_id, COUNT(*) AS num, MIN(money) as min_money, MAX(money) as max_money, AVG(money) AS avg_money FROM user_pay;
以上得出的是总的信息,如果要获取每个用户的这些信息,就可以使用分组了。
SELECT role_id, COUNT(*) AS num, MIN(money) as min_money, MAX(money) as max_money, AVG(money) AS avg_money FROM user_pay GROUP BY role_id ORDER BY num;
以上按照每个用户来计算结果。
- 需要注意的是,GROUP BY 必须出现在 WHERE 之后,ORDER BY 之前
- 可以使用WITH ROLLUP得到汇总的值
SELECT role_id, COUNT(*) AS num, MIN(money) as min_money, MAX(money) as max_money, AVG(money) AS avg_money FROM user_pay GROUP BY role_id WITH ROLLUP;
以上在在结果的最后,会附上总的结果。
2 分组过滤
假设只需要得到充值2次(包含)以上用户的数据,则需要使用HAVING来过滤。
SELECT role_id, COUNT(*) AS num, MIN(money) as min_money, MAX(money) as max_money, AVG(money) AS avg_money FROM user_pay GROUP BY role_id HAVING num >= 2 ORDER BY num;
- 注意HAVING跟GROUP BY后面。
- 也可以同时使用WHERE和HAVING。
SELECT role_id, COUNT(*) AS num, MIN(money) as min_money, MAX(money) as max_money, AVG(money) AS avg_money FROM user_pay WHERE time >= 1483200000 GROUP BY role_id HAVING num >= 2 ORDER BY num;
以上通过WHERE新增了条件,2017年以来充值的。
- 当sql_mode为ONLY_FULL_GROUP_BY需要注意
- 查看sql_mode值
SELECT @@sql_mode;
结果:
ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
- 在sql_mode=ONLY_FULL_GROUP_BY的模式下,以下句子报错
SELECT role_id, money FROM user_pay GROUP BY role_id;
错误:
SELECT list is not in GROUP BY clause and contains nonaggregated column ...
表中的列
,出现在SELECT中时,也得出现在GROUP BY中。SELECT role_id, money FROM user_pay GROUP BY role_id, money;
同样,ORDER BY也需要注意这个问题。
- 查看sql_mode值
3 SELECT字句顺序
SELECT > FROM > WHERE > GROUP BY > HAVING > ORDER BY > LIMIT
9 子查询
1 子查询过滤
假设要得出充值用户的用户信息
SELECT role_id, name FROM user WHERE role_id in (SELECT role_id FROM user_pay);
- 在SELECT语句中,子查询总是从内向外处理。
- 需要保证WHERE语句中需要和子SELECT语句中有相同数目的列。二者名称可以不相同。
... WHERE role_id in (SELECT role_id ...) ... WHERE role_id in (SELECT r_id ...)
2 做为计算字段使用子查询
假设要得出用户的充值次数(user_pay)以及用户信息(user)
SELECT role_id,
name,
(SELECT COUNT(*)
FROM user_pay
WHERE user_pay.role_id = user.role_id) AS recharge_count
FROM user
LIMIT 10;
10 联结
1 内联结
同9.2 假设要得出用户的充值次数(user_pay)以及用户信息(user), 以下两种方法都可以获得结果。
使用WHERE子句
SELECT role_id, COUNT(money)
FROM user, user_pay
WHERE user.role_id = user_pay.rid
GROUP BY role_id;
使用INNER JOIN
SELECT role_id,
COUNT(money)
FROM user
INNER JOIN user_pay
ON user_pay.role_id = user.role_id
GROUP BY role_id;
- ANSI SQL规范首选INNER JOIN。
2 外联结
外联结使用OUTER JOIN来表示。
必须在OUTER 前加上LEFT或RIGHT关键字。OUTER可以省略不写。 LEFT: 表示选中OUTER左侧表的所有行。
RIGHT: 表示选中OUTER右侧表的所有行。
SELECT a.role_id, SUM(b.money) AS total_recharge
FROM user a
LEFT JOIN user_pay b
ON a.role_id = b.role_id
GROUP BY a.role_id;
以上信息获取用户的充值信息,如果有用户没有充值,则total_recharge=NULL。
如果使用RIGHT JOIN,如果user_pay中有用户数据在user表中找不到,则role_id=NULL。
11 组合查询
1 UNION
假设需要获取充值额度为30的用户, 以及渠道为1001的用户,使用组合查询:
SELECT role_id, money FROM user_pay WHERE money = 30
UNION
SELECT role_id, money FROM user_pay WHERE channel_id = 1001;
- 组合使用UNION将独立的SELECT相连。
- 每个SELECT查询都必须包含相同的列,表达式或函数。但次序不必相同。
2 UNION ALL
UNION从查询结果中自动去除了重复的行。比如渠道1001也有人充值30的。
如果不想被去除重复的行,可以使用UNION ALL。
3 组合查询结果排序
可以在最后一条的SELECT后添加ORDER BY语句对结果进行排序。
12 全文本搜索
1 引擎支持
- MyISAM和InnoDB(5.6)都支持全文本搜索。
TODO
13 视图
视图可以简化操作,保护数据。
1 创建视图
- 使用CREATE VIEW创建视图。
- 使用DROP VIEW删除视图。
- 这边使用 CREATE OR REPLACE VIEW
- 创建一个视图,该视图从用户表(user), 用户充值表(user_pay)获取用户基本信息,总充值额度。
CREATE OR REPLACE VIEW user_pay_info AS SELECT b.role_id, b.name, SUM(a.money) AS total_money FROM user_pay a RIGHT JOIN user b ON a.role_id = b.role_id GROUP BY b.role_id;
使用SHOW TABLES可以发现多了一个表,user_pay_info。
2 使用视图 SELECT
- 创建好视图后,再想获得用户充值信息,可以通过以下语句:
SELECT * FROM user_pay_info;
十分便捷。
- 虽然表面看是从user_pay_info中获取数据,但实际上仍然是从user, user_pay中获取数据。
3 更新视图 UPDATE
视图中存在以下操作,则不可更新:
- 分组 (GROUP BY, HAVING)
- 联结
- 子查询
- 聚集函数 (MIN, COUNT, SUM)
- DISTINCT
但凡MySQL不能确定能够正确更新到实际表(user, user_pay),则不允许进行视图更新。
一般,应该将视图用于检索,而不用于更新。
14 存储过程
相当于调用预编译好的sql集合。
1 创建存储过程 CREATE PROCEDURE
假设要知道每个用户的充值总额
CREATE PROCEDURE user_pay_total()
BEGIN
SELECT role_id, SUM(money) AS total_recharge
FROM user_pay
GROUP BY role_id;
END
以上就创建好了。
需要注意的是,如果在命令行工具中直接用以上语句创建,会报错。
因为命令行工具也用;
做为分隔符,sql语句中也是用;
做为分隔符,存在冲突。
是用 DELIMITER 可以自定义命令行工具的分隔符
DELIMITER //
CREATE PROCEDURE user_pay_total()
BEGIN
SELECT role_id, SUM(money) AS total_recharge
FROM user_pay
GROUP BY role_id;
END //
DELIMITER ;
以上DELIMITER告诉命令行工具,使用//
做为分隔符。最后一句恢复回;
做为分隔符。
2 使用存储过程 CALL
CALL user_pay_total();
以上语句会执行刚才创建的存储过程。
3 删除存储过程 DROP
可以直接使用DROP删除
DROP PROCEDURE user_pay_total;
但是,如果不存在user_pay_total(),就会报错。
所以,建议用以下命令:
DROP PROCEDURE IF EXISTS user_pay_total;
4 使用参数
参数可以用IN, OUT, INOUT修饰。 TODO
5 检测存储过程
以下语句可以显示创建存储过程的鳄鱼局
SHOW CREATE PROCEDURE user_pay_info;
15 触发器
1 创建触发器
- MySQL触发器只响应以下语句: INSERT, UPDATE, DELETE
- 保持每个数据库触发器名称唯一。
- 只有表才支持触发器,视图,临时表不支持。
创建tb1, tb2同tb1
CREATE TABLE `tb1` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '索引值',
`value` int(11) NOT NULL COMMENT '数据',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
创建触发器,命令行下别忘了 DELIMITER
DELIMITER //
CREATE TRIGGER tb1_cp_tb2
AFTER INSERT ON tb1
FOR EACH ROW
BEGIN
INSERT INTO tb2(id, value) VALUES (NEW.id, NEW.value);
END //
DELIMITER ;
以上触发器在tb1执行INSERT操作时触发,会给tb2插入相同的数据。
2 删除触发器
DROP TRIGGER IF EXISTS tb1_cp_tb2;
3 触发说明
- INSERT
- INSERT触发器可在INSERT执行之前或之后触发。
- 在触发器代码内,可以使用一个名为NEW的虚拟表,访问被插入的行。
- 对于AUTO_INCREMENT列,NEW在INSERT之前为0,在INSERT执行之后为自动生成的值。
- UPDATE
同INSERT - DELETE
- DELETE触发器可在DELETE执行之前或之后触发。
- 在触发器代码内,可以使用一个名为OLD的虚拟表,访问被插入的行。
DELIMITER // CREATE TRIGGER tb1_cp_tb2 AFTER DELETE ON tb1 FOR EACH ROW BEGIN INSERT INTO tb2(id, value) VALUES (OLD.id, OLD.value); END // DELIMITER ;
从tb1删除的数据会被复制到tb2中。
16 事务处理
事务处理可以用来维护数据库的完整性,保证多个SQL命令要么完全执行,要么完全不执行。
1 事务处理示例
SELECT * FROM tb1;
START TRANSACTION;
DELETE FROM tb1;
SELECT * FROM tb1;
ROLLBACK;
SELECT * FROM tb1;
- 以上语句中,当删除tb1后,再次查询,没有内容。当回滚后,数据又出现了。
- 可以使用COMMIT将事务提交上去执行。
- 不能回退SELECT, CREATE, DROP操作。
- 当执行COMMIT或ROLLBACK后,事务会自动关闭。
2 保留点
复杂的事务处理中,可能存在需要部分回退或者部分提交的情况。
可以使用保留点来处理。
SAVEPOINT d1; -- 创建了保留点
...
ROLLBACK TO d1; -- 回滚到保留点
当事务关闭后,保留点会自动释放。
3 autocommit
InnoDB默认 autocommit=on,即每一条sql语句都是当成一个事务,执行后就提交。
当写下START TRANSACTION时,autocommit的设置就无效了。需要等待COMMIT或ROLLBACK来结束事务。
autocommit针对的是每个与MySQL的链接,改变其值不会影响其它链接。