简体   繁体   中英

Oracle SQL to convert rows to columns

I could not find a suitable answer so I am writing it here. I have a table with following fields.

ID           Amount    DocNum   DocStatus   DueDate
AA           2400      00005     1          10-Jun-2019
AA           1400      00006     4          21-Sep-2019
AA           9000      00028     1          22-Aug-2020 
AA           5000      00201     2          31-Aug-2020
AA           6400      00410     1          22-Jan-2021
AA           2000      00511     1          01-Mar-2021
BB           1500      01390     1          01-Jan-2021

I would want to display a top 3 latest documents with Status 1

ID Document1 Amount1 Document2 Amount2 Document3 Amount3 
AA 00511     2000    00410     6400    00028     9000 
BB 01390     1500    XX        XX      XX        XX

I thought I could use Pivot or Decode but unable to determine other conditions. Any help is appreciated.

You can use row_number() and conditional aggregation:

select id,
       max(case when seqnum = 1 then docnum end) as docnum_1,
       max(case when seqnum = 1 then amount end) as amount_1,
       max(case when seqnum = 2 then docnum end) as docnum_2,
       max(case when seqnum = 2 then amount end) as amount_2,
       max(case when seqnum = 3 then docnum end) as docnum_3,
       max(case when seqnum = 3 then amount end) as amount_3
from (select t.*,
             row_number() over (partition by id order by due_date desc) as seqnum
      from t
      where status = 1
     ) t
group by id;
alter session set nls_date_format='dd-Mon-yyyy';

with
  my_table (id, amount, docnum, docstatus, duedate) as (
    select 'AA', 2400, '00005', 1, to_date('10-Jun-2019') from dual union all
    select 'AA', 1400, '00006', 4, to_date('21-Sep-2019') from dual union all
    select 'AA', 9000, '00028', 1, to_date('22-Aug-2020') from dual union all
    select 'AA', 5000, '00201', 2, to_date('31-Aug-2020') from dual union all
    select 'AA', 6400, '00410', 1, to_date('22-Jan-2021') from dual union all
    select 'AA', 2000, '00511', 1, to_date('01-Mar-2021') from dual union all
    select 'BB', 1500, '01390', 1, to_date('01-Jan-2021') from dual
  )
select id, "1_DOC" as document1, "1_AMT" as amount1,
           "2_DOC" as document2, "2_AMT" as amount2,
           "3_DOC" as document3, "3_AMT" as amount3
from   (
         select id, amount, docnum, 
                row_number() over (partition by id 
                                   order by duedate desc) as rn
         from   my_table
         where  docstatus = 1
       )
pivot  (min(docnum) as doc, min(amount) as amt for rn in (1, 2, 3))
;


ID DOCUMENT1    AMOUNT1 DOCUMENT2    AMOUNT2 DOCUMENT3    AMOUNT3
-- --------- ---------- --------- ---------- --------- ----------
AA 00511           2000 00410           6400 00028           9000
BB 01390           1500     

You need to do all the prep work in the subquery: filter for docstatus = 1 , create the RN ranking by duedate descending, and select just the columns you need for the pivot. The outer query, other than the trivial pivoting (trivial after you do all the prep work in the subquery), needs just a bit of care in the select clause, to get the column names right.

You can dynamically generate the desired SQL SELECT Statement in order to pivot the rows to display whether top 2,3,4..etc by creating such a function with IN parameter to represent the top 2,3,4..etc and returning SYS_REFCURSOR type result set as

CREATE OR REPLACE FUNCTION Fn_Pivot_Doc_and_Amounts( numcol INT ) RETURN SYS_REFCURSOR IS
  v_recordset SYS_REFCURSOR;
  v_sql       VARCHAR2(32767);
  v_cols      VARCHAR2(32767);
BEGIN
  SELECT LISTAGG( ''||level||' AS "'||level||'"' , ',' )
                 WITHIN GROUP ( ORDER BY level )
    INTO v_cols
    FROM dual
   CONNECT BY level <= numcol;

  v_sql :='SELECT *
             FROM(SELECT id,docnum,amount,
                         ROW_NUMBER() OVER (PARTITION BY id ORDER BY duedate DESC) AS rn
                    FROM tab t
                   WHERE docstatus = 1)
            PIVOT(
                  MAX(docnum) AS document,
                  MAX(amount) AS amount  FOR rn IN ( '|| v_cols ||'  )
                 )';

  OPEN v_recordset FOR v_sql;
  RETURN v_recordset;
END;

and then call from SQL Developer 's console as

SQL> DECLARE
       result SYS_REFCURSOR;
BEGIN
   :result := Fn_Pivot_Doc_and_Amounts(2); -- 3,4,...
END;
/

SQL> PRINT result;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM