Oracle pivoting unknown number of column before execution time - oracle

I have something like this:
id cod
1 a
1 b
1 c
2 d
2 e
3 f
3 g
and i need something like this:
id cod 1 cod 2 cod 3
1 a b c
2 d e
3 f g
you understand that there is no way to know how many column oracle will have to generate before the execution time.

You can use procedure p_pivot, code below. It dynamically builds view v_test based on your table.
Then you can select from this view like here:
Connected to Oracle Database 10g Release 10.2.0.4.0
SQL> execute p_pivot;
PL/SQL procedure successfully completed
SQL> select * from v_test;
ID COD1 COD2 COD3
---------- ----- ----- -----
1 a b c
2 d e
3 f g
Procedure (please change table name from test to your table name in code):
create or replace procedure p_pivot is
v_cols number;
v_sql varchar2(4000);
begin
select max(cnt) into v_cols
from (select count(1) cnt from test group by id);
v_sql :=
'create or replace view v_test as
with t as (select row_number() over (partition by id order by cod) rn, test.* from test)
select id';
for i in 1..v_cols
loop
v_sql := v_sql || ', max(decode(rn, '||i||', cod)) cod'||i;
end loop;
v_sql := v_sql || ' from t group by id';
execute immediate v_sql;
end p_pivot;

There is NO way how to do that. Oracle HAS to know the number of columns at the moment when it is compiling the query.
Imagine that you create a view using such a query. The view would have different number of columns each time you look at it.
Also there should be no way how to fetch data from such a query. Because you do not know how many columns have until you evaluate all the data in the table.

Hi You can use this query also.
--Creating test data
create table test_me(id_num number,val varchar2(10));
insert all
into test_me
values(1,'a')
into test_me values (1,'b')
into test_me values (1,'c')
into test_me values (2,'d')
into test_me values (2,'e')
into test_me values (3,'f')
into test_me values (3,'g')
select 1 from dual;
select * from
(
select id_num,val,row_number() over (partition by id_num order by val) rn
from test_me )
pivot (max(val) for id_num in(1 as val_1,2 as val_2,3 as val_3));
The SQLFiddle demo is here
http://sqlfiddle.com/#!4/4206f/1

Related

Oracle View or Table Function that returns union of queries stored as text in another table

