累计添加上个月或上年的缺失数据

时间:2016-04-19 09:47:09

标签: sql oracle plsql

说我有以下数据:

select 1 id, 'A' name, '2007' year, '04' month,  5 sales  from dual union all
select 2 id, 'A' name, '2007' year, '05' month,  2 sales  from dual union all
select 3 id, 'B' name, '2008' year, '12' month,  3 sales  from dual union all
select 4 id, 'B' name, '2009' year, '12' month, 56 sales  from dual union all
select 5 id, 'C' name, '2009' year, '08' month, 89 sales  from dual union all
select 13 id,'B' name, '2016' year, '01' month, 10 sales  from dual union all
select 14 id,'A' name, '2016' year, '02' month,  8 sales  from dual union all
select 15 id,'D' name, '2016' year, '03' month, 12 sales  from dual union all
select 16 id,'E' name, '2016' year, '04' month, 34 sales  from dual

我想累计累计所有年份及其各自期间(月份)的所有销售额。输出应如下所示:

name    year    month   sale   opening bal   closing bal
 A      2007     04      5        0              5
 A      2007     05      2        5              7
 B      2008     12      3        12             15
 A      2008     04      0        5              5    -- to be generated
 A      2008     05      0        7              7    -- to be generated
 B      2009     12      56       15             71
 C      2009     08      89       71             160
 A      2009     04      0        5              5    -- to be generated
 A      2009     05      0        7              7    -- to be generated
 B      2016     01      10       278            288
 B      2016     12      0         71             71  -- to be generated
 A      2016     02      8        288            296
 A      2016     04      0         5              5   -- to be generated
 A      2016     05      0         7              7   -- to be generated
 D      2016     03      12       296            308
 E      2016     04      34       308            342
 C      2016     08      0        160            160  -- to be generated

期初余额是上个月的期末余额,如果进入下一年,则明年的期初余额是上一年的期末余额。它应该能够在接下来的几年里像这样工作。我有这部分工作。但是,我不知道如何绕过2008年存在的2009年缺席。例如,密钥A,2008,04以及A,2008,05在2009年不存在且代码应该能够添加它像2009年一样。同样适用于其他年份和月份。

我正在研究Oracle 12c。

提前致谢。

2 个答案:

答案 0 :(得分:1)

这是我能得到的最接近你的结果,虽然我发现它并不完全匹配。例如,您的期初余额看起来不正确(12的期初余额来自id = 3的输出行?)。无论如何,希望以下内容能够让您根据需要进行修改:

with sample_data as (select 1 id, 'A' name, '2007' year, '04' month,  5 sales  from dual union all
                     select 2 id, 'A' name, '2007' year, '05' month,  2 sales  from dual union all
                     select 3 id, 'B' name, '2008' year, '12' month,  3 sales  from dual union all
                     select 4 id, 'B' name, '2009' year, '12' month, 56 sales  from dual union all
                     select 5 id, 'C' name, '2009' year, '08' month, 89 sales  from dual union all
                     select 13 id, 'B' name, '2016' year, '01' month, 10 sales  from dual union all
                     select 14 id, 'A' name, '2016' year, '02' month,  8 sales  from dual union all
                     select 15 id, 'D' name, '2016' year, '03' month, 12 sales  from dual union all
                     select 16 id, 'E' name, '2016' year, '04' month, 34 sales  from dual),
             dts as (select distinct year
                     from   sample_data),
             res as (select sd.name,
                            dts.year,
                            sd.month,
                            nvl(sd.sales, 0) sales,
                            min(sd.year) over (partition by sd.name, sd.month) min_year_per_name_month,
                            sum(nvl(sd.sales, 0)) over (partition by name order by to_date(dts.year||'-'||sd.month, 'yyyy-mm')) - nvl(sd.sales, 0) as opening,
                            sum(nvl(sd.sales, 0)) over (partition by name order by to_date(dts.year||'-'||sd.month, 'yyyy-mm')) as closing
                     from   dts
                            left outer join sample_data sd partition by (sd.name, sd.month) on (sd.year = dts.year))
select name,
       year,
       month,
       sales,
       opening,
       closing
