Generate JSON Structure looping over table in Oracle SQL - sql

I have ORACLE-SQL Table in below format.
Version is Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit
CREATE TABLE temp_Table
(
"OVRPK_CNTNR_ID" CHAR(20) NOT NULL ENABLE,
"ITEM_DISTB_Q" NUMBER(3) NOT NULL ENABLE,
"SSP_Q" NUMBER(6,0) NOT NULL ENABLE,
"BRKPK_CNTNR_ID" CHAR(20)
) ;
INSERT INTO temp_Table (OVRPK_CNTNR_ID, ITEM_DISTB_Q, SSP_Q, BRKPK_CNTNR_ID ) VALUES('92000000356873110552', 6,8,'93058901021076000085');
INSERT INTO temp_Table (OVRPK_CNTNR_ID,ITEM_DISTB_Q,SSP_Q, BRKPK_CNTNR_ID ) VALUES('92000000356873110552',8, 10,'93058901021117700080');
INSERT INTO temp_Table (OVRPK_CNTNR_ID,ITEM_DISTB_Q,SSP_Q, BRKPK_CNTNR_ID ) VALUES('92000000356873110552', 10,2,'93058901022276900083');
INSERT INTO temp_Table (OVRPK_CNTNR_ID,ITEM_DISTB_Q,SSP_Q, BRKPK_CNTNR_ID ) VALUES('92000000334160826089', 3,4,'000000000083465239');
INSERT INTO temp_Table (OVRPK_CNTNR_ID,ITEM_DISTB_Q,SSP_Q, BRKPK_CNTNR_ID ) VALUES('92000000334160826089', 3,4,'000000000083438160');
I have written trigger for above table.
AFTER INSERT OR DELETE OR UPDATE OF
OVRPK_CNTNR_ID, ITEM_DISTB_Q, SSP_Q, BRKPK_CNTNR_ID
ON
temp_Table
FOR EACH ROW
DECLARE
json_message CLOB := '';
BEGIN
/*
* pick OVRPK_CNTNR_ID from triggers row and send it to function createJson() return json object, example : 92000000356873110552
*/
json_message = CALL FUNCTION createJson(:NEW.OVRPK_CNTNR_ID);
END;
CREATE OR REPLACE FUNCTION createJson(ovrpk_container_id IN char)
BEGIN
SELECT json_query(
json_objectagg(OVRPK_CNTNR_ID value
json_array(
json_object("break_pack_container_id" VALUE BRKPK_CNTNR_ID,
"ssp_quantity" VALUE SSP_Q,
"item_distribution_quantity" VALUE ITEM_DISTB_Q)))),
'$' returning VARCHAR2(4000) pretty )
AS "Result JSON"
FROM temp_Table
END;
I am trying to create OUTPUT like below: but oracle version is Database 12c. I am not sure if it supports.
{
"Over_pack_container_id":"92000000356873110552"
"type": "OVER_PACK",
"sub_containers": [
{
"break_pack_container_id": "93058901021076000085",
"ssp_quantity": 8,
"item_distribution_quantity": 6
},
{
"break_pack_container_id": "93058901021117700080",
"ssp_quantity": 10,
"item_distribution_quantity": 8
},
{
"break_pack_container_id": "93058901022276900083",
"ssp_quantity": 2,
"item_distribution_quantity": 10
}
]
}
can someone help me here? Thank you

