I have a table where the column value I want to show as a new column in select. Next images show what I want.
I tried to use "pivot" but it not worked. Is it possible to do with pivot function?
My query:
select m.tmarti as line, dsmdcp as column, vlmdmn as valuee
from TABLE01 m
--pivot (count(vlmdmn for dsmdcp in ('Cintura', 'Quadril', 'Busto'))
join TABLE02 t on t.tmarti = m.tmarti
where cdarti = 2026397
order by t.seqtam, dsmdcp;
You can use Conditional Aggregation in such a way that to create a select statement string to pivot those columns dynamically within a stored function which returns a value in SYS_REFCURSOR type such as
CREATE OR REPLACE FUNCTION Get_Pivoted_Values RETURN SYS_REFCURSOR IS
v_recordset SYS_REFCURSOR;
v_sql VARCHAR2(32767);
v_cols VARCHAR2(32767);
BEGIN
SELECT LISTAGG( 'MAX( CASE WHEN column = '''||column||''' THEN valuee END )
AS "'||column||'"' , ',' ) WITHIN GROUP ( ORDER BY dsmdcp )
INTO v_cols
FROM ( SELECT DISTINCT dsmdcp
FROM table02 );
v_sql :=
'SELECT tmarti,'|| v_cols ||
' FROM
(
SELECT t1.tmarti AS line, dsmdcp AS column, vlmdmn AS valuee
FROM table01 t1
JOIN table02 t2
ON t2.tmarti = t1.tmarti
)
GROUP BY tmarti';
OPEN v_recordset FOR v_sql;
RETURN v_recordset;
END;
/
The function might be invoked from the SQL Developer's console as
SQL> DECLARE
result SYS_REFCURSOR;
BEGIN
:result := Get_Pivoted_Values;
END;
/
SQL> PRINT result;
Related
I want to convert a number of rows into columns
I found the following code
My question is, can I stored procedure output in a table?
from this link
https://asktom.oracle.com/pls/apex/f?p=100:11:::::P11_QUESTION_ID:4471013000346257238
the code
create table fish (
fish_id number,
fish_type varchar2(3),
fish_weight number);
insert into fish values (1,'COD',20);
insert into fish values(1,'HAD',30);
insert into fish values(2,'COD',45);
insert into fish values(2,'HKE',10);
insert into fish values(2,'LIN',55);
insert into fish values(3,'CTY',90);
insert into fish values (3,'HAD',60);
insert into fish values (3,'COD',52);
/
create or replace procedure go_fishing( p_cursor in out sys_refcursor )
as
l_query long := 'select fish_id';
begin
for x in (select distinct fish_type from fish order by 1 )
loop
l_query := l_query ||
replace( q'|, sum(decode(fish_type,'$X$',fish_weight)) $X$|',
'$X$',
dbms_assert.simple_sql_name(x.fish_type) );
end loop;
l_query := l_query || ' from fish group by fish_id order by fish_id';
open p_cursor for l_query;
end;
/
enter image description here
You can dynamically pivot the data by using SYS_REFCURSOR within a stored function (or procedure) such as
CREATE OR REPLACE FUNCTION Get_Fish_Weight_ByType RETURN SYS_REFCURSOR IS
v_recordset SYS_REFCURSOR;
v_sql VARCHAR2(32767);
v_cols VARCHAR2(32767);
BEGIN
SELECT LISTAGG( ''''||fish_type||''' AS "'||fish_type||'"' , ',' )
WITHIN GROUP ( ORDER BY fish_type )
INTO v_cols
FROM ( SELECT DISTINCT fish_type
FROM fish );
v_sql :='SELECT *
FROM fish e
PIVOT
(
MAX(fish_weight) FOR fish_type IN ( '|| v_cols ||' )
)';
OPEN v_recordset FOR v_sql;
DBMS_OUTPUT.PUT_LINE(v_sql);
RETURN v_recordset;
END;
/
which produces and runs the following Select Statement
SELECT *
FROM fish e
PIVOT
(
MAX(fish_weight) FOR fish_type IN ( 'COD' AS "COD",'CTY' AS "CTY",'HAD' AS "HAD",
'HKE' AS "HKE",'LIN' AS "LIN" )
)
Demo for the produced SQL statement
and then call the created function from SQL Developer's console as
SQL> DECLARE
res SYS_REFCURSOR;
BEGIN
:res := Get_Fish_Weight_ByType;
END;
/
SQL> PRINT res;
I have table as -
filename description value
-------- ------------------------------------------ --------------------
rec_123 PropertyWriteCount_rows_0_month February 2018
rec_123 rows_PropertyWriteCount_rows_0_description property write count
rec_123 PropertyWriteCount_rows_0_value_value 1234
rec_123 PropertyWriteCount_rows_0_value_baseType LONG
I want to convert it into -
filename month value description value_basetype
-------- ------------- ----- ------------------------- --------------
rec_2134 February 2018 1234 PropertyWriteCount_rows_0 long
I could able to write PL/SQL till this point, need help for next steps -
CREATE PROCEDURE rows_column IS
CURSOR get_rows IS
SELECT * FROM table1 where description LIKE 'PropertyWriteCount_rows%';
BEGIN
DBMS_OUTPUT.PUT_LINE('table info -');
END;
/
You can do this with conditional aggregation:
select
filename,
max(case when description = 'PropertyWriteCount_rows_0_month' then value end) as month,
max(case when description = 'rows_PropertyWriteCount_rows_0_description' then value end) as description,
max(case when description = 'PropertyWriteCount_rows_0_value_value' then value end) as value,
max(case when description = 'PropertyWriteCount_rows_0_value_baseType' then value end) as value_basetype
from table1
where description like '%PropertyWriteCount_rows%'
group by filename
You can use PIVOT clause after rendering description column in order to extract the last word after last underscore from that column.
The following query might be used as a static approach
SELECT *
FROM
(
SELECT REGEXP_SUBSTR(description,'[^_]+$') AS title,
filename, value
FROM table1 )
PIVOT
(
MAX(value) FOR title IN ('month' AS month, 'value' AS value,
'description' AS description, 'baseType' AS baseType)
)
Demo
but a dynamic approach should be preferred rather, through creating a stored function as in the following PL/SQL code
CREATE OR REPLACE FUNCTION Get_File_Values RETURN SYS_REFCURSOR IS
v_recordset SYS_REFCURSOR;
v_sql VARCHAR2(32767);
v_col VARCHAR2(32767);
BEGIN
SELECT LISTAGG( ''''||titles||''' AS '||titles , ',' )
WITHIN GROUP ( ORDER BY titles DESC )
INTO v_col
FROM ( SELECT DISTINCT REGEXP_SUBSTR(description,'[^_]+$') AS titles
FROM table1 );
v_sql :=
'SELECT *
FROM (
SELECT REGEXP_SUBSTR(description,''[^_]+$'') AS title,
filename, value
FROM table1
)
PIVOT
(
MAX(value) FOR title IN ( '|| v_col ||' )
)';
OPEN v_recordset FOR v_sql;
DBMS_OUTPUT.PUT_LINE(v_sql);
RETURN v_recordset;
END;
/
Then running the below code :
VAR rc REFCURSOR
EXEC :rc := Get_File_Values;
PRINT rc
from SQL Developer's Command Line
or
BEGIN
:result := Get_File_Values;
END;
from Test window of PL/SQL Developer (as tagged)
in order to see the expected result set varying even if the new values for description column are inserted or some former values are deleted.
I need to verify converted data, distinct values and records counts. I would like to write statements so that I can enter a table name, then retrieve it's columns and use them in a query to get its distinct values (the actual values, not just a count of how many distinct) and their count.
I think I need to a CURSOR or CURSOR FOR LOOP and create something like this:
declare
cursor field_name
is
select COLUMN_NAME
from user_tab_cols
where table_name='TABLE1'
c_field_name field_name%ROWTYPE;
BEGIN
OPEN field_name
loop
fetch field_name INTO c_field_name;
exit when field_name%NOTFOUND;
end loop;
CLOSE field_name;
end;
Then run a query using that above in something like
select field_name, count(*)
from table1
group by field_name
Do I need to create 2 loop statements? I've not yet created one and can't quite get the context to get my results so far.
BEGIN
FOR myrow in (select field_name, count(*) as "count" from table1 group by field_name)
loop
dbms_output.put_line(myrow.field_name);
dbms_output.put_line(myrow.count);
end loop;
end;
Considering you will be giving the table name as parameter below code will print all the values of all the columns one by one along with the count of the values
create or replace PROCEDURE PR_PREP(
P_TABLE_NAME IN VARCHAR2)
IS
CURSOR CUR_COLUMNS (PA_TABLE_NAME VARCHAR2)
IS
SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = PA_TABLE_NAME;
COL_NAMES CUR_COLUMNS%ROWTYPE;
TYPE TYP_RECORD
IS
RECORD
(
FIELD_NAME VARCHAR2(255),
CNT INT);
TYPE TYP_OP_TABLE
IS
TABLE OF TYP_RECORD;
OP_TABLE TYP_OP_TABLE;
I INT;
V_SQL VARCHAR2(2000);
BEGIN
FOR COL_NAMES IN CUR_COLUMNS(P_TABLE_NAME)
LOOP
V_SQL := 'SELECT ' || COL_NAMES.COLUMN_NAME || ' AS FIELD_NAME ,
COUNT(*) AS CNT FROM ' ||
P_TABLE_NAME || ' GROUP BY ' || COL_NAMES.COLUMN_NAME ;
-- DBMS_OUTPUT.PUT_LINE (V_SQL);
EXECUTE IMMEDIATE V_SQL BULK COLLECT INTO OP_TABLE;
dbms_output.put_line('columna name = ' ||COL_NAMES.COLUMN_NAME);
FOR I IN OP_TABLE.FIRST .. OP_TABLE.LAST
LOOP
DBMS_OUTPUT.PUT_LINE('FIELD VALUE '||OP_TABLE(I).FIELD_NAME || ' COUNT = ' || OP_TABLE(I).CNT);
END LOOP;
DBMS_OUTPUT.PUT_LINE('ONE FILED ENDED , NEXT STARTED');
END LOOP;
END;
I have table with 100 columns with not correlated names (ABC1, DA23, EE123 - there is no common pattern there).
I want to iterate through every row and every column in this table.
My current script:
BEGIN
FOR single_row IN (
SELECT *
FROM MY_TABLE)
LOOP
--iterate through columns of 'single_row'
--for each nullable column do insert with real current column name and column value)
--I assume each column is nullable except of ID
INSERT INTO ANOTHER_TABLE VALUES (single_row.id, column_name, column_value);
END LOOP;
END;
So for example, if MY_TABLE contains 2 rows:
ID|ABC1|DA23|EE123|...
1|123|456|789|...
2|321|654|987|...
After running my script, my ANOTHER_TABLE will contain:
MY_TABLE_ID|COLUMN_NAME|COLUMN_VALUE
1|ABC1|123
1|DA23|456
1|EE123|789
... other columns from row 1
2|ABC1|321
2|DA23|654
2|EE123|987
... other columns from row 2
How I can do this?
I'm using Oracle 11g
EDIT
#vkp provided great solution, but there is one more thing to solve. I don't want to specify all columns in in clause. I would love to use some kind of query there or * or anything else, just to not be forced to list all of them.
I have tried something like this:
select *
from MY_TABLE t
unpivot (
column_value for column_name in (select column_name
from user_tab_columns
where table_name = 'MY_TABLE'
and nullable = 'Y')
) u
but it returns error:
ORA-00904: : invalid identifier
00904. 00000 - "%s: invalid identifier"
This is an application of unpivot.
select *
from my_table m
unpivot (column_value for column_name in (ABC1,DA23,EE123)) u
null values for any of the columns for an id won't be shown in the result.
If you have to include null values in the output, use the option INCLUDE NULLS.
select *
from my_table m
unpivot include nulls (column_value for column_name in (ABC1,DA23,EE123)) u
Edit: To include column names dynamically, use
DECLARE
sql_stmt VARCHAR2(4000);
var_columns VARCHAR2(4000); --use clob datatype if the column names can't fit in with this datatype
BEGIN
SELECT LISTAGG(column_name,',') WITHIN GROUP(ORDER BY column_name)
INTO var_columns
FROM user_tab_columns
WHERE table_name='MY_TABLE' AND column_name<>'ID';
sql_stmt:='select * from my_table m
unpivot
(column_value for column_name in (' || var_columns || ')) u';
EXECUTE IMMEDIATE sql_stmt;
END;
/
First option. With dynamic sql.
declare
v_ctx number;
v_query varchar2(500);
v_total NUMBER;
v_desctab DBMS_SQL.DESC_TAB;
v_column_cnt NUMBER;
v_value varchar2(32767);
v_result clob := '';
v_rownum number := 0;
begin
v_ctx := dbms_sql.open_cursor;
v_query := 'select * from user_objects where rownum < 100';
dbms_sql.parse(v_ctx,v_query,dbms_sql.v7);
v_total := dbms_sql.execute(v_ctx);
DBMS_SQL.DESCRIBE_COLUMNS(v_ctx, v_column_cnt, v_desctab);
for i in 1 .. v_column_cnt loop
dbms_sql.define_column(v_ctx, i, v_value /* data_type varchar2*/, 32767 /* max_length*/);
end loop;
loop
exit when dbms_sql.fetch_rows(v_ctx) = 0;
v_rownum := v_rownum +1;
for i in 1 .. v_column_cnt loop
dbms_sql.column_value(v_ctx, i, v_value);
dbms_output.put_line(v_rownum||' - '||v_desctab(i).col_name||' - '||v_value);
end loop;
end loop;
dbms_sql.close_cursor(v_ctx);
exception
when others then
dbms_sql.close_cursor(v_ctx);
raise;
end;
/
2nd option with xquery.
select t1.id,t2.* from xmltable('for $i in ora:view("<you_table_here>")/ROW
return $i'
columns id FOR ORDINALITY
, row_value xmltype path'.'
) t1
,xmltable('for $i in $row_value/ROW/* return $i'
passing t1.row_value as "row_value"
columns col_index for ORDINALITY ,
column_name varchar2(100) path 'name()',
column_value varchar2(100) path 'text()'
) t2
Here is a simple solution using REF CURSOR.
I've tried this code and it's working at my end.
DECLARE
query_2 VARCHAR2(1000);
TYPE icur IS REF CURSOR;
ic icur;
col_val VARCHAR2(100);
BEGIN
FOR j IN
(SELECT * FROM user_tab_cols WHERE table_name = UPPER('MY_TABLE'))
LOOP
dbms_output.put_line(j.column_name);
query_2 := 'SELECT ' || j.column_name|| ' FROM MY_TABLE';
OPEN ic FOR query_2;
LOOP
FETCH ic INTO col_val;
EXIT WHEN ic%NOTFOUND;
INSERT INTO ANOTHER_TABLE VALUES( j.column_name, col_val);
END LOOP;
END LOOP;
END;
/
I would love to be able to use the system tables (Oracle in this case) to drive which fields are used in a SELECT statement. Something like:
SELECT
(
select column_name
from all_tab_cols
where table_Name='CLARITY_SER'
AND OWNER='CLARITY'
AND data_type='DATE'
)
FROM CLARITY_SER
This syntax doesn't work, as the subquery returns multiple rows, instead of one row with multiple columns.
Is it possible to generate a SQL statement dynamically by querying the table schema information in order to select only certain columns?
** edit **
Do this without using a function or procedure, if possible.
You can do this:
declare
l_sql varchar2(32767);
rc sys_refcursor;
begin
l_sql := 'select ';
for r in
( select column_name
from all_tab_cols
where table_Name='CLARITY_SER'
AND OWNER='CLARITY'
AND data_type='DATE'
)
loop
l_sql := l_sql || r.column_name || ',';
end loop;
l_sql := rtrim(l_sql,',') || ' from clarity_ser';
open rc for l_sql;
...
end;
No, it's not possible to specify a column list dynamically in SQL. You'll need to use a procedural language to run the first query, use that to construct a second query, then run the second query.
You could use dynamic SQL. Create a function that takes the table name, owner, data type, executes the inner query and returns a comma-separated list of column names, or an array table if you prefer. Then construct the outer query and execute it with execute immediate.
CREATE FUNCTION get_column_list(
table_name IN varchar2,
owner_name IN varchar2,
data_type IN varchar2)
RETURN varchar2
IS
BEGIN
...... (get columns and return comma-separated list)
END;
/
If your function returns a comma-separated list you can inline it:
execute immediate 'select ' || get_column_list(table_name, owner_name, datatype) || ' from ' || table_name
Admittedly it's a long time since I played with oracle so I may be a bit off but I'm pretty sure this is quite doable.
In SQLPlus you could do this:
COLUMN cols NEW_VALUE cols
SELECT max( ltrim( sys_connect_by_path( column_name, ',' ), ',' ) ) cols
FROM
(
select rownum rn, column_name
from all_tab_cols
where table_Name='CLARITY_SER'
and OWNER='CLARITY'
AND data_type='DATE'
)
start with rn = 1 connect by rn = prior rn +1
;
select &cols from clarity.clarity_ser;