oracle

关注公众号 jb51net

关闭
首页 > 数据库 > oracle > Oracle窗口函数

Oracle窗口函数详解及练习题总结

作者:IvanCodes

Oracle窗口函数允许用户对查询结果的每一行执行计算,而不会改变原始查询结果的行数或顺序,这篇文章主要介绍了Oracle窗口函数详解及练习题的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

前言

Oracle 窗口函数是SQL语言中一项极其强大的功能,它赋予了你在保留原始行集的同时,对相关数据子集(“窗口”)进行复杂计算的能力。与将多行压缩为一行的标准聚合函数 (GROUP BY) 不同,窗口函数为结果集中的每一行都返回一个独立的计算值。

思维导图

一、窗口函数的通用语法结构

所有窗口函数都遵循一个核心的 OVER() 子句结构,它定义了计算的上下文——“窗口”。

function_name([arguments]) OVER (
  [PARTITION BY partition_expression, ...]
  [ORDER BY sort_expression [ASC|DESC] [NULLS FIRST|NULLS LAST], ...]
  [windowing_clause]
)

二、窗口函数分类与实战

背景表:我们将使用一个简化的 emp 表进行所有演示,包含 empnoenamejobdeptnosalhiredate 等列。

2.1 排名窗口函数

ROW_NUMBER()

SELECT ename, deptno, sal,
  ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY sal DESC) AS row_num_rank
FROM emp;

RANK()

SELECT ename, deptno, sal,
  RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS rank_val
FROM emp;

DENSE_RANK()

SELECT ename, deptno, sal,
  DENSE_RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS dense_rank_val
FROM emp;

NTILE(n)

SELECT ename, deptno, sal,
  NTILE(4) OVER (PARTITION BY deptno ORDER BY sal DESC) AS salary_quartile
FROM emp;

2.2 聚合窗口函数

SUM() / COUNT() / AVG() / MAX() / MIN()

SELECT ename, deptno, sal,
  SUM(sal) OVER (PARTITION BY deptno) AS total_dept_salary,
  ROUND(AVG(sal) OVER (PARTITION BY deptno), 2) AS avg_dept_salary
FROM emp;
SELECT ename, deptno, sal, hiredate,
  SUM(sal) OVER (PARTITION BY deptno ORDER BY hiredate) AS running_total_salary
FROM emp;
SELECT ename, deptno, sal, hiredate,
  ROUND(AVG(sal) OVER (PARTITION BY deptno ORDER BY hiredate ROWS BETWEEN 2 PRECEDING AND CURRENT ROW), 2) AS moving_avg_3_rows
FROM emp;

2.3 位置/偏移窗口函数

LAG(expression, [offset], [default_value])

SELECT ename, deptno, sal,
  LAG(sal, 1, 0) OVER (PARTITION BY deptno ORDER BY sal DESC) AS previous_salary
FROM emp;

LEAD(expression, [offset], [default_value])

SELECT ename, deptno, hiredate,
  LEAD(ename, 1, 'N/A') OVER (PARTITION BY deptno ORDER BY hiredate) AS next_hired_employee
FROM emp;

FIRST_VALUE(expression)

SELECT ename, deptno, hiredate,
  FIRST_VALUE(ename) OVER (PARTITION BY deptno ORDER BY hiredate) AS first_hired_in_dept
FROM emp;

LAST_VALUE(expression)

