You've come to the right place. I will show you how and it's crazy simple.
In my case, and probably yours, you have a query result such as this:
colname1 colname2 colname3
data data data
And you need it like this:
Attribute Value
colname1 data
colname2 data
colname3 data
So did I. And I did exactly what you're doing. I scoured my books. Pivot or max/decode. I Googled till my fingers bled. Pivot or union all. More Googling. A table function example. Too heavy.
In most, if not all, cases stating that you needed to hardcode column values of your query.
I didn't want to do that as I knew the project where I needed this type of query result would change. And I didn't want to crack open a package or hard code another column in somewhere when it did.
In the middle of writing a table function, at the ends of desperation, I googled one final time.
And found. The answer.
XML.
Oracle's provided XML functions would allow me to transform my result set into XML, and in doing so, allow me to use XPATH to be able to discern both column name and value from each node (record) returned.
The solution to all your troubles my friend is below for your enjoyment, plain and simple. You may need to brush up a bit on your XPATH syntax, but having the below query @ your disposal takes care of 90% of your problems.
SELECT
X.ATTR
,X.VAL
FROM XMLTABLE ('ROWSET/ROW/*' PASSING
DBMS_XMLGEN.GETXMLTYPE('
SELECT
dummy AS example_col1, dummy AS example_col2, dummy AS example_col3 from dual where rownum < 2') COLUMNS
ATTR VARCHAR2(4000) PATH 'name()', VAL VARCHAR2(4000) PATH 'data(.)'
) X
So if you need it, allow me to explain, briefly, what's going on above.
- First you have YOUR query highlighted in yellow. If your query returns a result, you can use it here. To make it simple to read in my real world project, I used a view to hide most of the major SQL and was able to simply write 'select * from my_view' in the high lighted area above. In the simple example above, you'd generally want one record returned so you'd usually add some sort of predicate specifying a key, etc.
- Your query is returned as XML at which time you use the awesome Oracle provided function XMLTABLE to query from that XML fragment as if it were a sql result set.
- You'll notice the hard-coded 'ROWSET/ROW/*' specification. That tells the XMLTABLE to work on every child node of ROW returned. You can adjust this search path to return more/less specific areas of your result set. (Oracle returns your query using DBMS_XMLGEN.GETXMLTYPE with ROWSET as the main XML node and ROW as the node housing each record.)
I hope this example reaches you before you've written that pivot query or table function as it provides a tremendously flexible, yet amazingly simple (10 lines of code up there - and I broke it up a bit ;)), solution to get your columns into the rows you need to loop thru.
Please keep in mind that the example I give is extremely simple just to demonstrate the point. My final query involved a much more complex example which allowed me to not only return a key/value pair, but also return an 'id' column for the returned rows allowing me to no longer need the 'key' in the xml'd sql query as suggested in step 1 above. As such, I could turn the column to row sql into a view where I could then get all records returned and then filter by the key after.
Please keep in mind that the example I give is extremely simple just to demonstrate the point. My final query involved a much more complex example which allowed me to not only return a key/value pair, but also return an 'id' column for the returned rows allowing me to no longer need the 'key' in the xml'd sql query as suggested in step 1 above. As such, I could turn the column to row sql into a view where I could then get all records returned and then filter by the key after.
Can you share your final "complex" script?
ReplyDeleteTHANK YOU SO MUCH!!! I was surprised by the effort it normally takes to do a simple transposee in oracle sql, this is a great solution.
ReplyDeleteMohammed - so happy to have helped! I've taken so much from the online community that it's nice to be able to give back on occasion!
ReplyDeleteis it possible to have data in multiple columns like
ReplyDeleteThis data
ATTR VAL
PDATE 02/23/2013
CARAT 4.25
LNK_SHAPE 113
LNK_CLARITY 94
LNK_COLOR 35
LNK_CUT 41
LNK_RATIO 38
LNK_FL 48
LNK_TINT 4
LNK_MILKY 57
LNK_POLISH 141
LNK_SYMN 145
LNK_NC 82
LNK_NS 85
PRICE 133416
PDATE 02/23/2013
CARAT 4.25
LNK_SHAPE 2
LNK_CLARITY 92
LNK_COLOR 35
LNK_CUT 162
PRICE 160854
to ....
ATTR VAL valx
PDATE 02/23/2013 02/23/2013
CARAT 4.25 4.25
LNK_SHAPE 113 2
LNK_CLARITY 94 92
LNK_COLOR 35 35
LNK_CUT 41 162
LNK_RATIO 38 -
LNK_FL 48 -
LNK_TINT 4 -
LNK_MILKY 57 -
LNK_POLISH 141 -
LNK_SYMN 145 -
LNK_NC 82 -
LNK_NS 85 -
PRICE 133416 160854
I'd have to see the structure of your original table to determine where the secondary (valx) would be coming from. Anything is possible, just don't have all the data needed to answer your question at the moment!
ReplyDeleteHi Steve,
DeleteThanks for the reply
Please have a look here http://tinypic.com/r/4vrbpf/6
THANK YOU SO MUCH!!!
ReplyDeleteIt's simple, it's the solution.
Geimer.
Steve,
ReplyDeleteI found your blog looking for the solution of converting columns to namevalue pairs. It works great and super easy too. I have a slight challenge and I am hoping you could help. The result set I have is attributes for locations. So after ysing your solution, my result set looks like this:
Attr AttrVal
STR# 1
Open 0900
Close 2100
Phone 1234567890
Timezone EST
STR# 2
Open 0800
Close 2200
Phone 9876543210
Timezone PST
I want it to look like this:
STR# Attr AttrVal
1 Open 0900
1 Close 2100
1 Phone 1234567890
1 Timezone EST
2 Open 0800
2 Close 2200
2 Phone 9876543210
2 Timezone PST
Any suggestions?
Thx,
Bach.