【问题标题】:Oracle SQL Hierarchy SummationOracle SQL 层次结构求和
【发布时间】:2020-08-18 08:46:54
【问题描述】:

我有一个包含以下记录的表 TRANS:

TRANS_ID TRANS_DT     QTY    
1        01-Aug-2020  5
1        01-Aug-2020  1
1        03-Aug-2020  2
2        02-Aug-2020  1

预期输出:

TRANS_ID TRANS_DT     BEGBAL TOTAL END_BAL 
1        01-Aug-2020  0      6     6
1        02-Aug-2020  6      0     6      
1        03-Aug-2020  6      2     8
2        01-Aug-2020  0      0     0
2        02-Aug-2020  0      1     1      
2        03-Aug-2020  1      0     1

每个 trans_id 的初始余额为 0(2020 年 8 月 1 日)。对于随后的日子,期初余额是前一天的期末余额,依此类推。 我可以创建 PL/SQL 块来创建输出。是否有可能在 1 个 SQL 语句中获得输出?

谢谢。

【问题讨论】:

    标签: sql oracle sum window-functions recursive-query


    【解决方案1】:

    使用 CTE 尝试以下脚本-

    Demo Here

    WITH CTE 
    AS
    (
        SELECT DISTINCT A.TRANS_ID,B.TRANS_DT
        FROM your_table A
        CROSS JOIN (SELECT DISTINCT TRANS_DT FROM your_table) B
    
    ),
    CTE2
    AS
    (
        SELECT C.TRANS_ID,C.TRANS_DT,SUM(D.QTY) QTY
        FROM CTE C
        LEFT JOIN your_table D 
            ON C.TRANS_ID = D.TRANS_ID 
            AND C.TRANS_DT = D.TRANS_DT
        GROUP BY C.TRANS_ID,C.TRANS_DT
        ORDER BY C.TRANS_ID,C.TRANS_DT
    )
    
    SELECT F.TRANS_ID,F.TRANS_DT,
    (    
        SELECT COALESCE (SUM(QTY), 0) FROM CTE2 E 
        WHERE E.TRANS_ID = F.TRANS_ID AND E.TRANS_DT < F.TRANS_DT
    ) BEGBAL,
    (    
        SELECT COALESCE (SUM(QTY), 0) FROM CTE2 E 
        WHERE E.TRANS_ID = F.TRANS_ID AND E.TRANS_DT = F.TRANS_DT
    ) TOTAL ,
    (    
        SELECT COALESCE (SUM(QTY), 0) FROM CTE2 E 
        WHERE E.TRANS_ID = F.TRANS_ID AND E.TRANS_DT <= F.TRANS_DT
    ) END_BAL 
    FROM CTE2 F
    

    【讨论】:

      【解决方案2】:

      你也可以这样做(我认为它会快一点):Demo

      with 
      dt_between as (
        select mindt + level - 1 as trans_dt
        from (select min(trans_dt) as mindt, max(trans_dt) as maxdt from t)
        connect by level <= maxdt - mindt + 1
      ),
      dt_for_trans_id as (
        select *
        from dt_between, (select distinct trans_id from t)
      ),
      qty_change as (
        select distinct trans_id, trans_dt,
          sum(qty) over (partition by trans_id, trans_dt) as total,
          sum(qty) over (partition by trans_id order by trans_dt) as end_bal
        from t
        right outer join dt_for_trans_id using (trans_id, trans_dt)
      )
      select 
        trans_id,
        to_char(trans_dt, 'DD-Mon-YYYY') as trans_dt,
        nvl(lag(end_bal) over (partition by trans_id order by trans_dt), 0) as beg_bal, 
        nvl(total, 0) as total,
        nvl(end_bal, 0) as end_bal
      from qty_change q
      order by trans_id, trans_dt
      

      dt_between 在您的数据中返回min(trans_dt)max(trans_dt) 之间的所有天数。

      dt_for_trans_id 返回数据中每个 trans_id 的所有这些天数。

      qty_change 查找每天的差异(在您的示例中为 TOTAL)和所有天的累积总和(在您的示例中为 END_BAL)。

      主选择从前一天获取END_BAL,并将其命名为BEG_BAL,它还对最终输出进行了一些格式化。

      【讨论】:

        【解决方案3】:

        首先,您需要生成日期,然后您需要通过 TRANS_DT 聚合您的值,然后将聚合的数据左连接到日期。获得所需总和的最简单方法是使用分析窗函数:

        with dates(dt) as ( -- generating dates between min(TRANS_DT) and max(TRANS_DT) from TRANS
           select min(trans_dt) from trans
           union all
           select dt+1 from dates
           where dt+1<=(select max(trans_dt) from trans)
        )
        ,trans_agg as ( -- aggregating QTY in TRANS
           select TRANS_ID,TRANS_DT,sum(QTY) as QTY
           from trans
           group by TRANS_ID,TRANS_DT
        )
        select -- using left join partition by to get data on daily basis for each trans_id:
           dt,
           trans_id,
           nvl(sum(qty) over(partition by trans_id order by dates.dt range between unbounded preceding and 1 preceding),0) as BEGBAL,
           nvl(qty,0) as TOTAL,
           nvl(sum(qty) over(partition by trans_id order by dates.dt),0) as END_BAL 
        from dates
             left join trans_agg tr
                  partition by (trans_id)
                  on tr.trans_dt=dates.dt;
        

        带有样本数据的完整示例:

        alter session set nls_date_format='dd-mon-yyyy';
        with trans(TRANS_ID,TRANS_DT,QTY) as (
        select 1,to_date('01-Aug-2020'),  5 from dual union all
        select 1,to_date('01-Aug-2020'),  1 from dual union all
        select 1,to_date('03-Aug-2020'),  2 from dual union all
        select 2,to_date('02-Aug-2020'),  1 from dual
        )
        ,dates(dt) as ( -- generating dates between min(TRANS_DT) and max(TRANS_DT) from TRANS
           select min(trans_dt) from trans
           union all
           select dt+1 from dates
           where dt+1<=(select max(trans_dt) from trans)
        )
        ,trans_agg as ( -- aggregating QTY in TRANS
           select TRANS_ID,TRANS_DT,sum(QTY) as QTY
           from trans
           group by TRANS_ID,TRANS_DT
        )
        select 
           dt,
           trans_id,
           nvl(sum(qty) over(partition by trans_id order by dates.dt range between unbounded preceding and 1 preceding),0) as BEGBAL,
           nvl(qty,0) as TOTAL,
           nvl(sum(qty) over(partition by trans_id order by dates.dt),0) as END_BAL 
        from dates
             left join trans_agg tr
                  partition by (trans_id)
                  on tr.trans_dt=dates.dt;
        

        【讨论】:

          【解决方案4】:

          您可以使用递归查询来生成整个日期范围,cross join 它带有不同的tran_id 列表,然后将带有left join 的表带入。最后一步是聚合和窗口函数:

          with all_dates (trans_dt, max_dt) as (
              select min(trans_dt), max(trans_dt) from trans group by trans_id
              union all
              select trans_dt + interval '1' day, max_dt from all_dates where trans_dt < max_dt
          )
          select
              i.trans_id,
              d.trans_dt,
              coalesce(sum(sum(t.qty)) over(partition by i.trans_id order by d.trans_dt), 0) - coalesce(sum(t.qty), 0) begbal,
              coalesce(sum(t.qty), 0) total,
              coalesce(sum(sum(t.qty)) over(partition by i.trans_id order by d.trans_dt), 0) endbal
          from all_dates d
          cross join (select distinct trans_id from trans) i
          left join trans t on t.trans_id = i.trans_id and t.trans_dt = d.trans_dt
          group by i.trans_id, d.trans_dt
          order by i.trans_id, d.trans_dt
          

          【讨论】:

            猜你喜欢
            • 2020-04-17
            • 2015-10-15
            • 1970-01-01
            • 2014-11-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多