SELECT ename, deptno, sal,
  LAST_VALUE(ename) OVER (PARTITION BY deptno ORDER BY sal ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS highest_paid_in_dept
FROM emp;

(这里通过薪水升序排列,然后取窗口的最后一行来找到薪水最高者)

NTH_VALUE(expression, n)

SELECT ename, deptno, sal,
  NTH_VALUE(sal, 2) OVER (PARTITION BY deptno ORDER BY sal DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS second_highest_salary
FROM emp;

2.4 统计/分布窗口函数

RATIO_TO_REPORT(expression)

SELECT ename, deptno, sal,
  TO_CHAR(RATIO_TO_REPORT(sal) OVER (PARTITION BY deptno) * 100, '990.99') || '%' AS percentage_of_dept_sal
FROM emp;

PERCENT_RANK()

SELECT ename, deptno, sal,
  ROUND(PERCENT_RANK() OVER (PARTITION BY deptno ORDER BY sal ASC) * 100, 2) AS percentile_rank
FROM emp;

CUME_DIST()

SELECT ename, deptno, sal,
  ROUND(CUME_DIST() OVER (PARTITION BY deptno ORDER BY sal ASC) * 100, 2) AS cumulative_distribution
FROM emp;

三、综合实战案例:构建员工绩效分析报告

这个案例整合了多种窗口函数来生成一份详细的员工分析报告。

目标:对于每一位员工,我们希望得到他/她在其部门内的薪水排名、与部门平均薪水的差距、薪水占部门总额的比例,以及其上司(按薪水排名的上一位)的薪水。

代码示例

WITH emp_analysis AS (
  SELECT
    empno,
    ename,
    deptno,
    sal,
    -- 使用聚合窗口函数计算部门的统计数据
    AVG(sal) OVER (PARTITION BY deptno) AS avg_dept_sal,
    SUM(sal) OVER (PARTITION BY deptno) AS total_dept_sal,
    -- 使用排名窗口函数计算薪水排名
    RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS dept_sal_rank,
    -- 使用位置窗口函数获取上一位员工的薪水
    LAG(sal, 1, 0) OVER (PARTITION BY deptno ORDER BY sal DESC) AS prev_rank_sal
  FROM emp
)
SELECT
  a.ename AS employee_name,
  a.deptno,
  a.sal AS current_salary,
  a.dept_sal_rank,
  ROUND(a.avg_dept_sal, 2) AS department_avg_salary,
  a.sal - ROUND(a.avg_dept_sal, 2) AS diff_from_avg,
  TO_CHAR(a.sal / a.total_dept_sal * 100, '990.99') || '%' AS percentage_of_total,
  a.prev_rank_sal AS superior_salary
FROM emp_analysis a
ORDER BY a.deptno, a.dept_sal_rank;

解析

总结: Oracle 窗口函数是进行复杂数据分析的核心技能。通过灵活运用 PARTITION BYORDER BY, 和窗口范围子句,你可以用简洁的SQL实现过去需要通过自连接、子查询或过程化代码才能完成的复杂逻辑。

练习题

背景表结构:

CREATE TABLE sales_data (
    sale_id          NUMBER(10),
    product_category VARCHAR2(50 CHAR),
    region           VARCHAR2(50 CHAR),
    sale_amount      NUMBER(10, 2),
    sale_date        DATE
);

请为以下每个场景编写使用窗口函数的SQL查询。

题目:

答案与解析

SELECT
  s.*,
  RANK() OVER (PARTITION BY product_category ORDER BY sale_amount DESC) AS category_rank
FROM sales_data s;
SELECT
  s.*,
  SUM(sale_amount) OVER (PARTITION BY region) AS total_region_sales
FROM sales_data s;
SELECT
  s.*,
  SUM(sale_amount) OVER (PARTITION BY region ORDER BY sale_date) AS monthly_running_total
FROM sales_data s;
SELECT
  s.*,
  LAG(sale_amount, 1, 0) OVER (PARTITION BY region ORDER BY sale_date) AS prev_sale_amount
FROM sales_data s;
SELECT
  s.*,
  LEAD(sale_amount, 1, -1) OVER (PARTITION BY product_category ORDER BY sale_date) AS next_sale_amount
FROM sales_data s;
SELECT * FROM (
  SELECT
    s.*,
    ROW_NUMBER() OVER (PARTITION BY product_category ORDER BY sale_amount DESC) AS rn
  FROM sales_data s
)
WHERE rn <= 2;
SELECT
  s.*,
  MAX(sale_amount) OVER (PARTITION BY product_category) AS highest_sale_in_category
FROM sales_data s;
SELECT
  s.*,
  RATIO_TO_REPORT(sale_amount) OVER (PARTITION BY region) AS sale_percentage_of_region
FROM sales_data s;
SELECT
  s.*,
  NTILE(3) OVER (PARTITION BY region ORDER BY sale_amount DESC) AS sales_tier
FROM sales_data s;
SELECT
  s.*,
  AVG(sale_amount) OVER (PARTITION BY region ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg_3_sales
FROM sales_data s;

总结 

到此这篇关于Oracle窗口函数详解及练习题的文章就介绍到这了,更多相关Oracle窗口函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文