位置: 文档库 > 数据库 > Oracle 语法之 OVER (PARTITION BY ..) 及开窗函数

Oracle 语法之 OVER (PARTITION BY ..) 及开窗函数

草莓味的风 上传于 2021-06-25 20:11

### Oracle 语法之 OVER (PARTITION BY ..) 及开窗函数

在数据库领域,尤其是 Oracle 数据库中,开窗函数(Window Functions)是一项强大且实用的功能。它允许我们在不减少行数的前提下,对数据进行聚合计算、排名等操作,为数据分析、报表生成等场景提供了极大的便利。而 OVER 子句与 PARTITION BY 子句的组合,则是开窗函数实现分区计算的核心语法结构。

#### 一、开窗函数概述

开窗函数,也被称为分析函数,它不同于传统的聚合函数(如 SUM、AVG、COUNT 等)。传统的聚合函数会将多行数据合并为一行,计算出一个聚合值,从而导致原始数据的行数减少。而开窗函数则是在保留原始数据行的基础上,对每一行数据进行额外的计算,生成一个新的列值。

开窗函数的基本语法结构如下:

函数名([参数]) OVER (
    [PARTITION BY 分区列1, 分区列2, ...]
    [ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...]
    [frame子句]
)

其中,函数名是具体的开窗函数,如 SUM、AVG、RANK 等;OVER 关键字标识这是一个开窗函数;PARTITION BY 子句用于将数据划分为多个分区,在每个分区内独立进行计算;ORDER BY 子句用于指定分区内数据的排序方式;frame 子句则用于定义开窗函数的计算范围,即当前行相对于分区内其他行的位置关系。

#### 二、PARTITION BY 子句详解

PARTITION BY 子句是开窗函数实现分区计算的关键部分。它将数据集按照指定的列值划分为多个互不重叠的分区,每个分区内的数据具有相同的分区列值。在每个分区内,开窗函数会独立进行计算,不会受到其他分区数据的影响。

例如,我们有一个员工工资表(EMPLOYEE_SALARY),包含员工姓名(EMP_NAME)、部门(DEPT)、工资(SALARY)等字段。我们想要计算每个部门内员工的平均工资,同时保留原始数据行。这时就可以使用 PARTITION BY 子句来实现。

SELECT 
    EMP_NAME,
    DEPT,
    SALARY,
    AVG(SALARY) OVER (PARTITION BY DEPT) AS DEPT_AVG_SALARY
FROM 
    EMPLOYEE_SALARY;

在上述 SQL 语句中,AVG(SALARY) OVER (PARTITION BY DEPT) 表示按照部门(DEPT)字段将数据划分为多个分区,在每个分区内计算工资(SALARY)的平均值。结果集中会保留原始的员工信息,同时新增一列 DEPT_AVG_SALARY,显示每个员工所在部门的平均工资。

PARTITION BY 子句可以指定一个或多个列作为分区依据。当指定多个列时,数据会按照这些列的组合值进行分区。例如:

SELECT 
    EMP_NAME,
    DEPT,
    JOB_TITLE,
    SALARY,
    AVG(SALARY) OVER (PARTITION BY DEPT, JOB_TITLE) AS DEPT_JOB_AVG_SALARY
FROM 
    EMPLOYEE_SALARY;

这条 SQL 语句按照部门(DEPT)和职位(JOB_TITLE)的组合值将数据划分为多个分区,在每个分区内计算工资的平均值。这样可以更细致地分析不同部门和职位组合下的工资水平。

#### 三、常见开窗函数及应用

除了前面提到的 AVG 函数外,Oracle 中还有许多其他常用的开窗函数,下面我们将详细介绍一些常见的开窗函数及其应用场景。

##### 1. SUM 函数

SUM 函数用于计算分区内指定列的总和。例如,我们想要计算每个部门内员工的工资总和:

SELECT 
    EMP_NAME,
    DEPT,
    SALARY,
    SUM(SALARY) OVER (PARTITION BY DEPT) AS DEPT_TOTAL_SALARY
FROM 
    EMPLOYEE_SALARY;

结果集中会显示每个员工的姓名、所在部门、工资以及该部门的工资总和。

##### 2. COUNT 函数

COUNT 函数用于计算分区内的行数。例如,统计每个部门的员工人数:

SELECT 
    DEPT,
    COUNT(*) OVER (PARTITION BY DEPT) AS DEPT_EMPLOYEE_COUNT
FROM 
    EMPLOYEE_SALARY;

这条 SQL 语句会按照部门分组,计算每个部门的员工数量。

##### 3. RANK 函数

RANK 函数用于为分区内的数据分配排名。排名相同的行会具有相同的排名值,并且会跳过后续的排名值。例如,按照工资从高到低对每个部门的员工进行排名:

