views:

434

answers:

2

update: what I was calling coalesce I should have been calling pivot.

I'm extracting some daily usage counts from a log table. I can easily get this data one row per date/item, but I would like to pivot coalesce the columns into a single row.

e.g., I have:

date    item-to-be-counted count-of-item
10/1    foo                23
10/1    bar                45
10/2    foo                67
10/2    bar                89

I want:

date    count-of-foo     count-of-bar
10/1    23               45
10/2    67               89

Here's my current 10g query.

select    trunc(started,'HH'),depot,count(*)
  from    logstats
 group by trunc(started,'HH'),depot
 order by trunc(started,'HH'),depot;

TRUNC(STARTED,'HH')       DEPOT      COUNT(*)
------------------------- ---------- --------
10/01/11 01.00.00         foo        28092
10/01/11 01.00.00         bar        2194
10/01/11 02.00.00         foo        3402
10/01/11 02.00.00         bar        1058

update: 11g has a pivot operation. The accepted answer shows how to do this in 9i and 10g.

A: 

Well, I can at least provide an 11 solution; use Pivot:

http://www.oracle.com/technology/pub/articles/oracle-database-11g-top-features/11g-pivot.html

The only 10g option I can think of offhand (I'm decent with SQL, but not an expert) is to fill a table variable and then select individual rows out of that table for your final result. Ugly and likely fairly slow, but could get the job done.

Randolpho
+3  A: 

What you're looking for is pivoting - transposing the row data into columnar.

Oracle 9i+, Using WITH/CTE:


Use:

WITH summary AS (
    SELECT TRUNC(ls.started,'HH') AS dt,
           ls.depot,
           COUNT(*) AS num_depot
      FROM logstats ls
  GROUP BY TRUNC(ls.started,'HH'), ls.depot)
  SELECT s.dt,
         MAX(CASE WHEN s.depot = 'foo' THEN s.num_depot ELSE 0 END) AS "count_of_foo",
         MAX(CASE WHEN s.depot = 'bar' THEN s.num_depot ELSE 0 END) AS "count_of_bar"
    FROM summary s
GROUP BY s.dt
ORDER BY s.dt

Non-WITH/CTE Equivalent


Use:

  SELECT s.dt,
         MAX(CASE WHEN s.depot = 'foo' THEN s.num_depot ELSE 0 END) AS "count_of_foo",
         MAX(CASE WHEN s.depot = 'bar' THEN s.num_depot ELSE 0 END) AS "count_of_bar"
    FROM (SELECT TRUNC(ls.started,'HH') AS dt,
                 ls.depot,
                 COUNT(*) AS num_depot
            FROM LOGSTATS ls
        GROUP BY TRUNC(ls.started, 'HH'), ls.depot) s
GROUP BY s.dt
ORDER BY s.dt

Pre Oracle9i would need the CASE statements changed to DECODE, Oracle specific IF/ELSE logic.

Oracle 11g+, Using PIVOT


Untested:

  SELECT * 
    FROM (SELECT TRUNC(ls.started, 'HH') AS dt,
                 ls.depot
            FROM LOGSTATS ls
        GROUP BY TRUNC(ls.started, 'HH'), ls.depot)
   PIVOT (
     COUNT(*) FOR depot
   )
ORDER BY 1
OMG Ponies
do you mind to double-check example 1? I fixed the "as" quoting, but am getting ORA-00904: "SUMMARY"."DT": invalid identifier on the last line. thx!
Mark Harrison
OMG Ponies
hmm, i'm also using sqldeveloper... with the single quotes it gives <<ORA-00923: FROM keyword not found where expected>>, changing to double-quotes fixes these. this with 10g.
Mark Harrison
OMG Ponies
hmm, it's not working with sqlplus. also, in the original there was an extra comma before the FROM. I'll revert my edit back to your example, and leave the results I'm getting. I'll google "10g pivot" and see if I can figure it out. thanks for the pointer!
Mark Harrison
btw, are you sure you're not thinking of bare-words and not single-quoted strings? <as foo> and <as "foo"> both work as identifiers, but not <as 'foo'>.
Mark Harrison
Checked my old 10g code - don't have one handy to test. I have instances where the `AS` keyword isn't used, nor is the alias within single or double quotes.
OMG Ponies
yes, that should be correct... the alias is specified as <<[AS] identifier>>, where identifier is either a bare-word or double-quoted.
Mark Harrison
hooray, it's working perfectly now... thanks for the fix!
Mark Harrison