I need to turn columns into rows and get its average.
For example I have this table:
Name Math Science Computer
---- ---- ------- -----
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