It is unclear what you are trying to achieve with the trigger or the function because the trigger does not do anything.
However, you can get your JSON output format using:
SELECT JSON_OBJECT(
KEY 'Over_pack_container_id' VALUE OVRPK_CNTNR_ID,
KEY 'type' VALUE 'OVER_PACK',
KEY 'sub_containers' VALUE JSON_ARRAYAGG(
JSON_OBJECT(
KEY 'break_pack_container_id' VALUE BRKPK_CNTNR_ID,
KEY 'ssp_quantity' VALUE SSP_Q,
KEY 'item_distribution_quantity' VALUE ITEM_DISTB_Q
)
)
) AS "Result JSON"
FROM temp_Table
GROUP BY OVRPK_CNTNR_ID;
Which, for the sample data, outputs:
Result JSON
{"Over_pack_container_id":"92000000334160826089","type":"OVER_PACK","sub_containers":[{"break_pack_container_id":"000000000083465239 ","ssp_quantity":4,"item_distribution_quantity":3},{"break_pack_container_id":"000000000083438160 ","ssp_quantity":4,"item_distribution_quantity":3}]}
{"Over_pack_container_id":"92000000356873110552","type":"OVER_PACK","sub_containers":[{"break_pack_container_id":"93058901021076000085","ssp_quantity":8,"item_distribution_quantity":6},{"break_pack_container_id":"93058901022276900083","ssp_quantity":2,"item_distribution_quantity":10},{"break_pack_container_id":"93058901021117700080","ssp_quantity":10,"item_distribution_quantity":8}]}
If you only want a single container ID in your function then use a where clause:
SELECT JSON_OBJECT(
KEY 'Over_pack_container_id' VALUE OVRPK_CNTNR_ID,
KEY 'type' VALUE 'OVER_PACK',
KEY 'sub_containers' VALUE JSON_ARRAYAGG(
JSON_OBJECT(
KEY 'break_pack_container_id' VALUE BRKPK_CNTNR_ID,
KEY 'ssp_quantity' VALUE SSP_Q,
KEY 'item_distribution_quantity' VALUE ITEM_DISTB_Q
)
)
) AS "Result JSON"
FROM temp_Table
WHERE OVRPK_CNTNR_ID = ovrpk_container_id
GROUP BY OVRPK_CNTNR_ID;
If you want to do it on a version before the JSON functions were introduced then you can use LISTAGG (from Oracle 11 and provided you are not generating more than 4000 byte strings):
SELECT '{'
|| '"Over_pack_container_id":"' || OVRPK_CNTNR_ID || '",'
|| '"type":"OVER_PACK",'
|| '"sub_containers":['
|| LISTAGG(
'{'
|| '"break_pack_container_id":"' || BRKPK_CNTNR_ID || '"'
|| '"ssp_quantity":' || COALESCE(TO_CHAR(SSP_Q), 'null')
|| '"item_distribution_quantity":' || COALESCE(TO_CHAR(ITEM_DISTB_Q), 'null')
|| '}',
','
) WITHIN GROUP (ORDER BY BRKPK_CNTNR_ID)
|| ']'
|| '}'
AS "Result JSON"
FROM temp_Table
GROUP BY OVRPK_CNTNR_ID;
fiddle

Related

Passing column name as parameter in Oracle PL_SQL function

I am trying to pass two input parameters, column name and table name . Then the function should output a string value as defined below :
create or replace function get_id5(in_col_name IN VARCHAR2,in_tbl_name IN VARCHAR2)
return VARCHAR2
is
/*in_col_nm varchar(32) := in_col_name;
/*in_tbl_nm varchar(64) := in_tbl_name; */
integer_part NUMBER ;
integer_part_str VARCHAR2(32) ;
string_part VARCHAR2(32) ;
full_id VARCHAR2(32) ;
out_id VARCHAR(32) ;
BEGIN
/*select MAX(in_col_nm) INTO full_id FROM in_tbl_nm ; */
execute immediate 'select MAX('||in_col_name||') FROM' || in_tbl_name ||'INTO' || full_id;
/*select regexp_replace(full_id , '[^0-9]', '') INTO integer_part_str , regexp_replace(full_id , '[^a-z and ^A-Z]', '') INTO string_part from dual ; */
integer_part_str := regexp_replace(full_id , '[^0-9]', '') ;
string_part := regexp_replace(full_id , '[^a-z and ^A-Z]', '') ;
integer_part := TO_NUMBER(integer_part_str);
integer_part := integer_part + 1 ;
integer_part_str := TO_CHAR(integer_part) ;
out_id := string_part + integer_part_str ;
return out_id;
END;
I have a table named BRANDS in Database and the max value for column BRAND_ID is 'Brand05'. The expected output is 'Brand06'.
However when i run:
select get_id5('BRAND_ID' , 'BRANDS') from dual;
or
DECLARE
a VARCHAR(32) ;
BEGIN
a := get_id5('BRAND_ID' , 'BRANDS');
END;
I am getting the below error:
ORA-00923: FROM keyword not found where expected
You need spaces between the FROM and the table_name and the INTO should be outside of the SQL text:
execute immediate 'select MAX('||in_col_name||') FROM ' || in_tbl_name INTO full_id;
You could also use DBMS_ASSERT:
execute immediate 'select MAX('||DBMS_ASSERT.SIMPLE_SQL_NAME(in_col_name)||') FROM ' || DBMS_ASSERT.SIMPLE_SQL_NAME(in_tbl_name) INTO full_id;
Then you need to use || as the string concatenation operator (rather than +).
Your function could be:
create or replace function get_id5(
in_col_name IN VARCHAR2,
in_tbl_name IN VARCHAR2
) RETURN VARCHAR2
IS
full_id VARCHAR2(32);
BEGIN
execute immediate 'select MAX('||DBMS_ASSERT.SIMPLE_SQL_NAME(in_col_name)||') '
|| 'FROM ' || DBMS_ASSERT.SIMPLE_SQL_NAME(in_tbl_name)
INTO full_id;
return regexp_replace(full_id , '\d+', '')
|| TO_CHAR(regexp_replace(full_id , '\D+', '') + 1);
END;
/
For the sample data:
CREATE TABLE brands (brand_id) AS
SELECT 'BRAND5' FROM DUAL;
Then:
select get_id5('BRAND_ID' , 'BRANDS') AS id from dual;
Outputs:
ID
BRAND6
db<>fiddle here
A better solution
To generate the number, use a SEQUENCE or, from Oracle 12, an IDENTITY column and, if you must have a string prefix then use a virtual column to generate it.
CREATE TABLE brands(
ID NUMBER
GENERATED ALWAYS AS IDENTITY
PRIMARY KEY,
brand_id VARCHAR2(10)
GENERATED ALWAYS
AS (CAST('BRAND' || TO_CHAR(id, 'FM000') AS VARCHAR2(10)))
);
BEGIN
INSERT INTO brands (id) VALUES (DEFAULT);
INSERT INTO brands (id) VALUES (DEFAULT);
INSERT INTO brands (id) VALUES (DEFAULT);
END;
/
SELECT * FROM brands;
Outputs:
ID
BRAND_ID
1
BRAND001
2
BRAND002
3
BRAND003
db<>fiddle here

How to have XMLForest returning all columns of a table

I have a table with a lot of columns (e.g : Column1, Column 2, Column 3, Column 4, ...)
I'd like to use XMLElement and XMLForest function to generate an XML with each column being a tag.
I'm only able to do this by manually adding each column in the XMLForest :
e.g :
SELECT
XMLElement("ParentTag",
XMLForest(TABLE.Column1,
TABLE.Column2,
TABLE.Column2,
...)
)
FROM ...
Results :
<ParentTag> <Column1>Value1</Column1> <Column2>Value2</Column2> ...</ParentTag>
However i'd like to avoid typing each column as their number could increase in the future.
How can i do something like this ? :
SELECT
XMLElement("ParentTag",
XMLForest(TABLE.*)
)
FROM ...
You can use a PLSQL procedure to get your requirement done. Here in the PLSQL procedure, it would accept a Tablename and then generate the XMLForest and show the result. See below:
-- Creating a type of XMLTYPE
CREATE OR REPLACE TYPE Outpt IS TABLE OF XMLTYPE;
/
--Procedure with In parameter as Tablename and out parameter as resultset
CREATE OR REPLACE PROCEDURE XM_FOREST (tabnm VARCHAR2, v_out IN OUT Outpt)
AS
var VARCHAR2 (4000);
v_sql VARCHAR2 (4000);
BEGIN
FOR i IN (SELECT cname
FROM col
WHERE tname = tabnm)
LOOP
var := var || ',' || i.cname;
END LOOP;
var := LTRIM (var, ',');
v_sql :=
'select XMLElement("ParentTag",XMLForest('
|| var
|| ' ) ) from '
|| tabnm;
EXECUTE IMMEDIATE v_sql BULK COLLECT INTO v_out;
END;
--------------
--Execution
DECLARE
var_out Outpt := Outpt ();
LCLOB CLOB;
BEGIN
var_out.EXTEND;
XM_FOREST (tabnm => 'EMPLOYEE', v_out => var_out);
FOR i IN 1 .. var_out.COUNT
LOOP
LCLOB := var_out (i).getCLOBVAL ();
DBMS_OUTPUT.put_line (LCLOB);
END LOOP;
END;
------
--Result
SQL> /
<ParentTag><EMPLOYEE_ID>1</EMPLOYEE_ID><FIRST_NAME>XXX</FIRST_NAME></ParentTag>
<ParentTag><EMPLOYEE_ID>2</EMPLOYEE_ID><FIRST_NAME>YYY</FIRST_NAME></ParentTag>
PL/SQL procedure successfully completed.
How can i do something like this ? :
SELECT
XMLElement("ParentTag",
XMLForest(TABLE.*)
)
FROM ...
You cannot, you will have to type out all the names individually.
You could generate the query using dynamic SQL
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name (
id NUMBER,
a NUMBER,
b NUMBER,
c NUMBER,
d NUMBER
);
Query 1:
SELECT '
SELECT XMLElement(
"ParentTag",
XMLForest( '
|| LISTAGG( '"' || column_name || '"', ',' )
WITHIN GROUP ( ORDER BY Column_id )
||' ) ) FROM ...' AS query
FROM user_tab_columns
WHERE table_name = 'TABLE_NAME'
Results:
| QUERY |
|-------------------------------------------------------|
| SELECT XMLElement( |
| "ParentTag", |
| XMLForest( "ID","A","B","C","D" ) ) FROM ... |

Count the number of null values into an Oracle table?

I need to count the number of null values of all the columns in a table in Oracle.
For instance, I execute the following statements to create a table TEST and insert data.
CREATE TABLE TEST
( A VARCHAR2(20 BYTE),
B VARCHAR2(20 BYTE),
C VARCHAR2(20 BYTE)
);
Insert into TEST (A) values ('a');
Insert into TEST (B) values ('b');
Insert into TEST (C) values ('c');
Now, I write the following code to compute the number of null values in the table TEST:
declare
cnt number :=0;
temp number :=0;
begin
for r in ( select column_name, data_type
from user_tab_columns
where table_name = upper('test')
order by column_id )
loop
if r.data_type <> 'NOT NULL' then
select count(*) into temp FROM TEST where r.column_name IS NULL;
cnt := cnt + temp;
END IF;
end loop;
dbms_output.put_line('Total: '||cnt);
end;
/
It returns 0, when the expected value is 6.
Where is the error?
Thanks in advance.
Counting NULLs for each column
In order to count NULL values for all columns of a table T you could run
SELECT COUNT(*) - COUNT(col1) col1_nulls
, COUNT(*) - COUNT(col2) col2_nulls
,..
, COUNT(*) - COUNT(colN) colN_nulls
, COUNT(*) total_rows
FROM T
/
Where col1, col2, .., colN should be replaced with actual names of columns of T table.
Aggregate functions -like COUNT()- ignore NULL values, so COUNT(*) - COUNT(col) will give you how many nulls for each column.
Summarize all NULLs of a table
If you want to know how many fields are NULL, I mean every NULL of every record you can
WITH d as (
SELECT COUNT(*) - COUNT(col1) col1_nulls
, COUNT(*) - COUNT(col2) col2_nulls
,..
, COUNT(*) - COUNT(colN) colN_nulls
, COUNT(*) total_rows
FROM T
) SELECT col1_nulls + col1_nulls +..+ colN_null
FROM d
/
Summarize all NULLs of a table (using Oracle dictionary tables)
Following is an improvement in which you need to now nothing but table name and it is very easy to code a function based on it
DECLARE
T VARCHAR2(64) := '<YOUR TABLE NAME>';
expr VARCHAR2(32767);
q INTEGER;
BEGIN
SELECT 'SELECT /*+FULL(T) PARALLEL(T)*/' || COUNT(*) || ' * COUNT(*) OVER () - ' || LISTAGG('COUNT(' || COLUMN_NAME || ')', ' + ') WITHIN GROUP (ORDER BY COLUMN_ID) || ' FROM ' || T
INTO expr
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = T;
-- This line is for debugging purposes only
DBMS_OUTPUT.PUT_LINE(expr);
EXECUTE IMMEDIATE expr INTO q;
DBMS_OUTPUT.PUT_LINE(q);
END;
/
Due to calculation implies a full table scan, code produced in expr variable was optimized for parallel running.
User defined function null_fields
Function version, also includes an optional parameter to be able to run on other schemas.
CREATE OR REPLACE FUNCTION null_fields(table_name IN VARCHAR2, owner IN VARCHAR2 DEFAULT USER)
RETURN INTEGER IS
T VARCHAR2(64) := UPPER(table_name);
o VARCHAR2(64) := UPPER(owner);
expr VARCHAR2(32767);
q INTEGER;
BEGIN
SELECT 'SELECT /*+FULL(T) PARALLEL(T)*/' || COUNT(*) || ' * COUNT(*) OVER () - ' || listagg('COUNT(' || column_name || ')', ' + ') WITHIN GROUP (ORDER BY column_id) || ' FROM ' || o || '.' || T || ' t'
INTO expr
FROM all_tab_columns
WHERE table_name = T;
EXECUTE IMMEDIATE expr INTO q;
RETURN q;
END;
/
-- Usage 1
SELECT null_fields('<your table name>') FROM dual
/
-- Usage 2
SELECT null_fields('<your table name>', '<table owner>') FROM dual
/
Thank you #Lord Peter :
The below PL/SQL script works
declare
cnt number :=0;
temp number :=0;
begin
for r in ( select column_name, nullable
from user_tab_columns
where table_name = upper('test')
order by column_id )
loop
if r.nullable = 'Y' then
EXECUTE IMMEDIATE 'SELECT count(*) FROM test where '|| r.column_name ||' IS NULL' into temp ;
cnt := cnt + temp;
END IF;
end loop;
dbms_output.put_line('Total: '||cnt);
end;
/
The table name test may be replaced the name of table of your interest.
I hope this solution is useful!
The dynamic SQL you execute (this is the string used in EXECUTE IMMEDIATE) should be
select sum(
decode(a,null,1,0)
+decode(b,null,1,0)
+decode(c,null,1,0)
) nullcols
from test;
Where each summand corresponds to a NOT NULL column.
Here only one table scan is necessary to get the result.
Use the data dictionary to find the number of NULL values almost instantly:
select sum(num_nulls) sum_num_nulls
from all_tab_columns
where owner = user
and table_name = 'TEST';
SUM_NUM_NULLS
-------------
6
The values will only be correct if optimizer statistics were gathered recently and if they were gathered with the default value for the sample size.
Those may seem like large caveats but it's worth becoming familiar with your database's statistics gathering process anyway. If your database is not automatically gathering statistics or if your database is not using the default sample size those are likely huge problems you need to be aware of.
To manually gather stats for a specific table a statement like this will work:
begin
dbms_stats.gather_table_stats(user, 'TEST');
end;
/
select COUNT(1) TOTAL from table where COLUMN is NULL;

Oracle SQL, varchar2, like with number

Given a table with the NO_TELEPHONE column of data type VARCHAR2(20 BYTE) which contain either 10- or 4-digit phone numbers. I have to ensure the last 4 digit of every number are unique (so 0000001111 and 1111 could not both exist).
I have tried this:
SELECT * from table where NO_TELEPHONE like '%1111';`
But it found 0 result, I really don't understand why.
After some tries, I got results with:
SELECT * from table where NO_TELEPHONE like '1111%';
SELECT * from table where NO_TELEPHONE like '______1111%'; (there is 6 '_' )
If I have 0000001111 in table and I want insert 0000, so program will do:
SELECT * from table where NO_TELEPHONE like '0000%';
and it will match with 0000001111, is not the behavior I wanted
Try using RTRIM
SELECT * from table where RTRIM(NO_TELEPHONE, ' ' , chr(13), chr(10)) like '%1111';
If you only want to select the data you can use:
SELECT RTRIM( no_telephone, CHR(13)||CHR(10) ) AS no_telephone
FROM table_name
WHERE RTRIM( no_telephone, CHR(13)||CHR(10) ) LIKE '%1111';
However, if you want to correct the data::
UPDATE table_name
SET no_telephone = RTRIM( no_telephone, CHR(13)||CHR(10) )
WHERE SUBSTR( no_telephone, -2 ) = CHR(13)||CHR(10);
Or, if there are more issues and you want to replace all non-digits then:
UPDATE table_name
SET no_telephone = REGEXP_REPLACE( no_telephone, '\D+' );
Then you can enforce the uniqueness of the last 4 characters:
CREATE UNIQUE INDEX table_name__no_telephone__u
ON table_name ( SUBSTR( no_telephone, -4 ) );
and you can enforce the format of the column using:
ALTER TABLE table_name ADD CONSTRAINT table_name__no_telephone__chk
CHECK ( REGEXP_LIKE(no_telephone, '^\d{4}\d{6}?$' ) );

Transposing a table through select query

I have a table like:
Key type value
---------------------
40 A 12.34
41 A 10.24
41 B 12.89
I want it in the format:
Types 40 41 42 (keys)
---------------------------------
A 12.34 10.24 XXX
B YYY 12.89 ZZZ
How can this be done through an SQL query. Case statements, decode??
What you're looking for is called a "pivot" (see also "Pivoting Operations" in the Oracle Database Data Warehousing Guide):
SELECT *
FROM tbl
PIVOT(SUM(value) FOR Key IN (40, 41, 42))
It was added to Oracle in 11g. Note that you need to specify the result columns (the values from the unpivoted column that become the pivoted column names) in the pivot clause. Any columns not specified in the pivot are implicitly grouped by. If you have columns in the original table that you don't wish to group by, select from a view or subquery, rather than from the table.
You can engage in a bit of wizardry and get Oracle to create the statement for you, so that you don't need to figure out what column values to pivot on. In 11g, when you know the column values are numeric:
SELECT
'SELECT * FROM tbl PIVOT(SUM(value) FOR Key IN ('
|| LISTAGG(Key, ',') WITHIN GROUP (ORDER BY Key)
|| ');'
FROM tbl;
If the column values might not be numeric:
SELECT
'SELECT * FROM tbl PIVOT(SUM(value) FOR Key IN (\''
|| LISTAGG(Key, '\',\'') WITHIN GROUP (ORDER BY Key)
|| '\'));'
FROM tbl;
LISTAGG probably repeats duplicates (would someone test this?), in which case you'd need:
SELECT
'SELECT * FROM tbl PIVOT(SUM(value) FOR Key IN (\''
|| LISTAGG(Key, '\',\'') WITHIN GROUP (ORDER BY Key)
|| '\'));'
FROM (SELECT DISTINCT Key FROM tbl);
You could go further, defining a function that takes a table name, aggregate expression and pivot column name that returns a pivot statement by first producing then evaluating the above statement. You could then define a procedure that takes the same arguments and produces the pivoted result. I don't have access to Oracle 11g to test it, but I believe it would look something like:
CREATE PACKAGE dynamic_pivot AS
-- creates a PIVOT statement dynamically
FUNCTION pivot_stmt (tbl_name IN varchar2(30),
pivot_col IN varchar2(30),
aggr IN varchar2(40),
quote_values IN BOOLEAN DEFAULT TRUE)
RETURN varchar2(300);
PRAGMA RESTRICT_REFERENCES (pivot_stmt, WNDS, RNPS);
-- creates & executes a PIVOT
PROCEDURE pivot_table (tbl_name IN varchar2(30),
pivot_col IN varchar2(30),
aggr IN varchar2(40),
quote_values IN BOOLEAN DEFAULT TRUE);
END dynamic_pivot;
CREATE PACKAGE BODY dynamic_pivot AS
FUNCTION pivot_stmt (
tbl_name IN varchar2(30),
pivot_col IN varchar2(30),
aggr_expr IN varchar2(40),
quote_values IN BOOLEAN DEFAULT TRUE
) RETURN varchar2(300)
IS
stmt VARCHAR2(400);
quote VARCHAR2(2) DEFAULT '';
BEGIN
IF quote_values THEN
quote := '\\\'';
END IF;
-- "\||" shows that you are still in the dynamic statement string
-- The input fields aren't sanitized, so this is vulnerable to injection
EXECUTE IMMEDIATE 'SELECT \'SELECT * FROM ' || tbl_name
|| ' PIVOT(' || aggr_expr || ' FOR ' || pivot_col
|| ' IN (' || quote || '\' \|| LISTAGG(' || pivot_col
|| ', \'' || quote || ',' || quote
|| '\') WITHIN GROUP (ORDER BY ' || pivot_col || ') \|| \'' || quote
|| '));\' FROM (SELECT DISTINCT ' || pivot_col || ' FROM ' || tbl_name || ');'
INTO stmt;
RETURN stmt;
END pivot_stmt;
PROCEDURE pivot_table (tbl_name IN varchar2(30), pivot_col IN varchar2(30), aggr_expr IN varchar2(40), quote_values IN BOOLEAN DEFAULT TRUE) IS
BEGIN
EXECUTE IMMEDIATE pivot_stmt(tbl_name, pivot_col, aggr_expr, quote_values);
END pivot_table;
END dynamic_pivot;
Note: the length of the tbl_name, pivot_col and aggr_expr parameters comes from the maximum table and column name length. Note also that the function is vulnerable to SQL injection.
In pre-11g, you can apply MySQL pivot statement generation techniques (which produces the type of query others have posted, based on explicitly defining a separate column for each pivot value).
Pivot does simplify things greatly. Before 11g however, you need to do this manually.
select
type,
sum(case when key = 40 then value end) as val_40,
sum(case when key = 41 then value end) as val_41,
sum(case when key = 42 then value end) as val_42
from my_table
group by type;
Never tried it but it seems at least Oracle 11 has a PIVOT clause
If you do not have access to 11g, you can utilize a string aggregation and a grouping method to approx. what you are looking for such as
with data as(
SELECT 40 KEY , 'A' TYPE , 12.34 VALUE FROM DUAL UNION
SELECT 41 KEY , 'A' TYPE , 10.24 VALUE FROM DUAL UNION
SELECT 41 KEY , 'B' TYPE , 12.89 VALUE FROM DUAL
)
select
TYPE ,
wm_concat(KEY) KEY ,
wm_concat(VALUE) VALUE
from data
GROUP BY TYPE;
type KEY VALUE
------ ------- -----------
A 40,41 12.34,10.24
B 41 12.89
This is based on wm_concat as shown here: http://www.oracle-base.com/articles/misc/StringAggregationTechniques.php
I'm going to leave this here just in case it helps, but I think PIVOT or MikeyByCrikey's answers would best suit your needs after re-looking at your sample results.