SELECT 
    EMP_NAME,
    DEPT,
    SALARY,
    RANK() OVER (PARTITION BY DEPT ORDER BY SALARY DESC) AS DEPT_SALARY_RANK
FROM 
    EMPLOYEE_SALARY;

结果集中,每个部门内工资最高的员工排名为 1,如果有多个员工工资相同,则它们的排名相同,下一个不同工资的员工排名会跳过相应的名次。

##### 4. DENSE_RANK 函数

DENSE_RANK 函数与 RANK 函数类似,也是为分区内的数据分配排名。但不同的是,DENSE_RANK 函数不会跳过后续的排名值。例如:

SELECT 
    EMP_NAME,
    DEPT,
    SALARY,
    DENSE_RANK() OVER (PARTITION BY DEPT ORDER BY SALARY DESC) AS DEPT_DENSE_SALARY_RANK
FROM 
    EMPLOYEE_SALARY;

在这种情况下,如果有多个员工工资相同,它们的排名相同,下一个不同工资的员工排名会紧接着上一个排名值,不会跳过。

##### 5. ROW_NUMBER 函数

ROW_NUMBER 函数为分区内的每一行数据分配一个唯一的序号,从 1 开始依次递增。例如:

SELECT 
    EMP_NAME,
    DEPT,
    SALARY,
    ROW_NUMBER() OVER (PARTITION BY DEPT ORDER BY SALARY DESC) AS DEPT_ROW_NUMBER
FROM 
    EMPLOYEE_SALARY;

这条 SQL 语句会按照部门分组,并在每个部门内按照工资从高到低为员工分配唯一的行号。

#### 四、ORDER BY 子句在开窗函数中的作用

ORDER BY 子句在开窗函数中用于指定分区内数据的排序方式。它决定了开窗函数在计算时所依据的数据顺序,对于排名函数(如 RANK、DENSE_RANK、ROW_NUMBER)以及一些基于顺序计算的函数(如 FIRST_VALUE、LAST_VALUE 等)来说,ORDER BY 子句至关重要。

例如,我们想要找出每个部门内工资最高的员工信息,可以使用 FIRST_VALUE 函数结合 ORDER BY 子句来实现:

SELECT 
    EMP_NAME,
    DEPT,
    SALARY,
    FIRST_VALUE(EMP_NAME) OVER (PARTITION BY DEPT ORDER BY SALARY DESC) AS DEPT_HIGHEST_SALARY_EMP
FROM 
    EMPLOYEE_SALARY;

在上述 SQL 语句中,FIRST_VALUE 函数会返回每个部门内按照工资从高到低排序后的第一个员工的姓名,即工资最高的员工姓名。

同样,LAST_VALUE 函数可以返回分区内按照指定顺序排序后的最后一个值。例如,找出每个部门内工资最低的员工信息:

SELECT 
    EMP_NAME,
    DEPT,
    SALARY,
    LAST_VALUE(EMP_NAME) OVER (PARTITION BY DEPT ORDER BY SALARY ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS DEPT_LOWEST_SALARY_EMP
FROM 
    EMPLOYEE_SALARY;

这里需要注意,对于 LAST_VALUE 函数,默认情况下它的计算范围是当前行到当前行(即只返回当前行的值),因此需要使用 frame 子句(ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)来指定计算范围为整个分区,这样才能正确返回分区内排序后的最后一个值。

#### 五、frame 子句详解

frame 子句用于定义开窗函数的计算范围,即当前行相对于分区内其他行的位置关系。它可以帮助我们更灵活地控制开窗函数的计算方式,实现一些复杂的计算需求。

frame 子句的常见语法形式有以下几种:

##### 1. ROWS 形式

ROWS 形式用于指定基于行数的计算范围。例如:

  • ROWS BETWEEN N PRECEDING AND CURRENT ROW:表示从当前行向前数 N 行到当前行。

  • ROWS BETWEEN CURRENT ROW AND N FOLLOWING:表示从当前行到当前行向后数 N 行。

  • ROWS BETWEEN N PRECEDING AND M FOLLOWING:表示从当前行向前数 N 行到当前行向后数 M 行。

  • ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:表示从分区第一行到当前行。

  • ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING:表示从当前行到分区最后一行。

  • ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING:表示整个分区。

例如,计算每个员工工资与同部门内前两名员工工资的平均值:

SELECT 
    EMP_NAME,
    DEPT,
    SALARY,
    AVG(SALARY) OVER (PARTITION BY DEPT ORDER BY SALARY DESC ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS AVG_TOP_3_SALARY
FROM 
    EMPLOYEE_SALARY;

##### 2. RANGE 形式

RANGE 形式用于指定基于值的计算范围。例如:

  • RANGE BETWEEN N PRECEDING AND CURRENT ROW:表示从当前行向前数值差不超过 N 的行到当前行。

  • RANGE BETWEEN CURRENT ROW AND N FOLLOWING:表示从当前行到当前行向后数值差不超过 N 的行。

RANGE 形式通常用于数值类型的排序列,根据排序列的值差来确定计算范围。

#### 六、开窗函数的性能优化

在使用开窗函数时,性能优化是一个重要的考虑因素。以下是一些优化开窗函数性能的方法:

##### 1. 合理使用分区列

选择合适的分区列可以减少每个分区内的数据量,从而提高开窗函数的计算效率。分区列应该具有较好的区分度,能够将数据均匀地分布到各个分区中。

##### 2. 优化排序操作

ORDER BY 子句会导致排序操作,如果分区内数据量较大,排序操作可能会成为性能瓶颈。可以考虑在分区列上建立索引,以加快排序速度。

##### 3. 避免不必要的开窗函数计算

只在使用开窗函数能够带来实际价值的情况下使用它,避免在不需要分区计算或排名等操作的场景下滥用开窗函数。

##### 4. 合理使用 frame 子句

frame 子句的定义会影响开窗函数的计算范围,过于宽泛的 frame 子句可能会导致不必要的计算,增加性能开销。应根据实际需求精确定义 frame 子句。

#### 七、实际应用案例

下面我们通过一个实际的应用案例来进一步理解开窗函数的使用。假设我们有一个销售数据表(SALES_DATA),包含销售日期(SALE_DATE)、产品(PRODUCT)、销售数量(QUANTITY)、销售金额(AMOUNT)等字段。我们想要分析每个产品在每个月的销售情况,包括该产品当月的销售总量、销售平均金额、销售排名等信息。

SELECT 
    TO_CHAR(SALE_DATE, 'YYYY-MM') AS SALE_MONTH,
    PRODUCT,
    QUANTITY,
    AMOUNT,
    SUM(QUANTITY) OVER (PARTITION BY TO_CHAR(SALE_DATE, 'YYYY-MM'), PRODUCT) AS MONTHLY_PRODUCT_TOTAL_QUANTITY,
    AVG(AMOUNT) OVER (PARTITION BY TO_CHAR(SALE_DATE, 'YYYY-MM'), PRODUCT) AS MONTHLY_PRODUCT_AVG_AMOUNT,
    RANK() OVER (PARTITION BY TO_CHAR(SALE_DATE, 'YYYY-MM') ORDER BY SUM(AMOUNT) OVER (PARTITION BY TO_CHAR(SALE_DATE, 'YYYY-MM'), PRODUCT) DESC) AS MONTHLY_PRODUCT_SALES_RANK
FROM 
    SALES_DATA;

在上述 SQL 语句中,我们首先使用 TO_CHAR 函数将销售日期转换为年月格式,作为分区的一部分。然后分别使用 SUM 函数计算每个产品在每个月的销售总量,AVG 函数计算每个产品在每个月的销售平均金额,RANK 函数按照每个产品的月销售金额进行排名。

通过这个案例,我们可以看到开窗函数在实际数据分析中的强大作用,它能够帮助我们快速、准确地获取所需的信息,为决策提供有力支持。

#### 八、总结

OVER (PARTITION BY ..) 语法结构与开窗函数的结合是 Oracle 数据库中一项非常实用的功能。它通过分区计算的方式,在不减少数据行数的前提下,为我们提供了丰富的数据分析手段。无论是进行聚合计算、排名操作,还是实现一些复杂的业务逻辑,开窗函数都能够发挥重要作用。

在实际应用中,我们需要根据具体的业务需求,合理选择分区列、排序方式以及开窗函数类型,同时注意性能优化,以确保开窗函数能够高效地运行。通过熟练掌握开窗函数的使用,我们可以大大提高数据库查询和数据分析的效率,为企业的发展提供有力的数据支持。

关键词:Oracle 数据库、开窗函数OVER 子句、PARTITION BY 子句、聚合函数、排名函数、frame 子句、性能优化

简介:本文详细介绍了 Oracle 数据库中 OVER (PARTITION BY ..) 语法结构及开窗函数的相关知识。首先阐述了开窗函数的概念和基本语法,接着深入讲解了 PARTITION BY 子句的作用和使用方法,然后介绍了常见的开窗函数及其应用场景,包括 SUM、COUNT、RANK 等函数。还讨论了 ORDER BY 子句和 frame 子句在开窗函数中的重要性,以及如何进行性能优化。最后通过一个实际应用案例展示了开窗函数在实际数据分析中的强大作用。