Is there a way to set schema name as variable in oracle procedure?
create or replace procedure test is
v_schema varchar2(30);
begin
insert into v_schema.tab_a ( a, b)
select (a, b) from xyz;
end;
/
Thanks
You'd need to resort to dynamic SQL
create or replace procedure test
is
v_schema varchar2(30);
v_sql varchar2(1000);
begin
v_sql := 'insert into ' || v_schema || '.tab_a( a, b ) ' ||
'select a, b from xyz';
dbms_output.put_line( 'About to execute the statement ' || v_sql );
execute immediate v_sql;
end;
A couple of points
You almost certainly want to build the SQL statement in a local variable that you can print out and/or log before executing it. Otherwise, when there are syntax errors, you're going to have a much harder time debugging.
You almost never want to resort to dynamic SQL in the first place. The fact that you have a procedure where you know you want to insert all the rows from xyz into a table named tab_a but you don't know which schema that table is in is a red flag. That's unusual and often indicates a problem with your design. Very, very occasionally dynamic SQL is a wonderful tool when you need extra flexibility. But more often than not when you're thinking about a problem and dynamic SQL is the answer you want to reconsider the problem.
Related
I want to create procedure, that will use cursor, which is the same for arbitrary tables. My current one looks like this:
create or replace
PROCEDURE
some_name(
p_talbe_name IN VARCHAR2,
p_chunk_size IN NUMBER,
p_row_limit IN NUMBER
) AS
CURSOR v_cur IS
SELECT common_column,
ora_hash(substr(common_column, 1, 15), p_chunk_size) as chunk_number
-- Here it can find the table!
FROM p_table_name;
TYPE t_sample IS TALBE OF v_cur%rowtype;
v_sample t_sample;
BEGIN
OPEN v_cur;
LOOP FETCH v_cur BULK COLLECT INTO v_sample LIMIT p_row_limit;
FORALL i IN v_sample.first .. v_sample.last
INSERT INTO chunks VALUES v_sample(i);
COMMIT;
EXIT WHEN v_cur%notfound;
END LOOP;
CLOSE v_cur;
END;
The problem is that it cannot find the table named p_table_name which I want to parametrize. The thing is that I need to create chunks based on hashes for common_column which exists in all intended tables. How to deal with that problem? Maybe there is the equivalent oracle code that will do the same thing? Then I need the same efficiency for the query. Thanks!
I would do this as a single insert-as-select statement, complicated only by the fact you're passing in the table_name, so we need to use dynamic sql.
I would do it something like:
CREATE OR REPLACE PROCEDURE some_name(p_table_name IN VARCHAR2,
p_chunk_size IN NUMBER,
p_row_limit IN NUMBER) AS
v_table_name VARCHAR2(32); -- 30 characters for the tablename, 2 for doublequotes in case of case sensitive names, e.g. "table_name"
v_insert_sql CLOB;
BEGIN
-- Sanitise the passed in table_name, to ensure it meets the rules for being an identifier name. This is to avoid SQL injection in the dynamic SQL
-- statement we'll be using later.
v_table_name := DBMS_ASSERT.ENQUOTE_LITERAL(p_table_name);
v_insert_sql := 'insert into chunks (common_column_name, chunk_number)'||CHR(10)|| -- replace the column names with the actual names of your chunks table columns.
'select common_column,'||CHR(10)||
' ora_hash(substr(common_column, 1, 15), :p_chunk_size) AS chunk_number'||CHR(10)||
'from '||v_table_name||CHR(10)||
'where rownum <= :p_row_limit';
-- Used for debug purposes, so you can see the definition of the statement that's going to be run.
-- Remove before putting the code in production / convert to proper logging code:
dbms_output.put_line(v_insert_sql);
-- Now run the statement:
EXECUTE IMMEDIATE v_insert_sql USING p_chunk_size, p_row_limit;
-- I've included the p_row_limit in the above statement, since I'm not sure if your original code loops through all the rows once it processes the
-- first p_row_limit rows. If you need to insert all rows from the p_table_name into the chunks table, remove the predicate from the insert sql and the extra bind variable passed into the execute immediate.
END some_name;
/
By using a single insert-as-select statement, you are using the most efficient way of doing the work. Doing the bulk collect (which you were using) would use up memory (storing the data in the array) and cause extra context switches between the PL/SQL and SQL engines that the insert-as-select statement avoids.
[EDIT]Editing the code to reflect changes coming from comments
I have a problem with one of the stored procedures I'm trying to create in an Oracle database.
The goal is to update every table which has an indiv column.
CREATE OR REPLACE PROCEDURE sp_majUserOnAllK (lastU IN VARCHAR2, newU IN VARCHAR2)
AS
BEGIN
FOR item IN (
select table_name , owner
from all_tab_columns
where column_name = 'INDIV' AND OWNER ='K'
)
LOOP
EXECUTE IMMEDIATE 'UPDATE K.' || item.table_name || ' SET indiv = :newValue WHERE indiv = :oldValue' USING newU, lastU;
END LOOP;
END sp_majUserOnAllK;
exec sp_majUserOnAllK( 'hum','hum');
Problem is, when I try to execute the stored procedure, I got an error message with no detail at all ('non valid SQL').
I tried taking the code out of the stored procedure. And there, it works. Only the beginning is changing to :
DECLARE
newU NVARCHAR2(50);
lastU NVARCHAR2(50);
req VARCHAR2(100);
CURSOR ctable IS
select table_name , owner from all_tab_columns where column_name = 'INDIV' AND OWNER ='KEXPLOIT';
BEGIN
newU := 'hum';
lastU := 'hum';
FOR item IN ctable
....
Like that, it works perfectly and does exactly what it is supposed to do.
As the only difference is the assignation of the variable, I think I may have a problem with my procedure declaration but I can't find a solution. The compilation is ok.
Any idea ?
Your procedure's syntax is not correct. Try this.
CREATE OR REPLACE PROCEDURE sp_majUserOnAllK (lastU IN VARCHAR2, newU IN VARCHAR2)
IS
req VARCHAR2(100);
BEGIN
FOR item IN (select table_name , owner from all_tab_columns where column_name = 'INDIV' AND OWNER ='K')
LOOP
req := 'UPDATE K.' || item.table_name || ' SET indiv = :newValue WHERE indiv = :oldValue';
EXECUTE IMMEDIATE req USING newU, lastU;
END LOOP;
-- return 1; -- note: procedures do not return values
END;
/
A five-second Google search on "dbeaver exec command" brought this up among the first few hits:
https://github.com/dbeaver/dbeaver/issues/749
In it, we learn that EXEC is not supported by dbeaver.
EXEC is an SQL*Plus command. It is not Oracle SQL, and it is not PL/SQL. SQL*Plus is a shell program of sorts for interacting with Oracle databases; it has its own language, distinct from SQL and PL/SQL.
SQL Developer and Toad (and perhaps other similar programs) support (most of) SQL*Plus, but apparently dbeaver (with which I am not familiar) does not.
The link I copied above suggests using the CALL command instead. See the link for examples.
As an aside, when we use EXEC in SQL*Plus and SQL Developer, there is no semicolon at the end of the procedure call. Adding an unnecessary semicolon, however, does not throw an error (SQL*Plus is, apparently, smart enough to simply ignore it).
I created a procedure to calculate the hashcode of a record (complete line of a table) and then update a column with the calculated hashcode number.
Here's my code at this point (which is based on some info I manage to gather from Google):
CREATE OR REPLACE PROCEDURE calcHashCode (inputTableString IN varchar2) IS
c_data varchar2(3000); --QUERY
c_cursor sys_refcursor; --CURSOR
c_record inputTableString%rowtype; -- Problem is here
BEGIN
c_data := 'SELECT * FROM ' || inputTableString;
OPEN c_cursor for c_data;
LOOP
FETCH c_cursor INTO c_record;
EXIT WHEN c_cursor%notfound;
-- will do stuff here with the records
dbms_output.put_line('stuff');
END LOOP;
CLOSE c_cursor;
END;
/
SHOW ERRORS
4/13 PLS-00310: with %ROWTYPE attribute, 'INPUTTABELA' must name a table, cursor or cursor-variable
4/13 PL/SQL: Item ignored
11/25 PLS-00320: the declaration of the type of this expression is incomplete or malformed
11/5 PL/SQL: SQL Statement ignored
So, my idea (for the final stage of the procedure) is to iterate through out the records, build a string with and then calculate the hashcode. After that, I'll run the update instruction.
The thing is at this point using a varchar as an argument and I'm not being able to iterate through the table in order to get my concatenate records.
dynamic cursors are the ugliest...
the problem is with that section:
c_data varchar2(3000); --QUERY
c_cursor sys_refcursor; --CURSOR
c_record inputTableString%rowtype;
i used something like this:
TYPE t_data IS REF CURSOR;
cr_data t_data;
cr_data_rec inputTableString%ROWTYPE; --that table need to be exists in compile time
the rest are good i think
Have you considered pushing the whole declaration and loop into an anonymous block that will then get executed by EXECUTE IMMEDIATE? You can then simplify your looping construct to a simple FOR loop too.
I'm away from my database at the moment, so excuse any syntax glitches, but something like
CREATE OR REPLACE PROCEDURE calcHashCode (inputTableString IN varchar2) IS
c_data varchar2(30000); --QUERY
BEGIN
c_data := '
BEGIN
FOR aRec IN
(SELECT * FROM ' || inputTableString ||' )
LOOP
--do your stuff
END LOOP;
END';
execute immediate c_Data;
END;
/
It may not be pretty, and your "Stuff" may not easily be able to be fit into this construct, but it IS feasible
You can do all this kind of stuff with PL/SQL package DBMS_SQL, however it is definitely not for beginners and you should start with something easier.
Examples for DBMS_SQL
When I create the following procedure
create or replace procedure check_exec_imm(
tab IN VARCHAR2,
col IN VARCHAR2,
col_name IN VARCHAR2
)
IS
cv SYS_REFCURSOR;
col_value VARCHAR2(32767);
lv_query VARCHAR2(32767);
BEGIN
lv_query := 'SELECT ' ||col||
' FROM ' ||tab||
' WHERE (:1 = ''EUR'' OR :1 = ''USD'') and rownum <=1';
EXECUTE IMMEDIATE lv_query INTO col_value USING col_name ;
DBMS_OUTPUT.PUT_LINE('COLUMN VALUE : ' || col_value);
END;
When the procedure is executed, I'm getting the following error
ORA-01008: not all variables bound
ORA-06512: at "GRM_IV.CHECK_EXEC_IMM", line 18
ORA-06512: at line 2
When I give the bind argument col_name again as below, the procedure is running fine.
EXECUTE IMMEDIATE lv_query INTO col_value USING col_name, col_name ;
Why oracle is behaving differently in this procedure. Since, it is the same bind variable, one bind argument should be sufficient right..!!? Please explain where I'm getting my logic wrong.
There is "special" behaviour in Oracle: Repeated Placeholder Names in Dynamic SQL Statements
In an Anonymous Block or CALL Statement it is not required to repeat the bind values if the names are equal.
For example this Anonymous Block is working:
DECLARE
a NUMBER := 4;
b NUMBER := 7;
plsql_block VARCHAR2(100);
BEGIN
plsql_block := 'BEGIN calc_stats(:x, :x, :y, :x); END;';
EXECUTE IMMEDIATE plsql_block USING a, b; -- calc_stats(a, a, b, a)
END;
/
But this EXECUTE IMMEDIATE plsql_block USING a, b; does not work inside a Procedure.
The way you have referenced the column name through bind variable is not a preferred method as Nichoas pointed out. What you tried is called as native dynamic SQL using 'cleverer' bind variables.
In this method, you need to bind every parameter X times since you use it X times because they are all treated as separate variables.
Read more on binding to dynamic SQL.
#ethan and #ManiSankar I too had a same problem in my scenario as well. I solved this using some kind of brute force techinque. What i have done is
Before this
EXECUTE IMMEDIATE lv_query INTO col_value USING col_name ;
I have added replace condition in my code by replacing parameter with the required value then called "Execute Immediate" without "using" clause
lv_query := replace(lv_query, ':1',col_name);
EXECUTE IMMEDIATE lv_query INTO col_value;
I don't know this is optimal one but served purpose for what i am expecting..
Please advice if this one recommended or not...
For debugging purposes, I'd like to be able to "TO_CHAR" an Oracle PL/SQL in-memory table. Here's a simplified example, of what I'd like to do:
DECLARE
TYPE T IS TABLE OF MY_TABLE%ROWTYPE INDEX BY PLS_INTEGER;
V T;
BEGIN
-- ..
-- Here, I'd like to dbms_output V's contents, which of course doesn't compile
FOR i IN V.FIRST .. V.LAST LOOP
dbms_output.put_line(V(i));
END LOOP;
-- I want to omit doing this:
FOR i IN V.FIRST .. V.LAST LOOP
dbms_output.put_line(V(i).ID || ',' || V(i).AMOUNT ...);
END LOOP;
END;
Can this be achieved, simply? The reason I ask is because I'm too lazy to write this debugging code again and again, and I'd like to use it with any table type.
ok, sorry this isn't complete, but to followup with #Lukas, here's what I have so far:
First, instead of trying to create anydata/anytype types, I tried using XML extracted from a cursor...weird, but its generic:
CREATE OR REPLACE procedure printCur(in_cursor IN sys_refcursor) IS
begin
FOR c IN (SELECT ROWNUM rn,
t2.COLUMN_VALUE.getrootelement () NAME,
EXTRACTVALUE (t2.COLUMN_VALUE, 'node()') VALUE
FROM TABLE (XMLSEQUENCE (in_cursor)) t,
TABLE (XMLSEQUENCE (EXTRACT (COLUMN_VALUE, '/ROW/node()'))) t2
order by 1)
LOOP
DBMS_OUTPUT.put_line (c.NAME || ': ' || c.VALUE);
END LOOP;
exception
when others then raise;
end;
/
Now, to call it, you need a cursor, so I tried casting to cursor in pl/sql, something like:
open v_cur for select * from table(cast(v_tab as tab_type));
But depending on how v_tab is defined, this may or may not cause issues in pl/sql cast (using %rowtype in nested table def seems to give issues).
Anyway, you can build on this or refine it as you like. (and possibly use xmltable...)
Hope that helps