I have a table which contains SQL query as one of the columns. Based on record id, I want to execute SQL query which is in the record.
Master_Table
------------------------------
|Rec_ID | Query |
------------------------------
|1 | SELECT * from EMP |
------------------------------
|2 | SELECT * FROM DEPT |
------------------------------
SELECT Query FROM Master_Table WHERE Rec_ID=1
I am expecting that If I select Rec_Id =1, I have to EMP records.
If I select Rec_Id=2, I need to get Dept records.
Is it possible to do it in SQL query?
There are a few ways to run dynamic SQL in SQL. In 18c we can use a polymorphic table function. If it's OK to get the results as XML we can use DBMS_XMLGEN.getXML. If we're able to create custom PL/SQL objects we can use Oracle data cartridge to build a Method4 solution.
For example, after installing Method4, we can run SQL like this:
select * from table(method4.dynamic_query(
'
select query
from master_table
where rec_id = 1
'
));
The above code will work with the below sample schema:
create table master_table as
select 1 rec_id, 'SELECT * from EMP' query from dual union all
select 2 rec_id, 'SELECT * FROM DEPT' query from dual;
create table emp(emp_name varchar2(100));
create table dept(dept_name varchar2(100));
The preceding information literally answers your question. But I agree with Mark D Powell that this design is often a bad idea and we should only create code like this after we've evaluated alternative designs.
Vsau, I agree with JNevill in that you need PL/SQL to execute the 'SQL' from your table column. Also I will add that these kind of designs are usually a bad idea. You want your application SQL to be static sql using bind variables otherwise your system will suffer from too high a percentage of hard parses and resulting contention on the dictionary objects and you may run into shared pool fragmentation issues.
You can use sys_refcursor
SQL> create or replace function get_emp_tab return sys_refcursor is
v_rc sys_refcursor;
v_sql varchar2(4000);
begin
select query into v_sql from Master_Table where Rec_ID = 1
open v_rc for v_sql;
return v_rc;
end;
/
SQL> declare
v_rc sys_refcursor;
begin
:v_rc := get_emp_tab;
end;
/
SQL> print v_rc;
Related
Create or replace procedure PROC AS
V_TABLE_NAME VARCHAR2(255);
V_LIST SYS_REFCURSOR;
DATE_VALUE_INS VARCHAR2(10);
BEGIN
DATE_VALUE_INS:=TO_CHAR(SYSDATE,'YYMMDD');
OPEN V_LIST FOR
SELECT NAME FROM DW.table_name_list ;
LOOP
FETCH V_LIST
INTO V_TABLE_NAME;
EXIT WHEN V_LIST%NOTFOUND;
EXECUTE IMMEDIATE 'CREATE TABLE Schema.'||V_TABLE_NAME||'_'||DATE_VALUE_INS||' AS SELECT * FROM DW.'||V_TABLE_NAME;
END LOOP;
CLOSE V_LIST;
end;
I have created this Proc which takes value from a table which has Table_name and create Backup using Execute Immediate.
Now the requirement has changed that i only need to create backup for partial records (i.e. where clause on each table )
I have 6 tables as such .
New Approach i am thinking is :
EXECUTE IMMEDIATE 'CREATE TABLE Schema.'||V_TABLE_NAME||'_'||DATE_VALUE_INS||' AS SELECT * FROM DW.'||V_TABLE_NAME where some condition;
But the problem becomes all 6 have different column to filter on.
My Ask is How should I change my design of proc to Adjust this new Requirement.
6 tables? Why bother? Create a procedure which - depending on table name passed as a parameter - in IF-THEN-ELSE runs 6 different CREATE TABLE statements.
On the other hand, another approach would be to create backup tables in advance (at SQL level), add BACKUP_DATE column to each of them, and - in procedure - just perform INSERT operation which doesn't require dynamic SQL at all.
For example:
create table emp_backup as select * from emp where 1 = 2;
alter table emp_backup add backup_date date;
create or replace procedure p_backup (par_table_name in varchar2) is
begin
if par_table_name = 'EMP' then
insert into emp_backup (empno, ename, job, sal, backup_date)
select empno, ename, job, sal, trunc(sysdate)
from emp
where deptno = 20; --> here's your WHERE condition
elsif par_table_name = 'DEPT' then
insert into dept_backup (...)
select ..., trunc(sysdate)
from dept
where loc = 'DALLAS';
elsif ...
...
end if;
end;
/
Doing so, you'd easier access backup data as you'd query only one table, filtered by BACKUP_DATE. That's also good if you have to search for some data that changed several days ago, but you don't know exact day. What would you rather do: query 10 tables (and still not find what you're looking for), or query just one table and find that info immediately?
I got table types:
CREATE OR REPLACE TYPE "TABLE_OF_VARCHAR2" AS TABLE OF VARCHAR2(4000);
CREATE OR REPLACE TYPE "TABLE_OF_NUMBER" AS TABLE OF NUMBER;
And in my package body i got a cursor:
cursor c_src_m(trans_list table_of_number, v_codes table_of_varchar2, ...) is
select ....something
where ....
--AND ta.id in (select COLUMN_VALUE from TABLE(trans_list))
--AND (tb.UNDERLYING_VALUE in (select COLUMN_VALUE from TABLE(v_codes)) OR (v_codes is null or (select count(1) from TABLE(v_codes)) = 0))
For each of last 2 lines if i uncomment them i get an error:
ORA-22905: cannot access rows from a non-nested table item
*Cause: attempt to access rows of an item whose type is not known at
parse time or that is not of a nested table type
*Action: use CAST to cast the item to a nested table type
I looked over 3 hours for a solution and still couldn't find a working one. Do i really need to cast it as error message says? Does anyone know what is the problem here?
There might be something wrong in your code which you are either not showing in your question or have missed it. Please see the below DEMO which is on the similar lines of your question. I used it the same way you did and its working fine on Oracle11g. Also, you can use MEMBER OF as well inplace of your Select statement query in where clause. See below and read my inline comments to understand more.
Table and Types Creation:
CREATE OR REPLACE TYPE TABLE_OF_VARCHAR2 AS TABLE OF VARCHAR2(4000);
/
CREATE OR REPLACE TYPE TABLE_OF_NUMBER AS TABLE OF NUMBER;
/
Create table Num_varchar(col1 number, col2 varchar2(1000));
/
INSERT ALL
INTO Num_varchar VALUES (1,'A')
INTO Num_varchar VALUES (2,'B')
INTO Num_varchar VALUES (3,'C')
INTO Num_varchar VALUES (4,'D')
INTO Num_varchar VALUES (5,'E')
SELECT * FROM dual;
/
Block:
DECLARE
CURSOR c_src_m(trans_list table_of_number, v_codes table_of_varchar2)
IS
SELECT col1,
col2
FROM Num_varchar
--Inplace of select query you can use Member of as well. However if you want to use select query, this will also work.
--WHERE col1 IN (SELECT COLUMN_VALUE FROM TABLE(trans_list) )
--AND col2 IN (SELECT COLUMN_VALUE FROM TABLE(v_codes));
WHERE col1 MEMBER OF trans_list
and col2 MEMBER OF v_codes;
--Populated the collection so that i can use it in my query above
var_varchr2 TABLE_OF_VARCHAR2:=TABLE_OF_VARCHAR2('A','B','C','D');
var_number TABLE_OF_NUMBER :=TABLE_OF_NUMBER(1,2,3,4);
var1 NUMBER;
var2 VARCHAR2(100);
BEGIN
OPEN c_src_m ( var_number , var_varchr2);
LOOP
FETCH c_src_m INTO var1,var2;
EXIT WHEN c_src_m%NOTFOUND;
--dispalying the result of the cursor
dbms_output.put_line(var1 || var2);
END LOOP;
Close c_src_m;
END;
Output:
1A
2B
3C
4D
Note: If you had create the types in Package specification, it will not work. Until Oracle11g , any types created under PLSQL scope cannot be referred in SQL statement inside the PLSQL block. If you have done so, just create types out of package scope and it should work then.
I have a problem trying to use an Execute Immediate statement containing a CREATE TABLE statement and a user defined Table Type. I get error ORA-22905 on Oracle 11g.
Is there any workaround to solve this issue?
CREATE TYPE MY_TABLE_TYPE AS TABLE OF VARCHAR2(30);
/
DECLARE
MT MY_TABLE_TYPE;
BEGIN
SELECT * BULK COLLECT INTO MT FROM DUAL;
-- Two steps
EXECUTE IMMEDIATE 'CREATE TABLE MY_TABLE1 (A VARCHAR2(30))';
EXECUTE IMMEDIATE 'INSERT INTO MY_TABLE1 SELECT * FROM TABLE(:T)' USING MT; -- OK
-- One step
EXECUTE IMMEDIATE 'CREATE TABLE MY_TABLE2 AS SELECT * FROM TABLE(:T)' USING MT; -- ERROR ORA-22905
END;
The real code for the SELECT * FROM TABLE(:T) is dynamic (main table name is temporary) and slow. That's why I try to avoid creating the table in two steps (as done with MY_TABLE1). Also with two steps I can't use SELECT * but I have to specify all the columns (variable amount and over 100 columns).
There is likely a way to completely avoid this issue. Skip the bulk collect and use a simple CREATE TABLE MY_TABLE AS SELECT * FROM DUAL; That may be an over-simplification of the real logic to gather the data. But there is almost always a way to bypass a bulk collect and store the data directly in an object with just SQL.
If a PL/SQL solution is truly needed, the error ORA-22905: cannot access rows from a non-nested table item can be avoided by creating an object type and creating the table based on that type. This may not solve the performance issue, but at least this avoids the need to re-specify all the columns in the table DDL.
CREATE TYPE MY_TABLE_OBJECT IS OBJECT
(
A VARCHAR2(30)
);
CREATE TYPE MY_TABLE_TYPE2 AS TABLE OF VARCHAR2(30);
DECLARE
MT MY_TABLE_TYPE2;
BEGIN
SELECT * BULK COLLECT INTO MT FROM DUAL;
EXECUTE IMMEDIATE 'CREATE TABLE MY_TABLE2 OF MY_TABLE_OBJECT';
EXECUTE IMMEDIATE 'INSERT INTO MY_TABLE2 SELECT * FROM TABLE(:T)' USING MT;
END;
/
I want to be able to delete by rowid then immediately insert the data being deleted in an audit table.
There are far too many records to
INSERT INTO ... SELECT CRITERIA then DELETE ... CRITERIA.
I already know how to do everything just using rowid and INSERT INTO ... SELECT.
Inside package body:
TYPE some_type IS RECORD (
row_id ROWID,
full_row table_name%ROWTYPE
);
TYPE some_type_list IS TABLE OF some_type
INDEX BY BINARY_INTEGER;
PROCEDURE do_stuff
IS
lc_data SYS_REFCURSOR;
lt_recs some_type_list;
BEGIN
OPEN lc_date FOR
SELECT rowid, a.*
FROM table_name;
LOOP
FETCH lc_data
BULK COLLECT INTO lt_recs
LIMIT 50000;
EXIT WHEN lt_recs.COUNT = 0;
--
FORALL i IN lt_recs.FIRST..lt_recs.LAST
DELETE table_name
WHERE ROWID = lt_recs(i).row_id;
--
FORALL i IN lt_recs.FIRST..lt_recs.LAST
INSERT INTO table_name_audit VALUES lt_recs(i).full_row;
END LOOP;
END;
If I try that i get the following error:
Line: 117 Column: 25 Type: error Text: PLS-00597: expression 'LT_RECS' in the INTO list is of wrong type
Oracle versions prior to 11gR2 restrict us to use BULK COLLECT into a collection (nested table or varray) of records. Read more here on Oracle Docs.
If you want to see how it is done in 11gR2, scroll down to EDIT 2 section of this answer.
An alternative tho this can be the use of separate collections for every column- an approach that is most widely used. In this you can have:
/*
TYPE some_type IS RECORD (
row_id ROWID,
full_row table_name%ROWTYPE
);
TYPE some_type_list IS TABLE OF some_type
INDEX BY BINARY_INTEGER;
-- */
CREATE TYPE t_row_id IS TABLE OF ROWID;
CREATE TYPE t_col1 IS TABLE OF table_name.col1%TYPE;
CREATE TYPE t_col2 IS TABLE OF table_name.col2%TYPE;
CREATE TYPE t_col3 IS TABLE OF table_name.col3%TYPE;
...
...
CREATE TYPE t_colN IS TABLE OF table_name.colN%TYPE;
PROCEDURE do_stuff
IS
lc_data SYS_REFCURSOR;
-- lt_recs some_type_list;
row_id t_row_id;
col1 t_col1;
col2 t_col2;
col3 t_col3;
...
...
colN t_colN;
BEGIN
OPEN lc_date FOR
SELECT rowid, a.*
FROM table_name;
LOOP
FETCH lc_data
BULK COLLECT INTO row_id, col1, col2, col3, ..., colN
LIMIT 50000;
EXIT WHEN lt_recs.COUNT = 0;
--
FORALL i IN row_id.FIRST..row_id.LAST
DELETE table_name
WHERE ROWID = row_id(i);
--
FORALL i IN col1.FIRST..col1.LAST
INSERT INTO table_name_audit VALUES (col1(i), col2(i), col3(i), ..., colN(i));
END LOOP;
END;
I have not removed many of the rows in your program in order to let you understand the changes.
EDIT : Refer to the "Restrictions on BULK COLLECT" section of the Oracle Docs link I have given above and also here.
EDIT #2 :
You have to use CREATE TYPE ... IS OBJECT instead of RECORD. Also, You need to modify the SELECT statement the way I have done when I tried it. Please see the Oracle Docs here and a StackOverflow question here for further reference.
The code I tried on my machine (runs Oracle 11g R2) is as follows:
-- SELECT * FROM user_objects WHERE object_type = 'TYPE';
CLEAR SCREEN;
SET SERVEROUTPUT ON;
CREATE OR REPLACE TYPE temp_t_test AS OBJECT ( -- << OBJECT, not RECORD.
test_id INTEGER
, test_val VARCHAR2(50)
);
/
CREATE OR REPLACE TYPE temp_tbl_test AS TABLE OF TEMP_T_TEST;
/
DECLARE
v_test TEMP_TBL_TEST;
BEGIN
SELECT temp_t_test(t_id, t_val) -- << Notice the syntax
-- I'm selecting the columns as the defined OBJECT type.
BULK COLLECT INTO v_test
FROM (SELECT 1 AS t_id, 'ABCD' AS t_val FROM dual
UNION ALL
SELECT 2, 'WXYZ' FROM dual
UNION ALL
SELECT 3, 'PQRS' FROM dual);
dbms_output.put_line('Bulk Collect Successful!');
END;
/
** OUTPUT **:
TYPE temp_t_test compiled
TYPE temp_tbl_test compiled
anonymous block completed
Bulk Collect Successful!
I don't think that I'd take this approach at all, to be honest.
A faster method would be along the lines of performing a multitable insert:
insert the table columns into the audit table, possibly using direct path (APPEND hint) for efficiency
insert the rowid's into an on-commit-delete-rows global temporary table.
Then perform a delete against the original table using DELETE .. WHERE ROWID IN (SELECT ORIGINAL_ROWID FROM MY_GLOBAL_TEMP_TAB)
... and then commit.
Faster, and less code I think.
What you're trying to works in 11gR2 - what version are you on?.
The only wrong-looking thing in your post is this:
OPEN lc_date FOR
SELECT rowid, a.*
FROM table_name;
It ought to be this ...
OPEN lc_data FOR
SELECT a.rowid, a.*
FROM table_name a;
... but these may simply be typos you introduced when sanitizing your code to post here.
In SQL Server, you can declare a table variable (DECLARE #table TABLE), which is produced while the script is run and then removed from memory.
Does Oracle have a similar function? Or am I stuck with CREATE/DROP statements that segment my hard drive?
Yes.
Declare TABLE TYPE variables in a
PL/SQL declare block. Table variables
are also known as index-by table or
array. The table variable contains one
column which must be a scalar or
record datatype plus a primary key of
type BINARY_INTEGER. Syntax:
DECLARE
TYPE type_name IS TABLE OF
(column_type |
variable%TYPE |
table.column%TYPE
[NOT NULL]
INDEX BY BINARY INTEGER;
-- Then to declare a TABLE variable of this type:
variable_name type_name;
-- Assigning values to a TABLE variable:
variable_name(n).field_name :=
'some text'; -- Where 'n' is the
index value
Ref: http://www.iselfschooling.com/syntax/OraclePLSQLSyntax.htm
You might want to also take a look at Global Temporary Tables
The below solution is the closest from SQL Server I can do today.
Objects:
CREATE OR REPLACE TYPE T_NUMBERS IS TABLE OF NUMBER;
CREATE OR REPLACE FUNCTION ACCUMULATE (vNumbers T_NUMBERS)
RETURN T_NUMBERS
AS
vRet T_NUMBERS;
BEGIN
SELECT SUM(COLUMN_VALUE)
BULK COLLECT INTO vRet
FROM TABLE(CAST(vNumbers AS T_NUMBERS));
RETURN vRet;
END;
Queries:
--Query 1: Fixed number list.
SELECT *
FROM TABLE(ACCUMULATE(T_NUMBERS(1, 2, 3, 4, 5)));
--Query 2: Number list from query.
WITH cteNumbers AS
(
SELECT 1 AS COLUMN_VALUE FROM DUAL UNION
SELECT 2 AS COLUMN_VALUE FROM DUAL UNION
SELECT 3 AS COLUMN_VALUE FROM DUAL UNION
SELECT 4 AS COLUMN_VALUE FROM DUAL UNION
SELECT 5 AS COLUMN_VALUE FROM DUAL
)
SELECT *
FROM TABLE(
ACCUMULATE(
(SELECT CAST(COLLECT(COLUMN_VALUE) AS T_NUMBERS)
FROM cteNumbers)
)
);
Yes it does have a type that can hold the result set of a query (if I can guess what TABLE does). From ask Tom: your procedure may look like this:
procedure p( p_state in varchar2, p_cursor in out ref_cursor_type )
is
begin
open p_cursor for select * from table where state = P_STATE;
end;
where p_cursor is like a table type. As has been already answered there are plenty of options for storing result sets in Oracle. Generally Oracle PL/SQL is far more powerful than sqlserver scripts.
the table in variable in oracle not the same as table variables in MS SQLServer.
in oracle it's like regular array in java or c#. but in MS SQLserver it is the same as any table, you can call it logical table.
but if you want something in oracle that does exactly the same as table variable of SQLserver you can use cursor.
regards