Let's say that I have a "Rules" table that has a column that contains oracle queries in a varchar2 column:
Row
Query
1
select 'Hardcoded_Tablename_1' tablename, request_id from table_1 where status >= 4 and resolve_date < *some date math goes here*
2
select 'Table_2' tablename, t2.request_id from table_2 t2 join table_1 t1 on t1.request_id = t2.parent_id where t1.status >= 4 and t1.resolve_date < *some date math goes here*
If this were never going to change, I'd just make a view with a union of these queries.
Our requirement is that we be able to add to or to modify these rules on-the-fly at the whims of leadership.
So, what I need is either:
a very smart view (I think impossible) that executes and unions all of these stored query strings
or
a table function that returns the results of the union of these stored query strings. (I think this is the more likely solution)
It will only ever be those two columns: The hardcoded name of the table and the ID of the record.
Can someone help get me started on this?
Thanks
You can use a PIPELINED function.
First create the types:
CREATE TYPE request_data IS OBJECT (tablename VARCHAR2(30), request_id NUMBER);
CREATE TYPE request_list IS TABLE OF request_data;
Then the function:
CREATE FUNCTION get_requests RETURN request_list PIPELINED
IS
BEGIN
FOR r IN (SELECT "QUERY" FROM table_name ORDER BY "ROW")
LOOP
DECLARE
c_cursor SYS_REFCURSOR;
v_tablename VARCHAR2(30);
v_request_id NUMBER;
BEGIN
OPEN c_cursor FOR r."QUERY";
LOOP
FETCH c_cursor INTO v_tablename, v_request_id;
EXIT WHEN c_cursor%NOTFOUND;
PIPE ROW (request_data(v_tablename, v_request_id));
END LOOP;
CLOSE c_cursor;
EXCEPTION
WHEN NO_DATA_NEEDED THEN
CLOSE c_cursor;
RETURN;
END;
END LOOP;
END;
/
Then, if you have the sample data:
CREATE TABLE table_name ("ROW", "QUERY") AS
SELECT 1, q'[select 'Hardcoded_Tablename_1' tablename, request_id from table_1 where status >= 4 and resolve_date < SYSDATE]' FROM DUAL UNION ALL
SELECT 2, q'[select 'Table_2' tablename, t2.request_id from table_2 t2 join table_1 t1 on t1.request_id = t2.parent_id where t1.status >= 4 and t1.resolve_date < SYSDATE]' FROM DUAL
CREATE TABLE table_1 (request_id, status, resolve_date) AS
SELECT 42, 4, SYSDATE - 1 FROM DUAL;
CREATE TABLE table_2 (request_id, parent_id) AS
SELECT 57, 42 FROM DUAL;
Then you can use the function in a table collection expression:
SELECT *
FROM TABLE(get_requests());
Which outputs:
TABLENAME
REQUEST_ID
Hardcoded_Tablename_1
42
Table_2
57
db<>fiddle here
One option might be a function that returns refcursor.
SQL> select * from rules;
CROW QUERY
---------- ----------------------------------------------------------------------------------------------------
1 select 'EMP' tablename, empno from emp where hiredate = (select max(hiredate) from emp)
2 select 'DEPT' tablename, d.deptno from emp e join dept d on d.deptno = e.deptno where e.hiredate = (
select min(hiredate) from emp)
Function creates union of all queries from the rules table and uses it as a source for the refcursor:
SQL> create or replace function f_test return sys_refcursor
2 is
3 l_rc sys_refcursor;
4 l_str clob;
5 begin
6 for cur_r in (select query from rules order by crow) loop
7 l_str := l_str || cur_r.query ||' union all ';
8 end loop;
9 l_str := rtrim(l_str, ' union all ');
10
11 open l_rc for l_str;
12 return l_rc;
13 end;
14 /
Function created.
Testing:
SQL> select f_test from dual;
F_TEST
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
TABL EMPNO
---- ----------
EMP 7876
DEPT 20
SQL>

Getting the SQL ID's of each statement executed in a stored procedure

