SQL学习笔记-分组聚合、子查询、关联操作与窗口函数

目录

聚合函数(aggregation function)---也就是组函数

一、基础知识

MySQL最常用分组聚合函数

1、count函数

2、max和min函数---统计列中的最大最小值

3、sum和avg函数---求和与求平均

二、分组SELECT

1、group by子句

2、GROUP_CONCAT()函数

3、with rollup子句:用来要求在一条group by子句中进行多个不同的分组

4、HAVING子句:对分组结果进行过滤

三、集合查询操作

四、分组聚合函数练习题

1、SQL86 返回每个订单号各有多少行数

2、SQL87 每个供应商成本最低的产品

3、SQL88 返回订单数量总和不小于100的所有订单的订单号

4、计算总和

子查询

一、基础知识

1、标量子查询(scalar subquery):返回1行1列一个值

2、行子查询(row subquery):返回的结果集是 1 行 N 列

3、列子查询(column subquery):返回的结果集是 N 行 1列

4、表子查询(table subquery):返回的结果集是 N 行 N 列

带EXISTS关键字的相关子查询(EXISTS存在)

三、子查询练习题

1.SQL94 返回每个顾客不同订单的总金额

关联操作——JOIN、UNION

一、JOIN

语法:

1.SQL INNER JOIN 语法

2.SQL LEFT JOIN 关键字

SQL LEFT JOIN 语法

3.SQL RIGHT JOIN 关键字

SQL RIGHT JOIN 语法

4.SQL FULL OUTER JOIN 关键字

SQL FULL OUTER JOIN 语法

二、SQL UNION 操作符

SQL UNION 语法

SQL UNION ALL 语法

 三、联结

3.创建联结3.1 使用联结

3.2.内部联结

3.3 联结多个表

3.4 创建高级联结


聚合函数(aggregation function)---也就是组函数

一、基础知识

MySQL最常用分组聚合函数

在一个行的集合(一组行)上进行操作,对每个组给一个结果。

常用的组函数:

AVG([distinct] expr)

求平均值

COUNT({*|[distinct] } expr)

统计行的数量

MAX([distinct] expr)

求最大值

MIN([distinct] expr)

求最小值

SUM([distinct] expr)

求累加和

  ①每个组函数接收一个参数

  ②默认情况下,组函数忽略列值为null的行,不参与计算

  ③有时,会使用关键字distinct剔除字段值重复的条数

注意:

  1)当使用组函数的select语句中没有group by子句时,中间结果集中的所有行自动形成一组,然后计算组函数;

  2)组函数不允许嵌套,例如:count(max(…));

  3)组函数的参数可以是列或是函数表达式;

  4)一个SELECT子句中可出现多个聚集函数。

1、count函数

①count(*):返回表中满足where条件的行的数量

mysql> select count(*) from salary_tab where salary='1000'; #salary为列名
+----------+
| count(*) |
+----------+
|        2 |
+----------+

mysql> select count(*) from salary_tab;  #没有条件,默认统计表数据行数
+----------+
| count(*) |
+----------+
|        5 |
+----------+

②count(列):返回列值非空的行的数量

mysql> select count(salary) from salary_tab; #返回salary列非空
+---------------+
| count(salary) |
+---------------+
|             4 |
+---------------+

③count(distinct 列):返回列值非空的、并且列值不重复的行的数量

mysql> select count(distinct salary) from salary_tab; #distinct剔除salary列重复的
+------------------------+
| count(distinct salary) |
+------------------------+
|                      3 |
+------------------------+

④count(expr):根据表达式统计数据

mysql> select * from TT;
+------+------------+
| UNIT | DATE       |
+------+------------+
| a    | 2018-04-03 |
| a    | 2017-12-12 |
| b    | 2018-01-01 |
| b    | 2018-04-03 |
| c    | 2016-06-06 |
| d    | 2018-03-03 |
+------+------------+
6 rows in set (0.00 sec)

mysql> select UNIT as '单位',
    ->     COUNT(TO_DAYS(DATE)=TO_DAYS(NOW()) or null) as '今日统计',
    ->     COUNT(YEAR(DATE)=YEAR(NOW()) or null) as '今年统计'
    -> from v_jjd
    -> group by JJDW;
+------+----------+----------+
| 单位  | 今日统计  | 今年统计  |
+------+----------+----------+
| a    |        1 |        1 |
| b    |        1 |        2 |
| c    |        0 |        0 |
| d    |        0 |        1 |
+------+----------+----------+
4 rows in set (0.00 sec)

2、max和min函数---统计列中的最大最小值

mysql> select max(salary) from salary_tab;
+-------------+
| max(salary) |
+-------------+
|     3000.00 |
+-------------+

mysql> select min(salary) from salary_tab;
+-------------+
| min(salary) |
+-------------+
|     1000.00 |
+-------------+

注意:如果统计的列中只有NULL值,那么MAX和MIN就返回NULL

3、sum和avg函数---求和与求平均

!!表中列值为null的行不参与计算

mysql> select sum(salary) from salary_tab;
+-------------+
| sum(salary) |
+-------------+
|     7000.00 |
+-------------+

mysql> select avg(salary) from salary_tab;
+-------------+
| avg(salary) |
+-------------+
| 1750.000000 |
+-------------+

mysql> select avg(ifnull(salary,0)) from salary_tab; #ifnull函数将salary列为空的对应值填充为0
+-----------------------+
| avg(ifnull(salary,0)) |
+-----------------------+
|           1400.000000 |
+-----------------------+

