it编程 > 数据库 > Oracle

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

25人参与 2025-07-04 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窗口函数内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

您想发表意见!!点此发布评论

推荐阅读

Oracle中ORA-01489:字符串连接的结果过长问题三种解决办法

07-04

Oracle高级语法篇之merge into语句复杂案例

07-04

Oracle数据库查询之单表查询的关键子句及其用法

06-30

Oracle找出一个表的间接授权信息的方法

06-27

Oracle大表添加索引的实现方式

07-14

Oracle日期时间查询方式

07-21

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论