8-1 窗口函数

窗口函数也称为OLAP函数。OLAP就是对数据库数据进行实时分析处理。

窗口函数的语法

  1. <窗口函数> OVER ([PARTITION BY <列清单>]
  2. ORDER BY<排序列清单>)

能够作为窗口函数使用的函数

  1. 能够作为窗口函数使用的聚合函数(SUM AVG COUNT MAX MIN)
  2. RANK DENSE_RANK ROW_NUMBER等专用窗口函数

语法的基本使用方法——使用RANK函数

建表

  1. CREATE TABLE Product
  2. (product_id CHAR(4) NOT NULL,
  3. product_name VARCHAR(100) NOT NULL,
  4. product_type VARCHAR(32) NOT NULL,
  5. sale_price INTEGER ,
  6. purchase_price INTEGER ,
  7. regist_date DATE ,
  8. PRIMARY KEY (product_id));
  9. INSERT INTO Product VALUES ('0001', 'T恤' ,'衣服', 1000, 500, '2009-09-20');
  10. INSERT INTO Product VALUES ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11');
  11. INSERT INTO Product VALUES ('0003', '运动T恤', '衣服', 4000, 2800, NULL);
  12. INSERT INTO Product VALUES ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20');
  13. INSERT INTO Product VALUES ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15');
  14. INSERT INTO Product VALUES ('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20');
  15. INSERT INTO Product VALUES ('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28');
  16. INSERT INTO Product VALUES ('0008', '圆珠笔', '办公用品', 100, NULL, '2009-11-11');

RANK是用来计算记录排序的函数。
对Product表中8件商品,根据商品种类(product_type),按照销售单价(sale_price)从低到高排序。

-- 按照不同的商品种类,按照销售单价从低到高排序
mysql> -- Oracle, SQL Server, DB2, PostgreSQL(我使用的是mariadb)
SELECT product_name, product_type, sale_price,
       RANK() OVER (PARTITION BY product_type  ORDER BY sale_price) AS ranking
  FROM Product;
+--------------+--------------+------------+---------+
| product_name | product_type | sale_price | ranking |
+--------------+--------------+------------+---------+
| 圆珠笔       | 办公用品     |        100 |       1 |
| 打孔器       | 办公用品     |        500 |       2 |
| 叉子         | 厨房用具     |        500 |       1 |
| 擦菜板       | 厨房用具     |        880 |       2 |
| 菜刀         | 厨房用具     |       3000 |       3 |
| 高压锅       | 厨房用具     |       6800 |       4 |
| T恤          | 衣服         |       1000 |       1 |
| 运动T恤      | 衣服         |       4000 |       2 |
+--------------+--------------+------------+---------+

PARTITION BY 能够设定排序的对象范围
ORDER BY 能够指定按照哪一列,何种数据进行排序。
image.png

窗口函数兼具GROUP BY 子句的分组功能以及ORDER BY子句的排序功能。但是PARTITION BY子句不具备GROUP BY子句的汇总功能。因此RANK函数不会减少原表中的记录的行数。

专用窗口函数的种类

RANK函数

计算排序时,如果有相同位次的记录,则会跳过之后的位次。例如 有3条记录排在第一位时:1位、1位、1位、4位……

DENSE_RANK

同样是计算排序,即使存在相同位次的记录,也不会跳过之后的位次。有3 条记录排在第1 位时:1 位、1 位、1 位、2 位……

ROW_NUMBER函数

赋予唯一的连续位次
有3 条记录排在第1 位时:1 位、2 位、3 位、4 位……

mysql> -- Oracle, SQL Server, DB2, PostgreSQL
SELECT product_name, product_type, sale_price, 
       RANK() OVER (ORDER BY sale_price) AS ranking,
       DENSE_RANK() OVER (ORDER BY sale_price) AS dense_ranking,
       ROW_NUMBER() OVER (ORDER BY sale_price) AS row_num
 FROM Product;
+--------------+--------------+------------+---------+---------------+---------+
| product_name | product_type | sale_price | ranking | dense_ranking | row_num |
+--------------+--------------+------------+---------+---------------+---------+
| 圆珠笔       | 办公用品     |        100 |       1 |             1 |       1 |
| 叉子         | 厨房用具     |        500 |       2 |             2 |       2 |
| 打孔器       | 办公用品     |        500 |       2 |             2 |       3 |
| 擦菜板       | 厨房用具     |        880 |       4 |             3 |       4 |
| T恤          | 衣服         |       1000 |       5 |             4 |       5 |
| 菜刀         | 厨房用具     |       3000 |       6 |             5 |       6 |
| 运动T恤      | 衣服         |       4000 |       7 |             6 |       7 |
| 高压锅       | 厨房用具     |       6800 |       8 |             7 |       8 |
+--------------+--------------+------------+---------+---------------+---------+
8 rows in set (0.06 sec)

窗口函数的适用范围

原则上窗口函数只能在SELECT子句中使用。
在DBMS内部,窗口函数是对WHERE子句或者GROUP BY子句处理后的结果进行的操作。在得到用户想要的结果之前,即使进行了排序处理,结果也是错误的。在得到排序结果之后,如果通过WHERE子句中的条件除去了某些记录,或者使用了GROUP BY子句进行了汇总,那好不容易得到的排序结果也无法使用了。
**