注意:要想列值为NULL的行也参与组函数的计算,必须使用IFNULL函数对NULL值做转换。

二、分组SELECT

SELECT select_expr [, select_expr ...]

    [FROM table_references

      [PARTITION partition_list]

    [WHERE where_condition]

    [GROUP BY {col_name | expr | position}

      [ASC | DESC], ... [WITH ROLLUP]]

    [HAVING where_condition]

    [ORDER BY {col_name | expr | position}

      [ASC | DESC], ...]

[LIMIT {[offset,] row_count | row_count OFFSET offset}]

分组SELECT的基本格式:

  select [聚合函数] 字段名 from 表名

    [where 查询条件]

    [group by 字段名]

    [having 过滤条件]

1、group by子句

  根据给定列或者表达式的每一个不同的值将表中的行分成不同的组,使用组函数返回每一组的统计信息

规则:

  ①出现在SELECT子句中的单独的列,必须出现在GROUP BY子句中作为分组列

  ②分组列可以不出现在SELECT子句中

  ③分组列可出现在SELECT子句中的一个复合表达式中

  ④如果GROUP BY后面是一个复合表达式,那么在SELECT子句中,它必须整体作为一个表达式的一部分才能使用。

1)指定一个列进行分组

mysql> select salary,count(*) from salary_tab
    -> where salary>=2000
    -> group by salary;   
+---------+----------+
| salary  | count(*) |
+---------+----------+
| 2000.00 |        1 |
| 3000.00 |        1 |
+---------+----------+

2)指定多个分组列,‘大组中再分小组’

mysql> select userid,count(salary) from salary_tab
    -> where salary>=2000
    -> group by salary,userid;   
+--------+---------------+
| userid | count(salary) |
+--------+---------------+
|      2 |             1 |
|      3 |             1 |
+--------+---------------+

3)根据表达式分组

mysql> select year(payment_date),count(*)
    -> from PENALTIES
    -> group by year(payment_date);
+--------------------+----------+
| year(payment_date) | count(*) |
+--------------------+----------+
|               1980 |        3 |
|               1981 |        1 |
|               1982 |        1 |
|               1983 |        1 |
|               1984 |        2 |
+--------------------+----------+
5 rows in set (0.00 sec)

4)带有排序的分组:如果分组列和排序列相同,则可以合并group by和order by子句

mysql> select teamno,count(*)
    -> from MATCHES
    -> group by teamno
    -> order by teamno desc;
+--------+----------+
| teamno | count(*) |
+--------+----------+
|      2 |        5 |
|      1 |        8 |
+--------+----------+
2 rows in set (0.00 sec)

mysql> select teamno,count(*)
    -> from MATCHES
    -> group by teamno desc;  #可以把desc(或者asc)包含到group by子句中简化
+--------+----------+
| teamno | count(*) |
+--------+----------+
|      2 |        5 |
|      1 |        8 |
+--------+----------+
2 rows in set (0.00 sec)

对于分组聚合注意:

  通过select在返回集字段中,这些字段要么就要包含在group by语句后面,作为分组的依据,要么就要被包含在聚合函数中。我们可以将group by操作想象成如下的一个过程:首先系统根据select语句得到一个结果集,然后根据分组字段,将具有相同分组字段的记录归并成了一条记录。这个时候剩下的那些不存在与group by语句后面作为分组依据的字段就很有可能出现多个值,但是目前一种分组情况只有一条记录,一个数据格是无法放入多个数值的,所以这个时候就需要通过一定的处理将这些多值的列转化成单值,然后将其放在对应的数据格中,那么完成这个步骤的就是前面讲到的聚合函数,这也就是为什么这些函数叫聚合函数了。

2、GROUP_CONCAT()函数

  函数的值等于属于一个组的指定列的所有值,以逗号隔开,并且以字符串表示。

例1:对于每个球队,得到其编号和所有球员的编号

mysql> select teamno,group_concat(playerno)
    -> from MATCHES
    -> group by teamno;
+--------+------------------------+
| teamno | group_concat(playerno) |
+--------+------------------------+
|      1 | 6,6,6,44,83,2,57,8     |
|      2 | 27,104,112,112,8       |
+--------+------------------------+
2 rows in set (0.01 sec)

如果没有group by子句,group_concat返回一列的所有值

例2:得到所有的罚款编号列表

mysql> select group_concat(paymentno)
    -> from PENALTIES;
+-------------------------+
| group_concat(paymentno) |
+-------------------------+
| 1,2,3,4,5,6,7,8         |
+-------------------------+
1 row in set (0.00 sec)

3、with rollup子句:用来要求在一条group by子句中进行多个不同的分组

用的比较少点,但是有时可以根据具体的需求使用

  如果有子句GROUP BY E1,E2,E3,E4 WITH ROLLUP

  那么将分别执行以下分组:[E1,E2,E3,E4]、[E1,E2,E3]、[E1,E2]、[E1]、[]

注意:[ ]表示所有行都分在一组中

示例:按照球员的性别和居住城市,统计球员的总数;统计每个性别球员的总数;统计所有球员的总数

mysql> select sex,town,count(*)
    -> from PLAYERS
    -> group by sex,town with rollup;