I have the following Stored Procedure:
create or replace procedure insert_employee_to_dept (f_name IN varchar2, l_name IN varchar2, dept IN varchar2, tier IN char)
is
dept_count number;
t1_count number;
begin
INSERT INTO employees (id, first_name, last_name, department, tier)
VALUES (employee_sequence.NEXTVAL,
f_name,
l_name,
dept,
tier);
select count(*) into dept_count from employees where department = dept;
update dept_info set emp_count = dept_count where id = dept;
select count(*) into t1_count from employees where tier = 1;
update company set tier_one_count = t1_count where name = 'MyCompany';
end;
I enable SQL ID feedback using below statement
set feedback on sql_id;
Finally I run my Stored Procedure as such:
call insert_employee_to_dept('John', 'Doe', 'Finance', '5')
The output of the above command contains the SQL id of the PL/SQL block but this block does not have an execution block which can be queried using DBMS_XPLAN.DISPLAY_CURSOR
Is there a way to get all the SQL ID's that were executed when the stored procedure was called?
Following this answer, I enabled tracing and ran the procedure which generated the trace file on the server. Unfortunately in my use case I do not have access the file system of the database server
You may use all_statements system view to get all the statements executed in PL/SQL unit and their SQL_ID's.
create table t (
id int,
val int
)
/
create or replace procedure test_proc(
p_id in int
)
as
begin
insert into t(id, val) values(p_id, 100);
update t set val = 10;
delete from t where id = p_id;
end;
/
begin
test_proc(1);
end;
/
select
owner
, object_type
, object_name
, type
, line
, sql_id
, text
from all_statements
/
OWNER
OBJECT_TYPE
OBJECT_NAME
TYPE
LINE
SQL_ID
TEXT
DEMO
PROCEDURE
TEST_PROC
DELETE
8
85cd3d95cya5x
DELETE FROM T WHERE ID = :B1
DEMO
PROCEDURE
TEST_PROC
UPDATE
7
d6au94gzv1ydh
UPDATE T SET VAL = 10
DEMO
PROCEDURE
TEST_PROC
INSERT
6
bm74chwppp8w4
INSERT INTO T(ID, VAL) VALUES(:B1 , 100)
If you doing that for testing purpose, you can use dbms_application_info to set module and action, and then use them to select from v$sql/v$sqlarea/v$active_session_history/etc:
SQL> declare
2 n number;
3 begin
4 dbms_application_info.set_module('test_module','test_action');
5 select/*+test1*/ count(*) into n from dual;
6 select/*+test2*/ count(*) into n from dual;
7 dbms_application_info.set_module('','');
8 end;
9 /
PL/SQL procedure successfully completed.
SQL> select sql_id,substr(sql_text,1,50) sqltext50
2 from v$sqlarea
3 where module='test_module'
4 and action='test_action';
SQL_ID SQLTEXT50
------------- ------------------------------------
bavxnddfrxju6 SELECT/*+test2*/ COUNT(*) FROM DUAL
7d8853usybzdg SELECT/*+test1*/ COUNT(*) FROM DUAL
Btw, you can use module/action/client_id also for dbms_monitor:
Performing Application Tracing
DBMS_MONITOR
Or sometimes you can even use v$open_cursor (depends on many factors like open_cursor parameter, etc):
SQL> declare
2 n number;
3 begin
4 dbms_application_info.set_module('test_module','test_action');
5 select/*+test10*/ count(*) into n from dual;
6 select/*+test11*/ count(*) into n from dual;
7 dbms_application_info.set_module('','');
8 end;
9 /
PL/SQL procedure successfully completed.
SQL> select sql_id,substr(sql_text,1,50) sqltext50
2 from v$open_cursor
3 where sid=userenv('sid')
4 and user_name=user
5 and last_sql_active_time is not null
6 order by last_sql_active_time desc
7 fetch first 5 rows only;
SQL_ID SQLTEXT50
------------- ---------------------------------------------------
3669hp0tndbgu declare n number; begin dbms_application_inf
9szagfb62bhgs SELECT/*+test10*/ COUNT(*) FROM DUAL
9h8pabdtrb1wm select sql_id,substr(sql_text,1,50) sqltext50 from
1901dfp1ktg5d SELECT/*+test11*/ COUNT(*) FROM DUAL
bavxnddfrxju6 SELECT/*+test2*/ COUNT(*) FROM DUAL

Oracle PL/SQL - Loop value as dynamic column name without dynamic SQL