作为窗口函数使用的聚合函数

SELECT product_id, product_name, sale_price,
       SUM(sale_price) OVER (ORDER BY product_id) AS current_sum
  FROM Product;

image.png

计算平均移动

窗口函数就是将表以窗口为单位进行分割,并在其中进行排序的函数。

SELECT product_id, product_name, sale_price,
       AVG(sale_price) OVER (ORDER BY product_id
                                ROWS 2 PRECEDING) AS moving_avg
  FROM Product;

image.png

指定框架(汇总范围)

上述汇总限定为了“最高近的3行”
使用了“row”和“PRECEDING”两个关键字,将框架指定为“截止到之前~行”,因此”ROW 2 PRECEDING”就是将框架指定为“截止到之前2行”。
使用关键字“FOLLOWING(之后)”,就可以指定“截止到之后~行”。
image.png

将当前记录的前后行作为汇总对象

如果希望当前记录前后行作为汇总对象时,可以同时使用PRECEDING(之前)和FOLLOWING(之后)关键字实现。

SELECT product_id, product_name, sale_price,
       AVG(sale_price) OVER (ORDER BY product_id
                                ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS moving_avg
  FROM Product;

image.png

两个ORDER BY

使用窗口函数必须在OVER子句中使用ORDER BY。但是结果中的记录不会按照该ORDER BY指定的顺序进行排序。
OVER 子句中的ORDER BY只是用来决定窗口函数按照什么样的顺序进行计算的,对结果的排列顺序没没有什么影响。有些DBMS也可以按照窗口函数的ORDER BY子句所指定的顺序对结果进行排序,但这仅仅是个个例而已。

8-2 GROUPING运算符

只使用GROUP BY子句和聚合函数是无法同时得出小计和合计的。如果想同时得到,可以使用GROUPING运算符。

同时得到合计行

GROUP BY子句是用来指定聚合键的场所。所以只会根据这里指定的键分割数据,当然不会出现合计行,而合计行是不指定聚合键时得到的汇总结果。

ROLLUP——同时得出合计和消极

标准SQL引入了GROUPING运算符,使用该运算符可以通过非常简单的SQL得出之前那样单位的不同汇总结果。
GROUPING运算符包含以下3种。

  • ROLLUP
  • CUBE
  • GROUPING SETS

    ROLLUP的使用方法——同时得出合计和小计

    mysql> -- MySQL 
    SELECT product_type, SUM(sale_price) AS sum_price
    FROM Product
    GROUP BY product_type WITH ROLLUP;
    +--------------+-----------+
    | product_type | sum_price |
    +--------------+-----------+
    | 办公用品     | 600       |
    | 厨房用具     | 11180     |
    | 衣服         | 5000      |
    | NULL         | 16780     |
    +--------------+-----------+
    
docker run --name nginx --net=host  -p 8080:80 -d nginx 
docker run --name nginx   -p 8080:80 -d nginx 
docker run -d --net="host" -p 8081:80 --name nginx1 nginx
# 重建docker网络
systemctl stop docker
yum install -y bridge-utils 
pkill docker 
iptables -t nat -F 
ifconfig docker0 down 
brctl delbr docker0 
docker -d 
systemctl restart docker

将“登记日期”添加到聚合建当中

mysql> SELECT product_type, regist_date, SUM(sale_price) AS sum_price
  FROM Product
 GROUP BY product_type, regist_date;
+--------------+-------------+-----------+
| product_type | regist_date | sum_price |
+--------------+-------------+-----------+
| 办公用品     | 2009-09-11  | 500       |
| 办公用品     | 2009-11-11  | 100       |
| 厨房用具     | 2008-04-28  | 880       |
| 厨房用具     | 2009-01-15  | 6800      |
| 厨房用具     | 2009-09-20  | 3500      |
| 衣服         | NULL        | 4000      |
| 衣服         | 2009-09-20  | 1000      |
+--------------+-------------+-----------+
7 rows in set (0.04 sec)

mysql> SELECT product_type, regist_date, SUM(sale_price) AS sum_price
  FROM Product
 GROUP BY product_type, regist_date WITH ROLLUP;
+--------------+-------------+-----------+
| product_type | regist_date | sum_price |
+--------------+-------------+-----------+
| 办公用品     | 2009-09-11  | 500       |
| 办公用品     | 2009-11-11  | 100       |
| 办公用品     | NULL        | 600       |
| 厨房用具     | 2008-04-28  | 880       |
| 厨房用具     | 2009-01-15  | 6800      |
| 厨房用具     | 2009-09-20  | 3500      |
| 厨房用具     | NULL        | 11180     |
| 衣服         | NULL        | 4000      |
| 衣服         | 2009-09-20  | 1000      |
| 衣服         | NULL        | 5000      |
| NULL         | NULL        | 16780     |
+--------------+-------------+-----------+
11 rows in set (0.06 sec)

image.png

-- oracle多列的用法
SELECT product_type, regist_date, SUM(sale_price) AS sum_price
  FROM Product
 GROUP BY ROLLUP(product_type, regist_date);

GROUPING函数——让NULL更加容易分辨

使用ROLLUP得到的结果合计为NULL。SQL提供了一个用来判断超级分组记录的NULL的特定函数——GROUPING函数。该函数在其参数列的值为超级分组记录所产生的NULL时返回1,其他情况返回0。