+-----+-----------+----------+
| sex | town      | count(*) |
+-----+-----------+----------+
| F   | Eltham    |        2 |
| F   | Inglewood |        1 |
| F   | Midhurst  |        1 |
| F   | Plymouth  |        1 |
| F   | NULL      |        5 |
| M   | Douglas   |        1 |
| M   | Inglewood |        1 |
| M   | Stratford |        7 |
| M   | NULL      |        9 |
| NULL | NULL      |       14 |
+-----+-----------+----------+
10 rows in set (0.00 sec)

4、HAVING子句:对分组结果进行过滤

注意:

  不能使用WHERE子句对分组后的结果进行过滤

  不能在WHERE子句中使用组函数,仅用于过滤行

mysql> select playerno
    -> from PENALTIES
    -> where count(*)>1
    -> group by playerno;
ERROR 1111 (HY000): Invalid use of group function

因为WHERE子句比GROUP BY先执行,而组函数必须在分完组之后才执行,且分完组后必须使用having子句进行结果集的过滤。

基本语法:

SELECT   select_expr [, select_expr ...]

   FROM  table_name

   [WHERE where_condition]

   [GROUP BY {col_name | expr} [ASC | DESC], ... [WITH ROLLUP]]

[HAVING where_condition]

!!!having子语句与where子语句区别:

  where子句在分组前对记录进行过滤;

  having子句在分组后对记录进行过滤

mysql> select salary,count(*) from salary_tab
    -> where salary>=2000
    -> group by salary
    -> having count(*)>=0;
+---------+----------+
| salary  | count(*) |
+---------+----------+
| 2000.00 |        1 |
| 3000.00 |        1 |
+---------+----------+

1)HAVING可以单独使用而不和GROUP BY配合,如果只有HAVING子句而没有GROUP BY,表中所有的行分为一组

2)HAVING子句中可以使用组函数

3)HAVING子句中的列,要么出现在一个组函数中,要么出现在GROUP BY子句中(否则出错)

mysql> select town,count(*)
    -> from PLAYERS
    -> group by town
    -> having birth_date>'1970-01-01';
ERROR 1054 (42S22): Unknown column 'birth_date' in 'having clause'
mysql> select town,count(*)
    -> from PLAYERS
    -> group by town
    -> having town in ('Eltham','Midhurst');
+----------+----------+
| town     | count(*) |
+----------+----------+
| Eltham   |        2 |
| Midhurst |        1 |
+----------+----------+
2 rows in set (0.00 sec)

三、集合查询操作

  union用于把两个或者多个select查询的结果集合并成一个

SELECT ...

UNION [ALL | DISTINCT]

SELECT ...

[UNION [ALL | DISTINCT]

SELECT ...]

默认情况下,UNION = UNION DISTINCT

  ①进行合并的两个查询,其SELECT列表必须在数量和对应列的数据类型上保持一致;

  ②默认会去掉两个查询结果集中的重复行;默认结果集不排序;

  ③最终结果集的列名来自于第一个查询的SELECT列表

UNION  ALL不去掉结果集中重复的行

注:联合查询结果使用第一个select语句中的字段名

mysql> select * from t1;
+------+------+
| num  | addr |
+------+------+
|  123 | abc  |
|  321 | cba  |
+------+------+
2 rows in set (0.00 sec)

mysql> select * from t2;
+------+------+
| id   | name |
+------+------+
|    1 | a    |
|    2 | A    |
+------+------+
2 rows in set (0.00 sec)

mysql> select * from t1
    -> union
    -> select * from t2;
+------+------+
| num  | addr |
+------+------+
|  123 | abc  |
|  321 | cba  |
|    1 | a    |
|    2 | A    |
+------+------+
4 rows in set (0.00 sec)

如果要对合并后的整个结果集进行排序,ORDER BY子句只能出现在最后面的查询中

注意:

  在去重操作时,如果列值中包含NULL值,认为它们是相等的

四、分组聚合函数练习题

1、SQL86 返回每个订单号各有多少行数

select order_num,count(order_num) as order_lines #order_num列的count改名为order_lines
from OrderItems
group by order_num
order by order_lines #默认asc升序

知识点:
1、count(*),count(列名)都可以,区别在于,count(列名)是统计非NULL的行数
2、order by最后执行,所以可以使用列别名
3、分组聚合一定不要忘记加上 group by ,不然只会有一行结果

2、SQL87 每个供应商成本最低的产品

常规解题思路:

select vend_id, min(prod_price) cheapest_item from Products group by vend_id order by cheapest_item

利用函数思路: 先用函数生成一个符合vend_id分组并根据prod_price的新表 然后嵌套查询这个新表+ where条件找到想要检索的结果

over(partition by )函数使用 1、partition by 字段名字A:子句进行分组,partition by是固定的分组语法; 2、order by 字段名字B:子句进行排序,order by 是固定的排序语法。 3、OVER()函数不能单独使用,必须跟在 排名函数( ROW_NUMBER、DENSE_RANK、RANK、NTILE) 或 5种聚合函数(SUM、MAX、MIN、AVG、COUNT)后边。

select vend_id,cheapest_item from (select vend_id,prod_price as cheapest_item,row_number() over(partition by vend_id order by prod_price) as rk from Products) as vind_min where rk=1 order by cheapest_item

3、SQL88 返回订单数量总和不小于100的所有订单的订单号

select order_num
from OrderItems
group by order_num
having sum(quantity)>=100
order by order_num
三个方法 还是直接用聚合比较方便


