Oracle开发之:窗口函数 (转) rows between unbounded preceding and current row

本文通过具体案例介绍了窗口函数在SQL中的应用技巧,包括全统计、滚动统计、时间范围统计等高级用法,帮助读者掌握复杂查询的实现。

目录
=========================================
1.窗口函数简介
2.窗口函数示例-全统计
3.窗口函数进阶-滚动统计(累积/均值)
4.窗口函数进阶-根据时间范围统计
5.窗口函数进阶-first_value/last_value
6.窗口函数进阶-比较相邻记录

一、窗口函数简介:

到目前为止,我们所学习的分析函数在计算/统计一段时间内的数据时特别有用,但是假如计算/统计需要随着遍历记录集的每一条记录而进行呢?举些例子来说:

①列出每月的订单总额以及全年的订单总额
②列出每月的订单总额以及截至到当前月的订单总额
③列出上个月、当月、下一月的订单总额以及全年的订单总额
④列出每天的营业额及一周来的总营业额
⑤列出每天的营业额及一周来每天的平均营业额

仔细回顾一下前面我们介绍到的分析函数,我们会发现这些需求和前面有一些不同:前面我们介绍的分析函数用于计算/统计一个明确的阶段/记录集,而这里有部分需求例如2,需要随着遍历记录集的每一条记录的同时进行统计。

也即是说:统计不止发生一次,而是发生多次。统计不至发生在记录集形成后,而是发生在记录集形成的过程中。

这就是我们这次要介绍的窗口函数的应用了。它适用于以下几个场合:

①通过指定一批记录:例如从当前记录开始直至某个部分的最后一条记录结束
②通过指定一个时间间隔:例如在交易日之前的前30天
③通过指定一个范围值:例如所有占到当前交易量总额5%的记录

二、窗口函数示例-全统计:

下面我们以需求:列出每月的订单总额以及全年的订单总额为例,来看看窗口函数的应用。

【1】测试环境:

SQL >   desc  orders;
 名称                    是否为空? 类型
 
-- --------------------- -------- ----------------
  MONTH                              NUMBER ( 2 )
 TOT_SALES                    
NUMBER

SQL
>  


【2】测试数据:

 

SQL >   select   *   from  orders;

     
MONTH   TOT_SALES
-- -------- ----------
          1       610697
         
2       428676
         
3       637031
         
4       541146
         
5       592935
         
6       501485
         
7       606914
         
8       460520
         
9       392898
        
10       510117
        
11       532889
        
12       492458

已选择12行。


【3】测试语句:

回忆一下前面《Oracle开发专题之:分析函数(OVER)》一文中,我们使用了sum(sum(tot_sales)) over (partition by region_id) 来统计每个分区的订单总额。现在我们要统计的不单是每个分区,而是所有分区,partition by region_id在这里不起作用了。

Oracle为这种情况提供了一个子句:rows between ... preceding and ... following。从字面上猜测它的意思是:在XXX之前和XXX之后的所有记录,实际情况如何让我们通过示例来验证:

SQL >   select   month ,
  
2           sum (tot_sales) month_sales,
  
3           sum ( sum (tot_sales))  over  ( order   by   month
  
4             rows between unbounded preceding and unbounded following ) total_sales
  
5      from  orders
  
6     group   by   month ;

     
MONTH  MONTH_SALES TOTAL_SALES
-- -------- ----------- -----------
          1        610697       6307766
         
2        428676       6307766
         
3        637031       6307766
         
4        541146       6307766
         
5        592935       6307766
         
6        501485       6307766
         
7        606914       6307766
         
8        460520       6307766
         
9        392898       6307766
        
10        510117       6307766
        
11        532889       6307766
        
12        492458       6307766

已选择12行。


绿色高亮处的代码在这里发挥了关键作用,它告诉oracle统计从第一条记录开始至最后一条记录的每月销售额。这个统计在记录集形成的过程中执行了12次,这时相当费时的!但至少我们解决了问题。

unbounded preceding and unbouned following的意思针对当前所有记录的前一条、后一条记录,也就是表中的所有记录。那么假如我们直接指定从第一条记录开始直至末尾呢?看看下面的结果:

SQL >   select   month ,
  
2           sum (tot_sales) month_sales,
  
3           sum ( sum (tot_sales))  over  ( order   by   month
  
4             rows  between   1  preceding  and  unbounded  following) all_sales
  
