Ianthe
Ianthe

Reputation: 5709

Oracle10G SQL : Turning Columns into rows

I need to turn columns into rows and get its average.

For example I have this table:

Name   Math    Science     Computer
----   ----    -------     --------
Ted    90       89          95
Zed    99       98          98
Fed    85       75          90

The output should be:

Subject      Average
-------      -------
Math         88
Science      87.33
Computer     94.33

How can I achieve it? Thank you for the help.

Upvotes: 1

Views: 7741

Answers (1)

Alex Poole
Alex Poole

Reputation: 191275

If you were on 11G you could use unpivot:

SELECT subject, AVG(percentage) AS percentage
FROM (
    SELECT * FROM tablea
    UNPIVOT (percentage FOR subject IN (math, science, computer))
)
GROUP BY subject
ORDER BY subject;

SUBJECT  PERCENTAGE
-------- ----------
COMPUTER      94.33
MATH          91.33
SCIENCE       87.33

But since you are not, you can fake it. Adapting from this site:

SELECT subject, AVG(percentage) AS percentage
FROM (
    SELECT DECODE(unpivot_row, 1, 'Math',
                               2, 'Science',
                               3, 'Computer') AS subject,
           DECODE(unpivot_row, 1, math,
                               2, science,
                               3, computer) AS percentage
    FROM tablea
    CROSS JOIN (SELECT level AS unpivot_row FROM dual CONNECT BY level <= 3)
)
GROUP BY subject
ORDER BY subject;

SUBJECT  PERCENTAGE
-------- ----------
Computer      94.33
Math          91.33
Science       87.33

In both cases, the inner select is transforming rows into columns; in 10g you just have to do it yourself. The SELECT ... CONNECT BY ... just generates a list of dummy values, and this has to have enough to cover the number of columns you are converting to rows (and if you really have 1000, you should really revisit the data model). The two decode statements use that generated number to match up a column name and value - run the inner select on its own to se what that looks like.

Without resorting to dynamic SQL, you can't get away from having to list the columns - only once with the real unpivot, but twice with the fake 10g version, and you have to make sure they match up properly, and that the row number generator is producing enough values. (Too many and you might get odd results, but as any extra values will be null here and you're using avg, it doesn't matter too much in this case; just as a sanity check you should probably make it match exactly anyway).


Or another version, based on you always wanting all the columns except name, which means you only need to list the columns you do want once and it's easier to match them up visually - just keep adding when clauses; and you don't need the row count:

SELECT subject, AVG(percentage) AS percentage
FROM (
    SELECT column_name AS subject,
        CASE
            WHEN column_name = 'MATH' then math
            WHEN column_name = 'SCIENCE' then science
            WHEN column_name = 'COMPUTER' then computer
        END AS percentage
    FROM tablea
    CROSS JOIN (
        SELECT column_name
        FROM user_tab_columns
        WHERE table_name = 'TABLEA'
        AND column_name != 'NAME'
    )
)
GROUP BY subject
ORDER BY subject;

SUBJECT                        PERCENTAGE
------------------------------ ----------
COMPUTER                            94.33
MATH                                91.33
SCIENCE                             87.33

Upvotes: 2

Related Questions