* 5分钟练习
* 继续使用mybase库, 对zhangwu表做以下操作:
* 查询表中一共有多少条记录: COUNT
* 计算所有金额的总和: SUM
* 计算总支出: SUM
* 计算总收入: SUM
* 获取最高金额: MAX
* 获取最低金额: MIN
* 获取最高收入: MAX
* 获取最低收入: MIN
* 计算金额平均值: AVG
```sql
# 查询表中一共有多少条记录: COUNT 7条
SELECT COUNT(*) FROM zhangwu;
# 计算所有金额的总和: SUM 34917
SELECT SUM(zmoney) AS '金额总和' FROM zhangwu;
# 计算总支出: SUM 9572
SELECT SUM(zmoney) AS '总支出' FROM zhangwu WHERE zname LIKE '%支出%';
# 计算总收入: SUM 20345
SELECT SUM(zmoney) AS '总收入' FROM zhangwu WHERE zname LIKE '%收入%';
# 获取最高金额: MAX 12345
SELECT MAX(zmoney) AS '最高金额' FROM zhangwu;
# 获取最低金额: MIN 247
SELECT MIN(zmoney) AS '最低金额' FROM zhangwu;
# 获取最高收入: MAX 12345
SELECT MAX(zmoney) AS '最高收入' FROM zhangwu WHERE zname LIKE '%收入%';
# 获取最低收入: MIN 8000
SELECT MIN(zmoney) AS '最低收入' FROM zhangwu WHERE zname LIKE '%收入%';
# 计算金额平均值: AVG 4988.1428...
SELECT AVG(zmoney) AS '平均金额' FROM zhangwu;
```
---
## 分组查询
* 分组查询
* 是`SELECT`的子句, 用于将查询出的结果集再次按照某列分组合并显示, 记录值相同的归为一组. 同时还可以对分组后的结果再次使用条件进行过滤
* 格式:
* `GROUP BY 列名 HAVING 条件`,
* `SELECT ... FROM ... WHERE ... GROUP BY 列名 HAVING 条件;`
* `HAVING`的作用: 可选, 对查询结果进行分组显示后, 再次按条件过滤
* 注意:
* 聚合函数与分组的使用效果
* 使用聚合函数后再进行分组, 才相当于汇总统计
* `SELECT zname, SUM(zmoney) FROM zhangwu GROUP BY zname;`
* 不使用聚合函数的分组, 只有第一条记录的值
* `SELECT zname, zmoney FROM zhangwu GROUP BY zname;`
* `HAVING`和`WHERE`的区别
* 过滤时机不同
* WHERE是在分组前先对查询结果进行过滤
* HAVING是在查询结果分组后再次过滤
* 是否可跟随聚合函数不同
* WHERE条件中不可使用聚合函数
* HAVING条件中可以使用聚合函数
* 条件可用列名不同
* HAVING条件中的列名必须是SELECT或GROUP BY中使用过的列名
* WHERE条件中可以使用表的所有列名
```sql
/* 最终一个完整的SELECT语句格式 */
SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... DESC;
```
```sql
# 按账务名称分组统计各组金额总和(查询zname, zmoney求和列, 并按照zname分组)
SELECT zname, SUM(zmoney) FROM zhangwu
GROUP BY zname;
# 按账务名称分组统计各组支出金额总和(查询zname, zmoney求和列, 过滤出支出, 并按zname分组)
SELECT zname, SUM(zmoney) FROM zhangwu
WHERE zname LIKE '%支出%'
GROUP BY zname;
# 按账务名称分组统计各组支出金额总和, 并按金额总和的降序排列(查询zname, zmoney求和列, 过滤出支出, 并按zname分组, 按zmoney求和列降序排序)
SELECT zname, SUM(zmoney) AS '金额总和' FROM zhangwu
WHERE zname LIKE '%支出%'
GROUP BY zname
ORDER BY 金额总和 DESC;
# 按账务名称分组统计各组支出金额总和大于5000的记录, 并按金额总和的降序排列(查询zname, zmoney求和列, 过滤出支出, 并按zname分组, 将分组后的结果再次过滤出大于5000的, 最后按zmoney求和列降序排序)
SELECT zname, SUM(zmoney) AS '金额总和' FROM zhangwu
WHERE zname LIKE '%支出%'
GROUP BY zname
HAVING 金额总和 > 5000
ORDER BY 金额总和 DESC;
```
* 用户注册和登录实际就是对数据库进行操作
* 注册: 将用户名和密码INSERT一条记录
* `INSERT INTO users (username, password) VALUES ('zhangsan', '123456');`
* 登录: 按照用户名和密码查询是否有符合条件的记录
* `SELECT * FROM users WHERE username='zhangsan' AND password='123456';`
* 如果查到了, 则说明用户已经注册过, 且用户名密码都匹配, 则允许登录
* 如果查不到, 则说明用户没有注册, 或用户名密码不匹配, 则提示重新输入
* 什么是SQL注入攻击?
```
users表
------------------------------
id | username | password
------------------------------
1 | a | 1
------------------------------
-- 正常登陆
用户名: ____a____
密 码: ____1____
# 服务器端的Java程序获取到用户名和密码后, 最简单的方式是将其使用字符串拼接的方式, 组成一个完整的SQL语句
String sql = "SELECT * FROM user WHERE username = '" + username + "' AND password = '" + password + "';";
SELECT * FROM user WHERE username = 'a' AND password = '1';
- -
# 查询到1条记录, 说明用户名存在, 且密码正确, 允许登陆
登录成功!
-- 利用SQL注入来登录
用户名:__随便怎么写____
密 码:__1' OR '1=1___
# 来看看字符串拼接以后的SQL语句变成了什么?
SELECT * FROM user WHERE username = '随便怎么写' AND password = '1' OR '1=1';
--------- ----------
# where条件变成了:
username = '随便怎么写' AND password = '1'
或者
'1=1'
因为'1=1'是永远成立的, 所以这个where条件永远成立, 则相当于没有过滤任何条件, 最终的执行效果等于SELECT * FROM user
```sql
# 格式
SELECT column_name, ... FROM table_name1
[WHERE condition]
UNION [ALL|DISTINCT]
SELECT column_name, ... FROM table_name2
[WHERE condition];
# 示例
SELECT username FROM user_table1
UNION
SELECT username FROM user_table2;
SELECT * FROM sort WHERE sid = 1 OR sid = 7;
(SELECT * FROM sort WHERE sid = 1)
UNION
(SELECT * FROM sort WHERE sid = 7)
(SELECT * FROM sort)
UNION
(SELECT * FROM sort)
```
* 演示
```
# 第一个SELECT
SELECT username FROM user_table1;
# 输出
|username|
|--------|
|zhangsan|
# 第二个SELECT
SELECT username FROM user_table2;
# 输出
|username|
|--------|
| lisi|
# 使用UNION合并
(SELECT username FROM user_table1)
UNION
(SELECT username FROM user_table2);
# 输出
|username|
|--------|
|zhangsan|
| lisi|
```
### 子查询(也叫嵌套查询)
* 子查询: 使用SELECT语句查询出来的结果集, 实际上也可以看做一个表. 既然是一个表, 我们就可以用他作为其他查询语句的表
* 子查询语句也是一个SELECT语句, 使用小括号`()`括起来
* 注意:
* 在SELECT查询语句中使用子查询, 相当于把结果集看做一个表
* 如果作为WHERE子句的条件, 则子查询可以不起别名
* 如: `SELECT * FROM user_table1 WHERE age < (SELECT AVG(age) FROM user_table1);`
* 如果作为FROM后被查询的表, **子查询必须起别名**
* 如: `SELECT * FROM (SELECT username FROM user_table1) AS user WHERE username = 'zhangsan';`
* 注意:
* 如果不起别名会报错: Every derived table must have its own alias
* 虽然起了别名, 但结果集实际还是结果集, 并不会变成真的表
* 使用UPDATE, DELETE语句时, 被更新的表不能用在set或where的子查询
* 否则会报错: `You can't specify target table 'xxx' for update in FROM clause`
* 解决办法: 在操作被更新的表的子查询外再套一层SELECT * FROM(是否取别名取决于是用在FROM还是WHERE子句), 然后外层再执行UPDATE或DELETE
* 如: `DELETE FROM stutb WHERE score < (SELECT * FROM (SELECT AVG(score) FROM stutb WHERE department LIKE 'javaEE基础班') AS avgscore);`
```sql
# 一个SELECT查询
SELECT username FROM user_table1;
# 如果把以上看做一个表, 假设这个表名为user, 则我们会这样写
SELECT * FROM user WHERE username = 'zhangsan';
# 可以得到
|username|
|--------|
|zhangsan|
# 所以, 我们把user替换为刚才的SELECT语句, 并用小括号将SELECT语句括起来
# 替换结果类似于下面这样
# SELECT * FROM user WHERE username = 'zhangsan';
# SELECT * FROM (SELECT username FROM user_table1) AS user WHERE username = 'zhangsan';
# 执行一下
SELECT * FROM (SELECT username FROM user_table1) WHERE username = 'zhangsan';
# 也可以得到
|username|
|--------|
|zhangsan|
# 这就是子查询, 括号内的查询相当于父查询语句的一个孩子, 使用子查询的结果作为表继续查询
SELECT sname FROM sort;
sheet = SELECT sname FROM sort;
SELECT * FROM (SELECT sname FROM sort) AS z WHERE sname = '家电';
```