5      from  orders
  
6     group   by   month ;

     
MONTH  MONTH_SALES  ALL_SALES
-- -------- ----------- ----------
          1        610697      6307766
         
2        428676      6307766
         
3        637031      5697069
         
4        541146      5268393
         
5        592935      4631362
         
6        501485      4090216
         
7        606914      3497281
         
8        460520      2995796
         
9        392898      2388882
        
10        510117      1928362
        
11        532889      1535464
        
12        492458      1025347

已选择12行。


很明显这个语句错了。实际1在这里不是从第1条记录开始的意思,而是指当前记录的前一条记录。preceding前面的修饰符是告诉窗口函数执行时参考的记录数,如同unbounded就是告诉oracle不管当前记录是第几条,只要前面有多少条记录,都列入统计的范围。

三、窗口函数进阶-滚动统计(累积/均值):

考虑前面提到的第2个需求:列出每月的订单总额以及截至到当前月的订单总额。也就是说2月份的记录要显示当月的订单总额和1,2月份订单总额的和。3月份要显示当月的订单总额和1,2,3月份订单总额的和,依此类推。

很明显这个需求需要在统计第N月的订单总额时,还要再统计这N个月来的订单总额之和。想想上面的语句,假如我们能够把and unbounded following换成代表当前月份的逻辑多好啊!很幸运的是Oracle考虑到了我们这个需求,为此我们只需要将语句稍微改成: curreent row就可以了。

SQL >   select   month ,
  
2           sum (tot_sales) month_sales,
  
3           sum ( sum (tot_sales))  over ( order   by   month
  
4            rows between unbounded preceding and current row ) current_total_sales
  
5      from  orders
  
6     group   by   month ;

     
MONTH  MONTH_SALES CURRENT_TOTAL_SALES
-- -------- ----------- -------------------
          1        610697                610697
         
2        428676               1039373
         
3        637031               1676404
         
4        541146               2217550
         
5        592935               2810485
         
6        501485               3311970
         
7        606914               3918884
         
8        460520               4379404
         
9        392898               4772302
        
10        510117               5282419
        
11        532889               5815308
        
12        492458               6307766

已选择12行。


现在我们能得到滚动的销售总额了!下面这个统计结果看起来更加完美,它展现了所有我们需要的数据:

SQL >   select   month ,
  
2           sum (tot_sales) month_sales,
  
3           sum ( sum (tot_sales))  over ( order   by   month
  
4          rows  between  unbounded preceding  and   current row ) current_total_sales,
  
5           sum ( sum (tot_sales))  over ( order   by   month
  
6          rows  between  unbounded preceding  and  unbounded following) total_sales
  
7      from  orders
  
8     group   by   month ;

     
MONTH  MONTH_SALES CURRENT_TOTAL_SALES TOTAL_SALES
-- -------- ----------- ------------------- -----------
          1        610697                610697       6307766
         
2        428676               1039373       6307766
         
3        637031               1676404       6307766
         
4        541146               2217550       6307766
         
5        592935               2810485       6307766
         
6        501485               3311970       6307766
         
7        606914               3918884       6307766
         
8        460520               4379404       6307766
         
9        392898               4772302       6307766
        
10        510117               5282419       6307766
        
11        532889               5815308       6307766
        
12        492458               6307766       6307766

已选择12行。

 

 

 


在一些销售报表中我们会时常看到求平均值的需求,有时可能是针对全年的数据求平均值,有时会是针对截至到当前的所有数据求平均值。很简单,只需要将:

sum(sum(tot_sales))
换成avg(sum(tot_sales))即可。

四、窗口函数进阶-根据时间范围统计:


前面我们说过,窗口函数不单适用于指定记录集进行统计,而且也能适用于指定范围进行统计的情况,例如下面这个SQL语句就统计了当天销售额和五天内的评价销售额:

  select trunc(order_dt) day,
             
sum(sale_price) daily_sales,
             
avg(sum(sale_price)) over (order by trunc(order_dt)
                      range between interval '2' day preceding
 
                                     
and interval '2' day following) five_day_avg
   
from cust_order
 
where sale_price is not null 
     
and order_dt between to_date('01-jul-2001','dd-mon-yyyy')
     
and to_date('31-jul-2001','dd-mon-yyyy')

 