I would like to update multiple tables that have the same column name with the same value, instead of having an update for each table, and since Oracle doesn't provide a way to update multiple tables at once I though of using a loop for this.
I tried this but it doesn't work as expected:
begin
for i in (select column_value from table(sys.dbms_debug_vc2coll('tab1', 'tab2'))) loop
update i set my_col = 'my value';
end loop;
end;
I know I can use dynamic SQL for this with execute immediate but is there a way to avoid it?
The question (and so the problem) looks pretty simple:
update multiple tables that have the same column name with the same value
How about creating a view based on those tables & an instead of trigger which would do the job? Here's how:
Sample tables:
SQL> select * from tab_a;
ID M
---------- -
1 x
2 y
SQL> select * from tab_b;
NA MY_
-- ---
LF www
JW zzz
MC
View:
SQL> create or replace view v_tabs as
2 select to_char(id) idn, my_col from tab_a
3 union all
4 select name , my_col from tab_b;
View created.
SQL> select * from v_tabs;
IDN MY_
---------------------------------------- ---
1 x
2 y
LF www
JW zzz
MC
Instead of trigger:
SQL> create or replace trigger trg_tabs
2 instead of update on v_tabs
3 for each row
4 begin
5 update tab_a set my_col = :new.my_col;
6 update tab_b set my_col = :new.my_col;
7 end;
8 /
Trigger created.
Testing:
SQL> update v_tabs set my_col = 'e';
5 rows updated.
SQL> select * from tab_a;
ID M
---------- -
1 e
2 e
SQL> select * from tab_b;
NA MY_
-- ---
LF e
JW e
MC e
SQL>
All MY_COL values, in all tables involved, are set to e. That's what you asked for, right?
If you can't use execute immediate try with the DBMS_SQL package:
https://docs.oracle.com/cd/B28359_01/appdev.111/b28419/d_sql.htm#i996963
Here is a syntyax sample of how to use this package taken from the doc:
CREATE OR REPLACE PROCEDURE demo(salary IN NUMBER) AS
cursor_name INTEGER;
rows_processed INTEGER;
BEGIN
cursor_name := dbms_sql.open_cursor;
DBMS_SQL.PARSE(cursor_name, 'DELETE FROM emp WHERE sal > :x', DBMS_SQL.NATIVE);
DBMS_SQL.BIND_VARIABLE(cursor_name, ':x', salary);
rows_processed := DBMS_SQL.EXECUTE(cursor_name);
DBMS_SQL.CLOSE_CURSOR(cursor_name);
EXCEPTION
WHEN OTHERS THEN
DBMS_SQL.CLOSE_CURSOR(cursor_name);
END;
/

ORA-00928: missing SELECT keyword

In MYTABLE there are courses and their predecessor courses.
What I am trying to is to find the courses to be taken after the specified course. I am getting missing SELECT keyword error. Why I am getting this error although I have SELECT statement in FOR statement ? Where am I doing wrong ?
DECLARE
coursename varchar2(200) := 'COURSE_101';
str varchar2(200);
BEGIN
WITH DATA AS
(select (select course_name
from MYTABLE
WHERE predecessors like ('''%' || coursename||'%''')
) str
from dual
)
FOR cursor1 IN (SELECT str FROM DATA)
LOOP
DBMS_OUTPUT.PUT_LINE(cursor1);
END LOOP;
end;
Unless I'm wrong, WITH factoring clause can't be used that way; you'll have to use it as an inline view, such as this:
declare
coursename varchar2(200) := 'COURSE_101';
str varchar2(200);
begin
for cursor1 in (select str
from (select (select course_name
from mytable
where predecessors like '''%' || coursename||'%'''
) str
from dual
)
)
loop
dbms_output.put_line(cursor1.str);
end loop;
end;
/
Apart from the fact that it doesn't work (wrong LIKE condition), you OVERcomplicated it. This is how it, actually, does something:
SQL> create table mytable(course_name varchar2(20),
2 predecessors varchar2(20));
Table created.
SQL> insert into mytable values ('COURSE_101', 'COURSE_101');
1 row created.
SQL>
SQL> declare
2 coursename varchar2(20) := 'COURSE_101';
3 begin
4 for cursor1 in (select course_name str
5 from mytable
6 where predecessors like '%' || coursename || '%'
7 )
8 loop
9 dbms_output.put_line(cursor1.str);
10 end loop;
11 end;
12 /
COURSE_101
PL/SQL procedure successfully completed.
SQL>
Also, is that WHERE clause correct? PREDECESSORS LIKE COURSENAME? I'm not saying that it is wrong, just looks somewhat strange.
To extend #Littlefoot's answer a bit: you can use a common table expression (WITH clause) in your cursor, but the WITH must be part of the cursor SELECT statement, not separate from it:
DECLARE
coursename varchar2(200) := 'COURSE_101';
BEGIN
FOR aRow IN (WITH DATA AS (select course_name AS str
from MYTABLE
WHERE predecessors like '''%' || coursename||'%''')
SELECT str FROM DATA)
LOOP
DBMS_OUTPUT.PUT_LINE(aRow.str);
END LOOP;
END;
Also note that the iteration variable in a cursor FOR-loop represents a row returned by the cursor's SELECT statement, so if you want to display whatever was returned by the cursor you must use dotted-variable notation (e.g. aRow.str) to extract fields from the row.
Best of luck.
CREATE TABLE product
(
PRODUCT_ID int Primary key,
NAME VARCHAR (20) not null,
Batchno int not null,
Rate int not null,
Tax int not null,
Expiredate date not null
);
INSERT INTO PRODUCT VALUSES(1 , 'vasocare', 32 , 15 , 2 , 01-JAN-2021);