from   res
where  (opening != 0 or closing != 0)
and    year >= min_year_per_name_month
order by to_date(year||'-'||month, 'yyyy-mm'),
         name;

NAME YEAR MONTH      SALES    OPENING    CLOSING
---- ---- ----- ---------- ---------- ----------
A    2007 04             5          0          5
A    2007 05             2          5          7
A    2008 04             0          7          7
A    2008 05             0          7          7
B    2008 12             3          0          3
A    2009 04             0          7          7
A    2009 05             0          7          7
C    2009 08            89          0         89
B    2009 12            56          3         59
B    2016 01            10         59         69
A    2016 02             8          7         15
D    2016 03            12          0         12
A    2016 04             0         15         15
E    2016 04            34          0         34
A    2016 05             0         15         15
C    2016 08             0         89         89
B    2016 12             0         69         69

我已使用Partition Outer Join链接表中的任何月份和名称组合(在我的查询中,sample_data子查询 - 您不需要该子查询,您只需使用您的表相反!)到同一张表中的任何一年,然后计算期初/期末余额。然后我丢弃任何开头和期末余额为0的行。

答案 1 :(得分:1)

@boneists方法的变体,从CTE中的样本数据开始:

with t as (
  select 1 id, 'A' name, '2007' year, '04' month,  5 sales  from dual union all
  select 2 id, 'A' name, '2007' year, '05' month,  2 sales  from dual union all
  select 3 id, 'B' name, '2008' year, '12' month,  3 sales  from dual union all
  select 4 id, 'B' name, '2009' year, '12' month, 56 sales  from dual union all
  select 5 id, 'C' name, '2009' year, '08' month, 89 sales  from dual union all
  select 13 id,'B' name, '2016' year, '01' month, 10 sales  from dual union all
  select 14 id,'A' name, '2016' year, '02' month,  8 sales  from dual union all
  select 15 id,'D' name, '2016' year, '03' month, 12 sales  from dual union all
  select 16 id,'E' name, '2016' year, '04' month, 34 sales  from dual
),
y (year, rnk) as (
  select year, dense_rank() over (order by year)
  from (select distinct year from t)
),
r (name, year, month, sales, rnk) as (
  select t.name, t.year, t.month, t.sales, y.rnk
  from t
  join y on y.year = t.year
  union all
  select r.name, y.year, r.month, 0, y.rnk
  from y
  join r on r.rnk = y.rnk - 1
  where not exists (
    select 1 from t where t.year = y.year and t.month = r.month and t.name = r.name
  )
)
select name, year, month, sales,
  nvl(sum(sales) over (partition by name order by year, month
    rows between unbounded preceding and 1 preceding), 0) as opening_bal,
  nvl(sum(sales) over (partition by name order by year, month
    rows between unbounded preceding and current row), 0) as closing_bal
from r
order by year, month, name;

虽然它也与问题中的预期结果不匹配,但结果也相同:

NAME YEAR MONTH      SALES OPENING_BAL CLOSING_BAL
---- ---- ----- ---------- ----------- -----------
A    2007 04             5           0           5
A    2007 05             2           5           7
A    2008 04             0           7           7
A    2008 05             0           7           7
B    2008 12             3           0           3
A    2009 04             0           7           7
A    2009 05             0           7           7
C    2009 08            89           0          89
B    2009 12            56           3          59
B    2016 01            10          59          69
A    2016 02             8           7          15
D    2016 03            12           0          12
A    2016 04             0          15          15
E    2016 04            34           0          34
A    2016 05             0          15          15
C    2016 08             0          89          89
B    2016 12             0          69          69

y CTE(随意使用更有意义的名字!)从您的原始数据中生成所有不同的年份,并且还添加了排名,因此2007年是1,2008年是2,2009是3,并且2016年是4。

r递归CTE根据前几年的名称/月份数据,将您的实际数据与零销售的虚拟行组合在一起。

从递归CTE产生的内容中,您可以使用分析累积总和来添加期初/期末余额。这是使用窗口子句来决定要包含哪些销售值 - 基本上是期初和期末余额是到目前为止所有值的总和,但是开盘并不包括当前行。