```# 子查询
 select order_num
 from ( select order_num, sum(quantity) as sum_num
                     from OrderItems group by order_num having sum_num >= 100
 ) a
 order by order_num;
 
# 直接使用聚合
 select order_num
 from OrderItems
 group by order_num
 having  sum(quantity) >= 100
 
# order by order_num
 
# 窗口函数
select order_num
from (
select order_num,sum(quantity)
    over (partition by order_num order by quantity ) as sum_1
    from OrderItems
) as tb1
where sum_1 >= 100
order by order_num;

4、计算总和

OrderItems表代表订单信息,包括字段:订单号order_num和item_price商品售出价格、quantity商品数量。

order_numitem_pricequantity
a110105
a211100
a21200
a421121
a5510
a2119
a775

【问题】编写 SQL 语句,根据订单号聚合,返回订单总价不小于1000 的所有订单号,最后的结果按订单号进行升序排序。

提示:总价 = item_price 乘以 quantity

【示例结果】

order_numtotal_price
a11050
a21319
a42242

解题代码:

SELECT order_num, SUM(quantity*item_price) AS total_price
FROM OrderItems
GROUP BY order_num
HAVING SUM(quantity*item_price) >= 1000
ORDER BY order_num ASC;

注:如果把item_price移到括号外面会报错,因为聚合函数的结果乘以一个item_price,它并不知道要乘以哪个item_price。

子查询

一、基础知识

子查询的定义:

  子查询是将一个查询语句嵌套在另一个查询语句中

  在特定情况下,一个查询语句的条件需要另一个查询语句来获取,内层查询(inner query)语句的查询结果,可以为外层查询(outer query)语句提供查询条件。

特点(规范):

  ①子查询必须放在小括号中

  ②子查询一般放在比较操作符的右边,以增强代码可读性

  ③子查询(小括号里的内容)可出现在几乎所有的SELECT子句中(如:SELECT子句、FROM子句、WHERE子句、ORDER BY子句、HAVING子句……)

(相关、不相关)子查询分类:

  ①标量子查询(scalar subquery):返回1行1列一个值

  ②行子查询(row subquery):返回的结果集是 1 行 N 列

  ③列子查询(column subquery):返回的结果集是 N 行 1列

  ④表子查询(table subquery):返回的结果集是 N 行 N 列

可以使用的操作符:= > < >= <= <> ANY IN SOME ALL EXISTS

注意:一个子查询会返回一个标量(就一个值)、一个行、一个列或一个表,这些子查询称之为标量、行、列和表子查询

  1、如果子查询返回一个标量值(就一个值),那么外部查询就可以使用:=、>、<、>=、<=和<>符号进行比较判断;

  2、如果子查询返回的不是一个标量值,而外部查询使用了比较符和子查询的结果进行了比较,那么就会抛出异常。

一、不相关子查询

  不相关,主查询和子查询是不相关的关系。也就是意味着在子查询中没有使用到外部查询的表中的任何列。

  先执行子查询,然后执行外部查询

1、标量子查询(scalar subquery):返回1行1列一个值

  因为是标量子查询,结果是一个值,所以可用来进行算数运算。

  可以使用 = > < >= <= <> 操作符对子查询的结果进行比较:

mysql> select num,name
    -> from employee
    -> where d_id=(
    ->   select d_id
    ->   from department
    ->   where d_name='科技部');
+------+--------+
| num  | name   |
+------+--------+
|    1 | 张三    |
|    2 | 李四    |
+------+--------+
mysql> select num,name
    -> from employee
    -> where d_id=(
    ->   select d_id
    ->   from department
    ->   where d_name='财务部');
Empty set (0.00 sec)
注意:如果子查询返回空值,可能导致外部查询的where条件也为空,从而外部查询的结果集为空。
mysql> SELECT  playerno,town,sex
    -> FROM  PLAYERS
    -> WHERE (town,sex) = ((SELECT town FROM PLAYERS WHERE playerno=7),
    ->                     (SELECT sex FROM PLAYERS WHERE playerno=44));
+----------+-----------+-----+
| playerno | town      | sex |
+----------+-----------+-----+
|        2 | Stratford | M   |
|        6 | Stratford | M   |
|        7 | Stratford | M   |
|       39 | Stratford | M   |
|       57 | Stratford | M   |
|       83 | Stratford | M   |
|      100 | Stratford | M   |
+----------+-----------+-----+
7 rows in set (0.01 sec)
注意: (列,列,…)叫做行表达式,比较时是比较列的组合。

2、行子查询(row subquery):返回的结果集是 1 行 N 列

  使用行表达式进行比较,可以使用 = > < >= <= <> in操作符

mysql> select d_id from department;
+------+
| d_id |
+------+
| 1001 |
| 1002 |
| 1003 |
+------+

mysql> select * from employee
    -> where d_id in
    ->   (select d_id from department);
+------+------+--------+------+------+--------------------+
| num  | d_id | name   | age  | sex  | homeaddr           |
+------+------+--------+------+------+--------------------+
|    1 | 1001 | 张三    |   26 | 男   | 北京市海淀区         |
|    2 | 1001 | 李四    |   24 | 女   | 上海市黄浦区         |
|    3 | 1002 | 王五    |   25 | 男   | 江西省赣州市         |
+------+------+--------+------+------+--------------------+

解析:此处首先查询出department表中所有d_id字段的信息,并将结果作为条件,接着查询employee表中以d_id为条件的所有字段信息;NOT IN的效果与上面刚好相反。

3、列子查询(column subquery):返回的结果集是 N 行 1列

  必须使用 IN、ANY 和 ALL 操作符对子查询返回的结果进行比较

注意:ANY 和 ALL 操作符不能单独使用,其前面必须加上单行比较操作符= > < >= <= <>

1)带ANY关键字的子查询:ANY关键字表示满足其中任一条件

mysql> select * from employee
    -> where d_id !=any
    ->   (select d_id from department);
+------+------+--------+------+------+--------------------+
| num  | d_id | name   | age  | sex  | homeaddr           |
+------+------+--------+------+------+--------------------+
|    1 | 1001 | 张三    |   26 | 男   | 北京市海淀区         |
|    2 | 1001 | 李四    |   24 | 女   | 上海市黄浦区         |
|    3 | 1002 | 王五    |   25 | 男   | 江西省赣州市         |
|    4 | 1004 | Aric   |   15 | 男   | England            |
+------+------+--------+------+------+--------------------+

2)带ALL关键字的子查询:ALL关键字表示满足其中所有条件

mysql> select * from employee
    -> where d_id >=all
    ->   (select d_id from department);
+------+------+------+------+------+----------+
| num  | d_id | name | age  | sex  | homeaddr |
+------+------+------+------+------+----------+
|    4 | 1004 | Aric |   15 | 男    | England |
+------+------+------+------+------+----------+

注意:如果子查询的结果集中有null值,使用>ALL 和not in操作符时,必须去掉子查询结果集中的null值,否则查询结果错误

mysql> select * from department
    -> where d_id >all    #>all背后执行and操作
    ->   (select d_id from employee);
Empty set (0.01 sec)
结果为空:子查询的结果集中包含null值(子查询结果集中没有主查询里的1004行,则为空)

4、表子查询(table subquery):返回的结果集是 N 行 N 列

  必须使用 IN、ANY 和 ALL 操作符对子查询返回的结果进行比较

示例:在committee_members表中,得到任职日期和卸任日期与具有Secretary职位的一行相同的所有行

mysql> select *
    -> from COMMITTEE_MEMBERS
    -> where (begin_date,end_date) in
    -> (
    ->   select begin_date,end_date
    ->   from COMMITTEE_MEMBERS
    ->   where position='Secretary'
    -> );
+----------+------------+------------+-----------+
| PLAYERNO | BEGIN_DATE | END_DATE   | POSITION  |
+----------+------------+------------+-----------+
|        6 | 1990-01-01 | 1990-12-31 | Secretary |
|        8 | 1990-01-01 | 1990-12-31 | Treasurer |
|        8 | 1991-01-01 | 1991-12-31 | Secretary |
|       27 | 1990-01-01 | 1990-12-31 | Member    |
|       27 | 1991-01-01 | 1991-12-31 | Treasurer |
|       57 | 1992-01-01 | 1992-12-31 | Secretary |
|      112 | 1992-01-01 | 1992-12-31 | Member    |
+----------+------------+------------+-----------+
7 rows in set (0.05 sec)

二、相关子查询(correlated subquery)

  在子查询中使用到了外部查询的表中的任何列。

  先执行外部查询,然后执行子查询

相关子查询的执行步骤:

  ①先执行外部查询,得到的行叫做候选行

  ②使用某个候选行来执行子查询

  ③使用子查询的返回值来决定该候选行是出现在最终的结果集中还是被丢弃

  ④重复以上步骤2和3,将所有的候选行处理完毕,得到最终的结果

示例:得到项目是‘研发产品’的雇员的编号

mysql> select num
    -> from employee
    -> where '研发产品'=(
    ->   select function
    ->   from department
    ->   where d_id=employee.d_id);
+------+
| num  |
+------+
|    1 |
|    2 |
+------+

解析:

  1)主查询得到候选行,一行一行的拿去执行子查询;

  2)主查询表employee的候选行的d_id和子查询的d_id匹配,返回值进行where过滤;

  3)符合,加入最终结果集;

  4)不符合,将候选行丢弃,接着进行处理下一个候选行。

带EXISTS关键字的相关子查询(EXISTS存在)

专门判断子查询的结果集是否不为空:

  非空空返回true

  空返回false

当返回的值为true时,外层查询语句将进行查询,否则不进行查询

mysql> select * from employee
    -> where exists
    -> (select d_name from department where d_id=1004);
Empty set (0.00 sec)

此处内层循环并没有查询到满足条件的结果,因此返回false,外层查询不执行

EXISTS关键字可以与其他的查询条件一起使用,条件表达式与EXISTS关键字之间用AND或者OR来连接

mysql> select * from employee
    -> where age>24 and exists
    -> (select d_name from department where d_id=1003);
+------+------+--------+------+------+--------------------+
| num  | d_id | name   | age  | sex  | homeaddr           |
+------+------+--------+------+------+--------------------+
|    1 | 1001 | 张三    |   26 | 男   | 北京市海淀区         |
|    3 | 1002 | 王五    |   25 | 男   | 江西省赣州市         |
+------+------+--------+------+------+--------------------+

三、子查询练习题

1.SQL94 返回每个顾客不同订单的总金额

我们需要一个顾客 ID 列表,其中包含他们已订购的总金额。

OrderItems表代表订单信息,OrderItems表有订单号:order_num和商品售出价格:item_price、商品数量:quantity。

order_numitem_pricequantity
a000110105
a000211100
a00021200
a001321121
a0003510
a0003119
a000375

Orders表订单号:order_num、顾客id:cust_id

order_numcust_id
a0001cust10
a0002cust1
a0003cust1
a0013cust2

【问题】

编写 SQL语句,返回顾客 ID(Orders 表中的 cust_id),并使用子查询返回total_ordered 以便返回每个顾客的订单总数,将结果按金额从大到小排序。

提示:你之前已经使用 SUM()计算订单总数。

【示例结果】返回顾客id cust_id和total_order下单总额

cust_idtotal_ordered
cust22242
cust11300
cust101050
cust2104

【示例解析】cust2在Orders里面的订单a0013,a0013的售出价格是2售出数量是1121,总额是2242,最后返回cust2的支付总额是2242。

答案:

select
cust_id,                                #返回的列1
(select
 SUM(item_price*quantity)
FROM OrderItems as a
WHERE a.order_num=b.order_num)     #返回的两列对应
as total_ordered      #返回的列2
from Orders as b
ORDER BY total_ordered DESC

关联操作——JOIN、UNION


一、JOIN

SQL join 用于把来自两个或多个表的行结合起来。

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== 编辑

SQL JOIN 子句用于把来自两个或多个表的行结合起来,基于这些表之间的共同字段。

最常见的 JOIN 类型:SQL INNER JOIN(简单的 JOIN)。 SQL INNER JOIN 从多个表中返回满足 JOIN 条件的所有行。

语法:

SELECT column1, column2, ...
FROM table1
JOIN table2 ON condition;

参数说明:

  • column1, column2, ...:要选择的字段名称,可以为多个字段。如果不指定字段名称,则会选择所有字段。
  • table1:要连接的第一个表。
  • table2:要连接的第二个表。
  • condition:连接条件,用于指定连接方式。


1.SQL INNER JOIN 语法

SELECT column_name(s)
FROM table1
INNER JOIN table2
ON table1.column_name=table2.column_name;

或:

SELECT column_name(s)
FROM table1
JOIN table2
ON table1.column_name=table2.column_name;

注释:INNER JOIN 与 JOIN 是相同的。

2.SQL LEFT JOIN 关键字

LEFT JOIN 关键字从左表(table1)返回所有的行,即使右表(table2)中没有匹配。如果右表中没有匹配,则结果为 NULL。

SQL LEFT JOIN 语法

SELECT column_name(s)
FROM table1
LEFT JOIN table2
ON table1.column_name=table2.column_name;

或:

SELECT column_name(s)
FROM table1
LEFT OUTER JOIN table2
ON table1.column_name=table2.column_name;

注释:在某些数据库中,LEFT JOIN 称为 LEFT OUTER JOIN。

 

3.SQL RIGHT JOIN 关键字

RIGHT JOIN 关键字从右表(table2)返回所有的行,即使左表(table1)中没有匹配。如果左表中没有匹配,则结果为 NULL。

SQL RIGHT JOIN 语法

SELECT column_name(s)
FROM table1
RIGHT JOIN table2
ON table1.column_name=table2.column_name;

或:

SELECT column_name(s)
FROM table1
RIGHT OUTER JOIN table2
ON table1.column_name=table2.column_name;

注释:在某些数据库中,RIGHT JOIN 称为 RIGHT OUTER JOIN。



4.SQL FULL OUTER JOIN 关键字

FULL OUTER JOIN 关键字只要左表(table1)和右表(table2)其中一个表中存在匹配,则返回行.FULL OUTER JOIN 关键字结合了 LEFT JOIN 和 RIGHT JOIN 的结果。

SQL FULL OUTER JOIN 语法

SELECT column_name(s)
FROM table1
FULL OUTER JOIN table2
ON table1.column_name=table2.column_name;

 

二、SQL UNION 操作符

UNION 操作符用于合并两个或多个 SELECT 语句的结果集。

请注意,UNION 内部的每个 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每个 SELECT 语句中的列的顺序必须相同

SQL UNION 语法

SELECT column_name(s) FROM table1
UNION
SELECT column_name(s) FROM table2;

注释:默认地,UNION 操作符选取不同的值。如果允许重复的值,请使用 UNION ALL。

SQL UNION ALL 语法

SELECT column_name(s) FROM table1
UNION ALL
SELECT column_name(s) FROM table2;

注释:UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名。

 三、联结

3.创建联结
3.1 使用联结

联结的创建需要规定要联结的表以及它们如何关联。

SELECT a.column1_name,b.column2_name 
FROM table1 a,table2 b 
WHERE a.column1 = b.column2;

完全限定名:在引用的列可能出现二义性时,必须使用完全限定列名(用一个分隔表名和列名),如果引用一个没有用表名限制的具有二义性的列名,MySQL将返回错误

笛卡尔积(cartesian product):由没有联结条件的表关系返回的结果为笛卡尔积。检索出的行的数目将是第一个表中的行数乘以第二个表中的行数。

应该保证所有的联结都有对应的WHERE子句,否则MySQL将返回比想要的数据多得多得数据。同理应该保证WHERE子句的正确性,不正确的过滤条件将导致MySQL返回不正确的数据。

3.2.内部联结

以上举的联结例子称为等值联结(equijoin),它基于两个表之间的相等测试,这种联结也称为内部联结,对于这种联结还可以使用另一种语法表示:

SELECT a.column1_name,b.column2_name 
FROM table1 a 
INNER JOIN table2 b 
ON a.column1 = b.column2;

3.3 联结多个表

SELECT a.column1_name,b.column2_name,c.column3_name 
FROM table1 a,table2 b,table3 c 
WHERE a.column1_name = b.column2_name 
AND a.column2_name = c.column3_name; 

性能考虑:
MySQL在运行时关联指定的每个表以处理联结,这种处理可能是非常耗费资源的,因此应该仔细,不要联结不必要的表,联结的表越多,性能下降越厉害。


3.4 创建高级联结

①使用别名:别名不但可以用在列以及计算字段,还可以给表起别名

SELECT a.column1_name,b.column2_name 
FROM table1 AS a 
INNER JOIN table2 AS b 
ON a.column1 = b.column2;

其中AS可以省略

应该注意表别名只在查询执行中使用。与列别名不一样,表别名不返回到客户机
②自联结

SELECT a.column1_name,a.column2_name 
FROM table a, table b 
WHERE a.column1 = b.column1 AND b.column2_name = "xxx";

此查询中需要的两个表实际上是相同的表,因此表table在FROM子句中出现了两次。为了避免二义性,使用了表别名

用自联结而不用子查询:自联结通常作为外部语句用来替代从相同表中检索数据时使用的子查询语句,虽然最终的结果是相同的,但有时候处理联结远比处理子查询快得多。
③自然联结:无论何时对表进行联结,应该至少有一个列出现不止在一个表中(被联结的列)。标准的联结返回的数据,甚至相同的列多次出现。自然联结排除多次出现,使每个列只返回一次。一般通过对表使用通配符(SELECT *),对所有其他表使用明确的子集来完成。如下:

SELECT a.*,b.column2_name 
FROM table1 AS a 
JOIN table2 AS b 
ON a.column1 = b.column2;

④外部联结:许多联结将一个表中的行与另一个表中的行相关联,有时候需要包含没有关联行的那些行。

SELECT a.*,b.column2_name 
FROM table1 AS a 
LEFT JOIN table2 AS b 
ON a.column1 = b.column2;

外部联结的类型:左外联结(LEFT JOIN)和右外联结(RIGHT JOIN)
外部联结:联结包含了在相关表中没有关联行的行。
左外部联结表示从左边表中选出所有行,右外部联结表示右边表中显示所有行
SELECT a.*,b.column2_name FROM table1 AS a LEFT OUTER JOIN table2 AS b ON a.column1 = b.column2;

SELECT a.*,b.column2_name 
FROM table1 AS a 
LEFT OUTER JOIN table2 AS b 
ON a.column1 = b.column2;

OUTER可以省略
⑤使用带聚集函数的联结

SELECT a.*,b.column2_name,COUNT(a.column_name) AS num_column 
FROM table1 AS a 
LEFT OUTER JOIN table2 AS b 
ON a.column1 = b.column2;

 四、练习题

1.SQL97 返回顾客名称和相关订单号以及每个订单的总价

描述

Customers 表有字段,顾客名称:cust_name、顾客id:cust_id

cust_idcust_name
cust10andy
cust1ben
cust2tony
cust22tom
cust221an
cust2217hex

Orders订单信息表,含有字段,订单号:order_num、顾客id:cust_id

order_numcust_id
a1cust10
a2cust1
a3cust2
a4cust22
a5cust221
a7cust2217

OrderItems表有字段,商品订单号:order_num、商品数量:quantity、商品价格:item_price

order_numquantityitem_price
a1100010
a220010
a31015
a42550
a51525
a777

【问题】

除了返回顾客名称和订单号,返回 Customers 表中的顾客名称(cust_name)和Orders 表中的相关订单号(order_num),添加第三列 OrderTotal,其中包含每个订单的总价,并按顾客名称再按订单号对结果进行升序排序。

【示例结果】返回顾客名称 cust_name、订单号order_num、订单总额OrderTotal

cust_nameorder_numOrderTotal
ana5375
andya110000
bena22000
hexa749
toma41250
tonya3150
select cust_name,o.order_num,OrderTotal from
(select order_num,sum(quantity*item_price) OrderTotal from OrderItems
group by order_num ) t        #中间表
join Orders o               #第1次联结
on t.order_num = o.order_num
join Customers c            #第2次联结
on o.cust_id = c.cust_id
order by cust_name,o.order_num;

2.SQL100 确定最佳顾客的另一种方式(二)

【问题】编写 SQL 语句,返回订单总价不小于1000 的客户名称和总额(OrderItems 表中的order_num)。

提示:需要计算总和(item_price 乘以 quantity)。按总额对结果进行排序,请使用INNER JOIN 语法。。

#inner join
select c.cust_name,sum(oi.item_price * oi.quantity) as total_price
from Customers c
join Orders o using(cust_id)
join OrderItems oi using(order_num)
group by cust_name #针对cust_name进行分组归类
having total_price >= 1000 #使用having过滤条件
order by total_price
#如果有两张以上的多表查询可以使用:
#select....
#from....
#where.....;
#这样的结构,可以少写两个inner join。
select c.cust_name,sum(oi.item_price*oi.quantity) total_price
from OrderItems oi,Orders o,Customers c
where oi.order_num=o.order_num and o.cust_id=c.cust_id #and联合两个合并条件
group by c.cust_name
having total_price>=1000
order by total_price;

 窗口函数-row_number等

一、概念

窗口函数,也叫OLAP函数(Online Anallytical Processing,联机分析处理),可以对数据库数据进行实时分析处理。

窗口函数的基本语法如下:

<窗口函数> over (partition by <用于分组的列名>
                order by <用于排序的列名>)

那么语法中的<窗口函数>都有哪些呢?

<窗口函数>的位置,可以放以下两种函数:

1) 专用窗口函数,包括后面要讲到的rank, dense_rank, row_number等专用窗口函数。

2) 聚合函数,如sum. avg, count, max, min等

因为窗口函数是对where或者group by子句处理后的结果进行操作,所以窗口函数原则上只能写在select子句中

二、语法

1.专用窗口函数rank

例如下图,是班级表中的内容

如果我们想在每个班级内按成绩排名,得到下面的结果。

以班级“1”为例,这个班级的成绩“95”排在第1位,这个班级的“83”排在第4位。上面这个结果确实按我们的要求在每个班级内,按成绩排名了。

得到上面结果的sql语句代码如下:

select *,
   rank() over (partition by 班级
                 order by 成绩 desc) as ranking
from 班级表

我们来解释下这个sql语句里的select子句。rank是排序的函数。要求是“每个班级内按成绩排名”,这句话可以分为两部分:

1)每个班级内:按班级分组

partition by用来对表分组。在这个例子中,所以我们指定了按“班级”分组(partition by 班级)
2)按成绩排名

order by子句的功能是对分组后的结果进行排序,默认是按照升序(asc)排列。在本例中(order by 成绩 desc)是按成绩这一列排序,加了desc关键词表示降序排列。

通过下图,我们就可以理解partiition by(分组)和order by(在组内排序)的作用了。

窗口函数具备了我们之前学过的group by子句分组的功能和order by子句排序的功能。那么,为什么还要用窗口函数呢?

这是因为,group by分组汇总后改变了表的行数,一行只有一个类别。而partiition by和rank函数不会减少原表中的行数。例如下面统计每个班级的人数。

相信通过这个例子,你已经明白了这个窗口函数的使用:

select *,
   rank() over (partition by 班级
                 order by 成绩 desc) as ranking
from 班级表

现在我们说回来,为什么叫“窗口”函数呢?这是因为partition by分组后的结果称为“窗口”,这里的窗口不是我们家里的门窗,而是表示“范围”的意思。

简单来说,窗口函数有以下功能:

1)同时具有分组和排序的功能

2)不减少原表的行数

3)语法如下:

<窗口函数> over (partition by <用于分组的列名>
                order by <用于排序的列名>)

2.其他专业窗口函数

专用窗口函数rank, dense_rank, row_number有什么区别呢?

它们的区别我举个例子,你们一下就能看懂:

select *,
   rank() over (order by 成绩 desc) as ranking,
   dense_rank() over (order by 成绩 desc) as dese_rank,
   row_number() over (order by 成绩 desc) as row_num
from 班级表

得到结果:

从上面的结果可以看出:

rank函数:这个例子中是5位,5位,5位,8位,也就是如果有并列名次的行,会占用下一名次的位置。比如正常排名是1,2,3,4,但是现在前3名是并列的名次,结果是: 1,1,1,4。

dense_rank函数:这个例子中是5位,5位,5位,6位,也就是如果有并列名次的行,不占用下一名次的位置。比如正常排名是1,2,3,4,但是现在前3名是并列的名次,结果是: 1,1,1,2。

row_number函数:这个例子中是5位,6位,7位,8位,也就是不考虑并列名次的情况。比如前3名是并列的名次,排名是正常的 1,2,3,4。

这三个函数的区别如下:

最后,需要强调的一点是:在上述的这三个专用窗口函数中,函数后面的括号不需要任何参数,保持()空着就可以。

现在,大家对窗口函数有一个基本了解了吗?

3.聚合函数作为窗口函数

聚和窗口函数和上面提到的专用窗口函数用法完全相同,只需要把聚合函数写在窗口函数的位置即可,但是函数后面括号里面不能为空,需要指定聚合的列名

我们来看一下窗口函数是聚合函数时,会出来什么结果:

select *,
   sum(成绩) over (order by 学号) as current_sum,
   avg(成绩) over (order by 学号) as current_avg,
   count(成绩) over (order by 学号) as current_count,
   max(成绩) over (order by 学号) as current_max,
   min(成绩) over (order by 学号) as current_min
from 班级表

得到结果:

有发现什么吗?我单独用sum举个例子:

如上图,聚合函数sum在窗口函数中,是对自身记录、及位于自身记录以上的数据进行求和的结果。比如0004号,在使用sum窗口函数后的结果,是对0001,0002,0003,0004号的成绩求和,若是0005号,则结果是0001号~0005号成绩的求和,以此类推。

不仅是sum求和,平均、计数、最大最小值,也是同理,都是针对自身记录、以及自身记录之上的所有数据进行计算,现在再结合刚才得到的结果(下图),是不是理解起来容易多了?

比如0005号后面的聚合窗口函数结果是:学号0001~0005五人成绩的总和、平均、计数及最大最小值。

如果想要知道所有人成绩的总和、平均等聚合结果,看最后一行即可。

这样使用窗口函数有什么用呢?

聚合函数作为窗口函数,可以在每一行的数据里直观的看到,截止到本行数据,统计数据是多少(最大值、最小值等)。同时可以看出每一行数据,对整体统计数据的影响。

四.注意事项

partition子句可以省略,省略就是不指定分组,结果如下,只是按成绩由高到低进行了排序:

select *,
   rank() over (order by 成绩 desc) as ranking
from 班级表

得到结果:

但是,这就失去了窗口函数的功能,所以一般不要这么使用。

  • 16
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值