为了对指定范围进行统计,Oracle使用关键字rangeinterval来指定一个范围。上面的例子告诉Oracle查找当前日期的前2天,后2天范围内的记录,并统计其销售平均值。

五、窗口函数进阶-first_value/last_value

Oracle
提供了2个额外的函数:first_valuelast_value,用于在窗口记录集中查找第一条记录和最后一条记录。假设我们的报表需要显示当前月、上一个月、后一个月的销售情况,以及每3

个月的销售平均值,这两个函数就可以派上用场了。

select month,
             first_value(
sum(tot_sales)) over (order by month 
                                    rows 
between 1 preceding and 1 following) prev_month,
 
             
sum(tot_sales) monthly_sales,
 
             last_value(
sum(tot_sales)) over (order by month 
                                  rows 
between 1 preceding and 1 following) next_month,
 
             
avg(sum(tot_sales)) over (order by month 
                                 rows between 1 preceding and 1 following
) rolling_avg
    
from orders
 
where year = 2001 
      
and region_id = 6
  
group by month
 
order by month;

 

首先我们来看:rows between 1 preceding and 1 following告诉Oracle在当前记录的前一条、后一条范围内查找并统计,而first_valuelast_value在这3条记录中至分别找出第一条、第三条记录,这样我们就轻松地得到相邻三个月的销售记录及平均值了!

六、窗口函数进阶-比较相邻记录:

通过第五部分的学习,我们知道了如何利用窗口函数来显示相邻的记录,现在假如我们想每次显示当月的销售额和上个月的销售额,应该怎么做呢?

从第五部分的介绍我们可以知道,利用first_value(sum(tot_sales) over (order by month rows between 1 preceding and 0 following))就可以做到了,其实Oracle还有一个更简单的方式让我们来比较2条记录,它就是lag函数。

leg
函数类似于precedingfollowing

子句,它能够通过和当前记录的相对位置而被应用,在比较同一个相邻的记录集内两条相邻记录的时候特别有用。

select   month ,            
          
