Skip to Main Content

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Steve.

Asked: October 22, 2019 - 3:52 pm UTC

Last updated: October 24, 2019 - 8:39 am UTC

Version: 18

Viewed 1000+ times

You Asked

Consider:

with data as (select 'MH' initials, to_date('01092019','ddmmyyyy') cal_date, 23 quantity from dual
              union all
              select 'MH' initials, to_date('02092019','ddmmyyyy') cal_date, 18 quantity from dual
              union all
              select 'MH' initials, to_date('03092019','ddmmyyyy') cal_date, 14 quantity from dual
              union all
              select 'RD' initials, to_date('01092019','ddmmyyyy') cal_date, 24 quantity from dual
              union all
              select 'RD' initials, to_date('02092019','ddmmyyyy') cal_date, 13 quantity from dual
              union all
              select 'RD' initials, to_date('03092019','ddmmyyyy') cal_date, 23 quantity from dual)


I'm trying to get an XML select statement that produces:

<rows>
  <head>
     <column> 1st Sep 2019 </column>
     <column> 2nd Sep 2019 </column>
     <column> 3rd Sep 2019 </column
   </head>
   <row>
     <cell> MH 23 </cell>
     <cell> MH 18 </cell>
     <cell> MH 14 </cell>
   </row>
   <row>
     <cell> RD 24 </cell>
     <cell> RD 13 </cell>
     <cell> RD 23 </cell>
   </row>
  </rows>




I seem to be able to get the bottom part ok but am struggling with getting the unique dates in the <head> tag.

and Chris said...

Here's one way to approach it:

- Format the data in a subquery. Also assign a row number to each date using dense_rank
- Use this to pivot the MH and RD groups. The rank will give you a row/date and columns for MH & RD
- Aggregate these all up into your XML:

with data as (
  select 'MH' initials, to_date('01092019','ddmmyyyy') cal_date, 23 quantity from dual
  union all
  select 'MH' initials, to_date('02092019','ddmmyyyy') cal_date, 18 quantity from dual
  union all
  select 'MH' initials, to_date('03092019','ddmmyyyy') cal_date, 14 quantity from dual
  union all
  select 'RD' initials, to_date('01092019','ddmmyyyy') cal_date, 24 quantity from dual
  union all
  select 'RD' initials, to_date('02092019','ddmmyyyy') cal_date, 13 quantity from dual
  union all
  select 'RD' initials, to_date('03092019','ddmmyyyy') cal_date, 23 quantity from dual
), vals as (
  select to_char ( cal_date , ' dd Mon yyyy ' ) cal_date,
         initials || ' ' || quantity as cell,
         initials grp,
         dense_rank () over ( order by cal_date ) rk
  from   data
), rws as ( 
  select * from vals
  pivot ( 
    min ( cell ) for grp in ( 'MH' mh, 'RD' rd ) 
  )
)
  select xmlelement ( 
           "rows",
           xmlforest (
             xmlagg ( xmlelement ( "column", cal_date ) order by rk ) as "head",
             xmlagg ( xmlelement ( "cell", mh ) order by rk ) as "row",
             xmlagg ( xmlelement ( "cell", rd ) order by rk ) as "row"
           )
         ) x
  from   rws;
  
<rows>
  <head>
    <column>01 Sep 2019</column>
    <column>02 Sep 2019</column>
    <column>03 Sep 2019</column>
  </head>
  <row>
    <cell>MH 23</cell>
    <cell>MH 18</cell>
    <cell>MH 14</cell>
  </row>
  <row>
    <cell>RD 24</cell>
    <cell>RD 13</cell>
    <cell>RD 23</cell>
  </row>
</rows>


This assumes that there's an MH & RD entry for every date. If there isn't you'll need to tweak this depending on how this should appear in the final document.

Rating

  (1 rating)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Steve, October 23, 2019 - 7:16 pm UTC

Lovely, thank you. But you're right - there could be nulls in which case we'd expect to see an empty cell. So, for example, if MH didn't have an entry for 2nd September we'd see
...<cell> MH 23</cell>
         <cell/>
         <cell> MH 14</cell>...


Also, a further complication would be that we don't know until runtime whether we have more unique "initials". So we could have MH, RD, JH, AM etc etc - or not for each date! And, in the above example, if MH didn't have any entries on any of the dates we would just want to ignore it.
Chris Saxon
October 24, 2019 - 8:39 am UTC

You could get around the missing entry issue by outer joining to a list of all possible dates before pivoting.

Dealing with unknown initials is a harder problem. Which doesn't have a good solution.

Head to

https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot

And search for "Dynamic Column Lists" for workarounds.

More to Explore

Analytics

Analytic SQL got you confused? Check out Connor McDonald's complete video course.