Say I have several tables that all start with 'PLAYER_' and I am trying to loop through all of those tables to get tables names and then loop again to get a value of a column in all of these tables.
This column exists in all tables so I want to use nested FOR loops to achieve that.
Here is what I have so far but it does not seem to work:
DECLARE
LOG_ID NUMBER;
TBL_NME VARCHAR2(30);
V_STRNG VARCHAR2(4000);
BEGIN
FOR i IN (SELECT TABLE_NAME FROM USER_TABLES WHERE TABLE_NAME LIKE 'PLAYER_%') LOOP
TBL_NME := i.TABLE_NAME;
DBMS_OUTPUT.PUT_LINE('TABLE EXTRACTED IS ' || TBL_NME);
FOR j IN(SELECT LOG_ID FROM i.TABLE_NAME) LOOP
V_EXEC_OBJ_STRNG := 'SELECT LOG_ID FROM ' || i.TABLE_NAME;
EXECUTE IMMEDIATE V_STRNG INTO LOG_ID;
DBMS_OUTPUT.PUT_LINE('LOG_ID IS ' || LOG_ID || ' FOR TABLE ' || i.TABLE_NAME);
END LOOP;
END LOOP;
END;
/
You can probably get away with just one loop ...
Example
create table player_01 ( id, name )
as
select level, dbms_random.string( 'x', 25 )
from dual
connect by level <= 10 ;
create table player_02 ( id, name )
as
select level, dbms_random.string( 'x', 25 )
from dual
connect by level <= 11 ;
create table player_03 ( id, name )
as
select level, dbms_random.string( 'x', 25 )
from dual
connect by level <= 12 ;
Anonymous block:
-- find all relevant tables and retrieve the highest id values
declare
logid number := 0 ;
tablename varchar2( 30 ) := '' ;
v_string varchar2( 4000 ) := '' ;
begin
for r in (
select table_name from user_tables
where table_name like 'PLAYER%'
order by table_name
) loop
-- dbms_output.put_line( ' current table -> ' || r.table_name ) ;
v_string := 'select max( id ) as logid from ' || r.table_name;
execute immediate v_string into logid ;
dbms_output.put_line( 'log id is ' || logid || ' for table ' || r.table_name ) ;
end loop ;
end ;
/
-- result
log id is 10 for table PLAYER_01
log id is 11 for table PLAYER_02
log id is 12 for table PLAYER_03
Dbfiddle here.
According to your comment, there are several LOGIDs in each PLAYER_ table. Maybe the following example is closer to the "real thing". (And: the anonymous block has nested loops ... ( tested with Oracle 12c and 11g, dbfiddle here ).
Tables
create table player_01 ( id, details, logid )
as
select level, dbms_random.string( 'x', 25 ), abs( dbms_random.random() )
from dual
connect by level <= 3 ;
create table player_02 ( id, details, logid )
as
select level, dbms_random.string( 'x', 25 ), abs( dbms_random.random() )
from dual
connect by level <= 4 ;
create table player_03 ( id, details, logid )
as
select level, dbms_random.string( 'x', 25 ), abs( dbms_random.random() )
from dual
connect by level <= 4 ;
Sample data in PLAYER_01 / PLAYER_02 / PLAYER_03
select * from player_01 ;
ID DETAILS LOGID
1 VZAQXPFCQK3U2F0RL32I31N40 699945134
2 32QWFFMUCF1DL6E3Z5QM4DSWY 1635628934
3 48GWBETOLUSDEFA3SMY061NUO 1237793316
select * from player_02;
ID DETAILS LOGID
1 HS827U4VCY853N8DKTI98J82D 1993524164
2 XLYS0XPJG0IQP4BNKDQ0ZITPA 1665941353
3 DWVVR5O6N5T1HP5MDYHVH3NZJ 1129581845
4 L7N8HCPVTHP466WJ5TCQ04YHE 794237444
select * from player_03;
ID DETAILS LOGID
1 SYVX5G2FE5IC1MI6TCSAHNOUU 720476135
2 4IQZIG6DAUCWW3APJY5OZ63TF 287457960
3 525NMZFVGLWKIT7EIFA41C8MB 784891618
4 0XHJXV2O4TCQQSITOTIQCO3AA 1578737054
Anonymous block
declare
logid number := 0 ;
tablename varchar2( 30 ) := '' ;
v_string1 varchar2( 4000 ) := '' ;
v_string2 varchar2( 4000 ) := '' ;
rowcount number := 0 ;
begin
for r in (
select table_name from user_tables
where table_name like 'PLAYER%'
order by table_name
) loop
v_string1 := 'select count(*) from ' || r.table_name ;
execute immediate v_string1 into rowcount ;
dbms_output.put_line( rowcount ) ;
for rn in 1 .. rowcount
loop
-- dbms_output.put_line( rn ) ;
v_string2 := 'select logid from ( '
|| 'select logid, row_number() over ( order by id ) rn '
|| ' from ' || r.table_name || ' )'
|| ' where rn = ' || rn;
-- dbms_output.put_line( v_string2 ) ;
execute immediate v_string2 into logid ;
dbms_output.put_line( 'log id is ' || logid || ' for table ' || r.table_name ) ;
end loop ;
end loop ;
end ;
/
dbms_output:
3
log id is 699945134 for table PLAYER_01
log id is 1635628934 for table PLAYER_01
log id is 1237793316 for table PLAYER_01
4
log id is 1993524164 for table PLAYER_02
log id is 1665941353 for table PLAYER_02
log id is 1129581845 for table PLAYER_02
log id is 794237444 for table PLAYER_02
4
log id is 720476135 for table PLAYER_03
log id is 287457960 for table PLAYER_03
log id is 784891618 for table PLAYER_03
log id is 1578737054 for table PLAYER_03
The second query string (v_string2) looks a bit like this (maybe a bit easier to read than all the string parts and ||):
select logid
from (
select
logid
, row_number() over ( order by id ) rn
from player_01
) where rn = 1
;
-- query result
LOGID
1338793259
Query in the inner loop
(answering the question in your comment)
The subquery uses row_number() - see documentation:
"ROW_NUMBER is an analytic function. It assigns a unique number to
each row to which it is applied (either each row in the partition or
each row returned by the query), in the ordered sequence of rows
specified in the order_by_clause, beginning with 1."
We are using this to get consecutive numbers, numbering the LOGIDs as it were. Then, we use the RN values in the WHERE clause (of the outer select), and compare them to the inner FOR loop's "rn" value.
select
logid
, row_number() over ( order by id ) rn
from player_01 ;
-- result
LOGID RN
1775991812 1
262095022 2
2090118607 3
Related
I would like to replace every string are null by 'n' and every number by 0.
Is there a way to do that?
With a polymorphic table function I can select all the columns of a certain type but I can't modify the value of the columns.
Yes, it is possible with PTF also. You may modify columns in case you've set pass_through to false for the column in the describe method (to drop it) and copy it into new_columns parameter of the describe.
Below is the code:
create package pkg_nvl as
/*Package to implement PTF*/
function describe(
tab in out dbms_tf.table_t
) return dbms_tf.describe_t
;
procedure fetch_rows;
end pkg_nvl;
/
create package body pkg_nvl as
function describe(
tab in out dbms_tf.table_t
) return dbms_tf.describe_t
as
modif_cols dbms_tf.columns_new_t;
new_col_cnt pls_integer := 0;
begin
/*Mark input columns as used and as modifiable for subsequent row processing*/
for i in 1..tab.column.count loop
if tab.column(i).description.type in (
dbms_tf.type_number,
dbms_tf.type_varchar2
) then
/*Modifiable*/
tab.column(i).pass_through := FALSE;
/*Used in the PTF context*/
tab.column(i).for_read := TRUE;
/* Propagate column to the modified*/
modif_cols(new_col_cnt) := tab.column(i).description;
new_col_cnt := new_col_cnt + 1;
end if;
end loop;
/*Return the list of modified cols*/
return dbms_tf.describe_t(
new_columns => modif_cols
);
end;
procedure fetch_rows
/*Process rowset and replace nulls*/
as
rowset dbms_tf.row_set_t;
num_rows pls_integer;
in_col_vc2 dbms_tf.tab_varchar2_t;
in_col_num dbms_tf.tab_number_t;
new_col_vc2 dbms_tf.tab_varchar2_t;
new_col_num dbms_tf.tab_number_t;
begin
/*Get rows*/
dbms_tf.get_row_set(
rowset => rowset,
row_count => num_rows
);
for col_num in 1..rowset.count() loop
/*Loop through the columns*/
for rn in 1..num_rows loop
/*Calculate new values in the same row*/
/*Get column by index and nvl the value for return column*/
if rowset(col_num).description.type = dbms_tf.type_number then
dbms_tf.get_col(
columnid => col_num,
collection => in_col_num
);
new_col_num(rn) := nvl(in_col_num(rn), 0);
elsif rowset(col_num).description.type = dbms_tf.type_varchar2 then
dbms_tf.get_col(
columnid => col_num,
collection => in_col_vc2
);
new_col_vc2(rn) := nvl(in_col_vc2(rn), 'n');
end if;
end loop;
/*Put the modified column to the result*/
if rowset(col_num).description.type = dbms_tf.type_number then
dbms_tf.put_col(
columnid => col_num,
collection => new_col_num
);
elsif rowset(col_num).description.type = dbms_tf.type_varchar2 then
dbms_tf.put_col(
columnid => col_num,
collection => new_col_vc2
);
end if;
end loop;
end;
end pkg_nvl;
/
create function f_replace_nulls(tab in table)
/*Function to replace nulls using PTF*/
return table pipelined
row polymorphic using pkg_nvl;
/
with a as (
select
1 as id, 'q' as val_vc2, 1 as val_num
from dual
union all
select
2 as id, '' as val_vc2, null as val_num
from dual
union all
select
3 as id, ' ' as val_vc2, 0 as val_num
from dual
)
select
id
, a.val_num
, a.val_vc2
, n.val_num as val_num_repl
, n.val_vc2 as val_vc2_repl
from a
join f_replace_nulls(a) n
using(id)
ID | VAL_NUM | VAL_VC2 | VAL_NUM_REPL | VAL_VC2_REPL
-: | ------: | :------ | -----------: | :-----------
3 | 0 | | 0 |
2 | null | null | 0 | n
1 | 1 | q | 1 | q
db<>fiddle here
Use COALESCE or NVL and list the columns you want to apply them to:
SELECT COALESCE(col1, 'n') AS col1,
COALESCE(col2, 0) AS col2,
COALESCE(col3, 'n') AS col3
FROM table_name;
The way I understood the question, you actually want to modify table's contents. If that's so, you'll need dynamic SQL.
Here's an example; sample data first, with some numeric and character columns having NULL values:
SQL> desc test
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NUMBER
NAME VARCHAR2(20)
SALARY NUMBER
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 200
3 Foot 0
4 0
Procedure reads USER_TAB_COLUMNS, checking desired data types (you can add some more, if you want), composes the update statement and executes it:
SQL> declare
2 l_str varchar2(1000);
3 begin
4 for cur_r in (select column_name, data_type
5 from user_tab_columns
6 where table_name = 'TEST'
7 and data_type in ('CHAR', 'VARCHAR2')
8 )
9 loop
10 l_str := 'update test set ' ||
11 cur_r.column_name || ' = nvl(' || cur_r.column_name ||', ''n'')';
12 execute immediate l_str;
13 end loop;
14
15 --
16
17 for cur_r in (select column_name, data_type
18 from user_tab_columns
19 where table_name = 'TEST'
20 and data_type in ('NUMBER')
21 )
22 loop
23 l_str := 'update test set ' ||
24 cur_r.column_name || ' = nvl(' || cur_r.column_name ||', 0)';
25 execute immediate l_str;
26 end loop;
27 end;
28 /
PL/SQL procedure successfully completed.
Result:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 n 200
3 Foot 0
4 n 0
SQL>
You can create a table macro to intercept the columns and replace them with nvl if they're a number or varchar2:
create or replace function replace_nulls (
tab dbms_tf.table_t
)
return clob sql_macro as
stmt clob := 'select ';
begin
for i in 1..tab.column.count loop
if tab.column(i).description.type = dbms_tf.type_number
then
stmt := stmt || ' nvl ( ' ||
tab.column(i).description.name || ', 0 ) ' ||
tab.column(i).description.name || ',';
elsif tab.column(i).description.type = dbms_tf.type_varchar2
then
stmt := stmt || ' nvl ( ' ||
tab.column(i).description.name || ', ''n'' ) ' ||
tab.column(i).description.name || ',';
else
stmt := stmt || tab.column(i).description.name || ',';
end if;
end loop;
stmt := rtrim ( stmt, ',' ) || ' from tab';
return stmt;
end;
/
with a (
aa1,aa2,aa3
) as (
select 1, '2hhhh', sysdate from dual
union all
select null, null, null from dual
)
select * from replace_nulls(a);
AA1 AA2 AA3
---------- ----- -----------------
1 2hhhh 27-JUN-2022 13:17
0 n <null>
I am looking for a way to create collection that would have one additional column that the source table/view columns referred by %ROWTYPE for simplicity.
TYPE my_type IS RECORD ( mode varchar2(10), some_table%ROWTYPE );
Goal:
I want to compare a table and its source view with a two-way MINUS operation and read source view once:
select * from (
select 'INSERT' as mode, a.* from (
select ... from aView
MINUS
select ... from aTable ) a
UNION ALL
select 'DELETE' as mode, b.* from (
select ... from aTable
MINUS
select ... from aView) b
)
I am starting to think of creating global temporary tables (GTTs) for INSERT and DELETE mode performed with INSERT ALL, but I have 25 tables, so that will add 50 GTTs :-/
INSERT ALL
WHEN mode=INSERT THEN INTO inserts_GTT01_tbl VALUES(...)
WHEN mode=DELETE THEN INTO deletes_GTT01_tbl VALUES(...)
SELECT * FROM two-way-minus-view
I hope I understood correctly your predicament, although it is possible I might miss something. If I got your idea correctly, you want to operate over a set of tables and generate the records to delete and the ones to insert based on the minus-two-way.
I would go for global temporary tables, using commit on preserve rows, but I would centralize the code in a procedure
First a control table
create table control_process
( id_time timestamp default systimestamp ,
source_table varchar2(128),
source_view varchar2(128),
rows_processed number,
exit_code number,
error_message varchar2(400)
);
Procedure for GTTs using input parameters table and view
It is very important that you adapt the code below to your needs:
If the columns in the table and view are not the same, you must adapt the query behind the GTT creation to use whatever is necessary. If you want to do it dynamically, you must create another cursor to match the columns between the table and view used as input parameter.
In order to delete/insert you have to compare the GTT with the original table. That is why I have the cursor with the columns that I use to create the dynamic construction, both for insert and delete.
I did not have a place to verify the code, so please be aware that could be typos or errors on it.
The control table is optional, but allows you to have a kind of place where you stored the results for each table/view
This solution allows you to use it for all the tables and views in one single place.
the materialize hint will help you as you read once the view and the table in the construction of the minus-two-way.
Having said that, that could be an approach ( you have to adapt it to your needs )
create or replace procedure pr_generate_rows ( psourcetable in varchar2 , psourceview in varchar2 )
is
vddl clob;
vdml clob;
vsql clob;
vcode pls_integer;
verrm varchar2(300);
out_string varchar2(128);
cursor c_tab_columns
is
select column_name, count(*) over () tot_rows
from all_tab_columns where table_name = psourcetable and owner = 'MY_SCHEMA'
order by column_id;
begin
vddl := ' create global temporary table gtt_'||psourcetable||' on commit preserve rows
as
with x
as
( select /*+materialize */ * from '||psourceview||'
),
y as
( select /*+materialize */ * from '||psourcetable||'
)
select * from (
select ''INSERT'' as mode, a.* from
(
select ... from x
MINUS
select ... from y
) a
UNION ALL
select ''DELETE'' as mode, b.* from (
select ... from y
MINUS
select ... from x ) b
)
' ;
execute immediate vddl;
vdml := ' insert into control_process ( source_table, source_view , rows_processed , exit_code )
select '''||psourcetable||''' as source_table ,
'''||psourceview||''' as source_view ,
( select count(*) from gtt_'||psourcetable||' ) as rows_processed ,
0 as exit_code from dual
' ;
execute immediate vdml ;
-- Perform Insert and Delete over final table
-- Delete
vsql := ' delete from '||psourcetable||'
where exists
( select 1 from gtt_'||psourcetable||' a join '||psourcetable||' b
on ( ';
for item in c_tab_columns
loop
out_string := item.COLUMN_NAME;
if c_tab_columns%rowcount = 1
then
vexpression := ' a.'||out_string||' = b.'||out_string||' and ' ;
dbms_lob.append(vsql,vexpression);
dbms_lob.append(vsql,''||chr(10)||'');
elsif c_tab_columns%rowcount < item.tot_rows then
vexpression := ' a.'||out_string||' = b.'||out_string||' and ' ;
dbms_lob.append(vsql,vexpression);
dbms_lob.append(vsql,''||chr(10)||'');
else
vexpression := ' a.'||out_string||' = b.'||out_string||' ' ;
dbms_lob.append(vsql,vexpression);
dbms_lob.append(vsql,''||chr(10)||'');
end if;
end loop;
dbms_lob.append ( vsql, ')' );
dbms_lob.append(vsql,''||chr(10)||'');
dbms_lob.append ( vsql, ' where a.mode = ''DELETE'' ');
dbms_lob.append ( vsql, ' ) ');
execute immediate vsql;
-- Insert
vsql := ' insert /*+ append */ into '||psourcetable||' a
select ' ;
for item in c_tab_columns
loop
out_string := item.COLUMN_NAME;
if c_tab_columns%rowcount = 1
then
vexpression := ' b.'||out_string||' , ' ;
dbms_lob.append(vsql,vexpression);
dbms_lob.append(vsql,''||chr(10)||'');
elsif c_tab_columns%rowcount < item.tot_rows then
vexpression := ' b.'||out_string||' , ' ;
dbms_lob.append(vsql,vexpression);
dbms_lob.append(vsql,''||chr(10)||'');
else
vexpression := ' b.'||out_string||' ' ;
dbms_lob.append(vsql,vexpression);
dbms_lob.append(vsql,''||chr(10)||'');
end if;
end loop;
dbms_lob.append(vsql,'from gtt_'||psourcetable||' b where b.mode = ''INSERT'' ');
execute immediate vsql;
exception
when others then
vcode := sqlcode;
verrm := substr(sqlerrm, 1, 300);
vdml := ' insert into control_process ( source_table, source_view , rows_processed , exit_code , error_message )
values (
'''||psourcetable||''' ,
'''||psourceview||''' ,
0,
vcode,
'''||verrm||'''
) ' ;
execute immediate vdml;
commit;
raise;
end;
/
Any questions, let me know.
I am stucked with an issue.
I have a table called gd_table_order which contains tablenames.
And i need to get the table_name and count of each table to a target one.
target is a view.
I only gave an example with 2 columns, there are 10 columns like this.So in the procedure we need to enter this as a parameter.
And there will be corresponsding 10 views to be generate. below is a sample of 2 views.
V_CHECK_RECORDS_* is the output view name
Could you please help?
I am not sure what is exact requirement here, But you can use the following approach to fetch the number of records from each table.
SQL> -- This is sample data
SQL> WITH SAMPLE_DATA(TNAME) AS
2 (SELECT 'CUSTOMERS' FROM DUAL UNION ALL
3 SELECT 'INTERVAL_TAB' FROM DUAL)
4 -- Your query starts from here
5 SELECT TABLE_NAME,
6 TO_NUMBER(
7 EXTRACTVALUE( XMLTYPE(
8 DBMS_XMLGEN.GETXML('select count(*) c from ' || U.TABLE_NAME)
9 ), '/ROWSET/ROW/C')) COUNT
10 FROM USER_TABLES U JOIN SAMPLE_DATA S ON S.TNAME = U.TABLE_NAME;
TABLE_NAME COUNT
--------------- ----------
CUSTOMERS 1
INTERVAL_TAB 0
SQL>
-- Update
You can generate the view as follows :
-- UPDATED THIS SECTION
CREATE OR REPLACE VIEW V_CHECK_RECORDS_AUS AS
SELECT TABLE_NAME,
TO_NUMBER(
EXTRACTVALUE( XMLTYPE(
DBMS_XMLGEN.GETXML('select count(*) c from '
|| U.TABLE_NAME || ' WHERE oe_name=''BUL''')
), '/ROWSET/ROW/C')) NUM_ROWS
FROM USER_TAB_COLUMNS U JOIN GD_TABLE_ORDER S ON S.TABLE_NAME_AUS = U.TABLE_NAME
WHERE U.COLUMN_NAME = 'OE_NAME';
In the same way you can generate other views.
-- Further Update
CREATE OR REPLACE VIEW V_CHECK_RECORDS_AUS AS
SELECT TABLE_NAME,
CASE WHEN U.COLUMN_NAME IS NOT NULL THEN TO_NUMBER(
EXTRACTVALUE( XMLTYPE(
DBMS_XMLGEN.GETXML('select count(*) c from '
|| U.TABLE_NAME || ' WHERE ' || U.COLUMN_NAME || '=''BUL''')
), '/ROWSET/ROW/C'))
ELSE 0 END NUM_ROWS
FROM GD_TABLE_ORDER S LEFT JOIN USER_TAB_COLUMNS U
ON S.TABLE_NAME_AUS = U.TABLE_NAME AND U.COLUMN_NAME = 'OE_NAME';
I am having 20 tables ( Each table has a PK and data ), i want to find out what is the current MAX(PK) Value for each table.
I Want the result as follows :
TABLE_NAME MAX_VAL
-------------------- ----------
TABELE_A 114
TABELE_B 55
TABELE_C 14
TABELE_D 866
TABELE_3 4552
is there any way to accomplish this or else i have to write 20 times SELECT MAX(PK_COL) FROM TABLE ?
Assuming your currently connected schema is composed of those twenty tables, and each have identical primary key column name(pk_col), then consider the following code block containing an implicit cursor :
declare
v_max pls_integer;
begin
dbms_output.put_line('table_name max_val');
for c in ( select * from user_tables )
loop
execute immediate 'select max(pk_col) from '||c.table_name into v_max;
dbms_output.put_line(c.table_name||' '||v_max);
end loop;
end;
/
i have found another method which will bring TABLE_NAME,PK_COLUMN and MAX( PK_COLUMN ).
SELECT CASE
WHEN RN = 1 THEN
FORMATTED_QUERY_SET
ELSE
FORMATTED_QUERY_SET || ' UNION ALL '
END AS FORMATTED_QUERY_SET
FROM (SELECT ' SELECT NVL(MAX( ' || COL.COLUMN_NAME ||
' ),0) CURR_MAX_VAL, ''' || TAB.TABLE_NAME ||
''' TABLE_NAME,''' || COL.COLUMN_NAME ||
''' COLUMN_NAME FROM ' || TAB.TABLE_NAME AS FORMATTED_QUERY_SET,
TAB.TABLE_NAME,
ROW_NUMBER() OVER(ORDER BY TAB.TABLE_NAME DESC) AS RN
FROM USER_CONSTRAINTS TAB
JOIN USER_CONS_COLUMNS COL
ON TAB.TABLE_NAME = COL.TABLE_NAME
JOIN USER_TAB_COLUMNS COL2
ON COL.COLUMN_NAME = COL2.COLUMN_NAME
AND COL.TABLE_NAME = COL2.TABLE_NAME
WHERE TAB.CONSTRAINT_TYPE = 'P'
AND COL.CONSTRAINT_NAME LIKE '%_PK'
AND REGEXP_LIKE(COL2.DATA_TYPE, ('NUMB|INTE')))
ORDER BY TABLE_NAME;
Copy the output returned by the above query and execute.
Note : Remove the last ' UNION ALL ' operator from the query string.
Note : Please correct me if i am doing anything wrong .
I'm trying to create pivot clause that gets it parameters from variable.
I have some test code like:
SELECT * FROM (
SELECT U.USER_ID, U.USER_NAME, E.EXAM_ID, EU.EXAM_DATE
FROM USERS U, EXAMS E, EXAM_USER EU
WHERE U.USER_ID = EU.USER_ID(+)
AND E.EXAM_ID(+) = EU.EXAM_ID
ORDER BY U.USER_ID
)
PIVOT (MAX(EXAM_DATE) FOR EXAM_ID IN ('3' AS "exam 3",'2' AS "exam 2",'1' AS "exam 1"))
order by 1
;
This works just fine. Then I declared the variable EXAM_IDS like:
DECLARE
EXAM_IDS VARCHAR2 (255);
BEGIN
SELECT LISTAGG('''' || EXAM_ID || ''' AS "' || EXAM_NAME || '"', ',')
WITHIN GROUP (ORDER BY EXAM_ID DESC)
INTO EXAM_IDS
FROM EXAMS;
END;
I'm pretty sure the variable EXAM_IDS has now a string as used in pivot clause(?) but I don know how to combine these two:
DECLARE
EXAM_IDS VARCHAR2 (255);
BEGIN
SELECT LISTAGG('''' || EXAM_ID || ''' AS "' || EXAM_NAME || '"', ',')
WITHIN GROUP (ORDER BY EXAM_ID DESC)
INTO EXAM_IDS
FROM EXAMS;
END;
SELECT * FROM (
SELECT U.USER_ID, U.USER_NAME, E.EXAM_ID, EU.EXAM_DATE
FROM USERS U, EXAMS E, EXAM_USER EU
WHERE U.USER_ID = EU.USER_ID(+)
AND E.EXAM_ID(+) = EU.EXAM_ID
ORDER BY U.USER_ID
)
PIVOT (MAX(EXAM_DATE) FOR EXAM_ID IN (' || EXAM_IDS || '))
order by 1
;
And this does not work. Is there a way to do this or should I just run two separate SQL queries?
More info about this setup (like my classes) can be found from Using Oracle combine three tables to one with PIVOT
For 12c and above, you may use DBMS_SQL.RETURN_RESULT by opening a REFCURSOR for the dynamic PIVOT query.
I have removed the notorious (+) syntax for left join, always use the ANSI join syntax.
DECLARE
exam_ids VARCHAR2(255);
x SYS_REFCURSOR;
BEGIN
SELECT
LISTAGG(''''
|| exam_id
|| ''' AS "'
|| exam_name
|| '"',',') WITHIN GROUP(
ORDER BY
exam_id DESC
)
INTO exam_ids
FROM
exam;
OPEN x FOR 'SELECT
*
FROM
(
SELECT
u.user_id,
u.user_name,
e.exam_id,
eu.exam_date
FROM
users u
LEFT JOIN exam_user eu ON u.user_id = eu.user_id
LEFT JOIN exam e ON e.exam_id = eu.exam_id
ORDER BY
u.user_id
)
PIVOT ( MAX ( exam_date )
FOR exam_id
IN ( ' || EXAM_IDS || ' )
)
ORDER BY
1';
dbms_sql.return_result(x);
END;
/
For 11g, you may use a bind variable and print command ( works in sqlplus and in sql developer/Toad when run as script (F5))
variable x REFCURSOR -- bind variable declared.
DECLARE
.. -- no need to declare sys_refcursor
BEGIN
..
OPEN :x FOR 'SELECT . --note the change with colon
*
FROM
(
SELECT
..
END;
/
print x