Oracle datasets from two different tables without joining

I need to write a stored procedure that will provide the data from two different tables. Say table1 and table2. These two tables doesn't have any relationship.
Now in SQL Server i can simply create a stored procedures like:
create procedure abc
as
begin
select * from table1;
select * from table2:
end;
Now in oracle, I usually create a SYS_REFCURSOR and do something like:
Open SYS_REFCURSOR_VAR For Select * from table1;
but I don't know how to provide the two result sets from two different tables table1 and table2. I tried to create two different SYS_REFCURSOR one for each table. But when I executed the procedure I got the data from first table only. The second SYS_REFCURSOR doesn't seems to be working.
Anyone have any idea, how to accomplish this?
Try this
create or replace procedure tst
(p_refcursor1 out sys_refcursor,p_refcursor2 out sys_refcursor)
is
begin
open p_refcursor1 for
select * from dual;
open p_refcursor2 for
select * from dual;
end;
So I assume the records you want to pull from each table are effectively the same. e.g. id, name, price. In that case just write your query like
SELECT t1.id AS id, t1.name AS name, t1.unit_price AS price FROM t1
UNION
SELECT t2.id AS id, t2.description AS name, t2.price AS price FROM t2
Not sure if its required, but always good form to have a union return data sets with the same "column" names. So I used the AS in teh example to demonstrate this
You need to give more details about your problem.. specifically
1) the code that you have tried so far
2) How are you accessing your ref cursor to see the results? (SQLPLUS or Java or VB.net)?
Based on your SQL Server code, I am assuming you are trying to get the UNION of the rows from the two tables. Here is the code in Oracle that lets you do that. As you can see, my client tool here is SQLPLUS and I am able to see the values from both the tables.
create table t1(
id number,
name varchar2(10)
);
create table t2(
id number,
name varchar2(10)
);
create or replace procedure get_t1_and_t2(
o_cur out sys_refcursor) is
begin
open o_cur for
select id from t1
union all
select id from t2;
end;
/
Procedure created.
SQL> var rc refcursor;
SQL> exec get_t1_and_t2( :rc);
PL/SQL procedure successfully completed.
SQL> print rc;
ID
----------
1
2
This script shows #Maxim Shevtsov answer working. Run it in SQL*Plus
SET serveroutput ON size 100000
DECLARE
c1 SYS_REFCURSOR;
c2 SYS_REFCURSOR;
v1 VARCHAR2(10);
v2 VARCHAR2(10);
n2 NUMBER;
PROCEDURE tst ( p_refcursor1 OUT SYS_REFCURSOR
, p_refcursor2 OUT SYS_REFCURSOR)
IS
BEGIN
OPEN p_refcursor1 FOR
SELECT 'val1' FROM DUAL;
OPEN p_refcursor2 FOR
SELECT 'val2', 42 FROM DUAL;
END tst;
BEGIN
tst( c1, c2 );
LOOP
FETCH c1 INTO v1;
EXIT WHEN c1%NOTFOUND;
DBMS_OUTPUT.put_line( 'CURSOR1: ' || v1 );
END LOOP;
LOOP
FETCH c2 INTO v2, n2;
EXIT WHEN c2%NOTFOUND;
DBMS_OUTPUT.put_line( 'CURSOR2: ' || v2 || ' ' || n2 );
END LOOP;
END;
/

Resources