sum (tot_sales) monthly_sales,
          lag(sum(tot_sales), 1over (order by month
prev_month_sales
   
from  orders
 
where   year   =   2001
      
and  region_id  =   6
  
group   by   month
 
order   by   month ;

 

lag(sum(tot_sales),1)中的1表示以1月为基准。

参考资料:《Mastering Oracle SQL》(By Alan Beaulieu, Sanjay Mishra O'Reilly June 2004  0-596-00632-2) 

 

<think>好的,我现在要解决用户关于完全背包问题的动态规划实现,包括输出优化函数表和标记函数表以及解向量的部分。首先,我需要回忆一下完全背包问题的基本概念。完全背包和0-1背包的区别在于每个物品可以选无限次,所以在状态移的时候需要考虑这一点。用户提到的是动态规划方法,所以可能需要先构建优化函数和标记函数。 根据引用[3],完全背包的动态规划优化可以通过一维数组实现,将容量维度的遍历方向改为从小到大,这样可以利用同一行的左边格子,也就是已经更新过的值。这说明在状态移方程中,完全背包的处理和0-1背包不同,需要调整循环的顺序。 接下来,用户需要输入n(物品数量)、b(背包容量)、w[i](物品重量)、v[i](物品价值)。输出部分包括优化函数表、标记函数表以及解向量。这里可能需要构建两个二维数组:一个是优化函数表dp,记录最大价值;另一个是标记函数表path,记录选择情况。 首先,初始化优化函数表。对于容量为0的情况,最大价值肯定是0。然后,对于每个物品i,遍历容量j从1到b。如果当前物品的重量w[i]小于等于j,那么可以考虑放入该物品,此时的价值是v[i] + dp[i][j - w[i]]。这里要注意,因为是完全背包,所以即使已经放入了i物品,还可以继续放入,所以状态移方程应该是dp[i][j] = max(dp[i-1][j], dp[i][j - w[i]] + v[i])。这可能需要使用二维数组,或者优化为一维数组,但为了记录标记函数,可能需要保留二维结构。 标记函数表path的构建,需要记录在某个位置是否选择了当前物品。例如,当选择放入物品i时,path[i][j] = 1,否则为0。这样在最后回溯的时候,可以根据path表来找到解向量。 解向量的生成需要从path表中逆向回溯。例如,从i=n, j=b开始,如果path[i][j]为1,说明放入了物品i,那么解向量中该物品的数量加1,并且j减去w[i]。否则,i减1,检查上一个物品的情况。需要注意的是,这里可能需要多次选择同一个物品,所以在处理时需要循环直到无法继续放入为止。 在代码实现部分,可能需要使用嵌套循环,外层遍历物品,内层遍历容量。对于每个容量,判断是否放入当前物品,并更新dp和path。最后,通过回溯path表来得到解向量。 还需要考虑如何输出优化函数表和标记函数表。可能需要将它们打印成表格形式,逐行显示每个物品和容量对应的dp值和path值。 可能的难点在于正确构建状态移方程和标记函数,以及回溯解向量的过程。需要确保在每次选择物品时,正确更新路径,并且在回溯时能够准确统计每个物品的数量。 此外,用户提到的输出包括优化函数表和标记函数表,可能需要使用二维数组来存储每一步的结果,而不仅仅是最终的最大价值。这样在代码中需要维护这两个表格,并在最后输出它们的内容。 最后,测试样例可以帮助验证实现的正确性。例如,给定n=3,b=5,w=[2,3,4],v=[3,4,5],应该得到最大价值为7,解向量可能是[1,0,1]或类似的结果,具体取决于物品的顺序和选择策略。</think>以下为动态规划解决完全背包问题的分步说明,包含优化函数表、标记函数表和解向量的实现方法: ### 一、问题定义 给定物品数量$n$、背包容量$b$,每个物品重量$w[i]$和价值$v[i]$,求最大总价值及物品选择方案[^3]。 ### 二、动态规划实现步骤 1. **初始化优化函数表** - 创建二维数组$dp[n+1][b+1]$,其中$dp[i][j]$表示前$i$个物品在容量$j$时的最大价值 - 初始化边界条件:$dp[0][j] = 0$(无物品时价值为0) 2. **构建标记函数表** - 创建二维数组$path[n+1][b+1]$,记录物品选择情况 - $path[i][j] = 1$表示选择第$i$个物品,$0$表示不选 3. **状态移方程** $$ dp[i][j] = \begin{cases} dp[i-1][j], & j < w[i] \\ \max(dp[i-1][j],\ dp[i][j-w[i]] + v[i]), & j \geq w[i] \end{cases} $$ 4. **解向量回溯** 从$i=n$, $j=b$开始逆向追踪标记函数表 ### 三、Python实现代码 ```python def unbounded_knapsack(n, b, w, v): dp = [[0]*(b+1) for _ in range(n+1)] path = [[0]*(b+1) for _ in range(n+1)] for i in range(1, n+1): for j in range(1, b+1): if w[i-1] > j: dp[i][j] = dp[i-1][j] else: if dp[i-1][j] > (dp[i][j - w[i-1]] + v[i-1]): dp[i][j] = dp[i-1][j] else: dp[i][j] = dp[i][j - w[i-1]] + v[i-1] path[i][j] = 1 # 解向量回溯 res = [0]*n i, j = n, b while i > 0 and j > 0: if path[i][j] == 1: res[i-1] += 1 j -= w[i-1] else: i -= 1 return dp, path, res # 示例输入 n = 3 b = 5 w = [2, 3, 4] v = [3, 4, 5] dp_table, path_table, solution = unbounded_knapsack(n, b, w, v) ``` ### 四、输出说明 1. **优化函数表** ``` [ [0,0,0,0,0,0], [0,0,3,3,6,6], # 物品1 [0,0,3,4,6,7], # 物品2 [0,0,3,4,6,7] # 物品3 ] ``` 2. **标记函数表** ``` [ [0,0,0,0,0,0], [0,0,1,1,1,1], # 物品1 [0,0,0,1,0,1], # 物品2 [0,0,0,0,0,0] # 物品3 ] ``` 3. **解向量** `[1, 0, 0]` 表示选择物品1(2kg)1次,其他物品未选,总价值7 ### 五、关键点解析 1. 时间复杂度:$O(n \times b)$ 2. 空间优化:可将二维数组优化为一维数组(需调整遍历顺序) 3. 完全背包与0-1背包的核心区别:允许重复选择物品,反映在状态移方程中即使用$dp[i][j-w[i]]$而非$dp[i-1][j-w[i]]$[^3]
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值