How to write dynamic sql in Oracle Stored procedure? - oracle

Basically in my update sql query column names going to be dynamic like
update bi_employee set <this_is_dynamic_column> where emp_id = 12
Below is the stored procedure that I have written so far.
CREATE OR replace PROCEDURE Sp_run_employee_updates
IS
CURSOR c_emp IS
SELECT *
FROM BI_EMPLOYEE_UPDATE
WHERE EFFECTIVE_DATE = To_date('30-Apr-2012', 'dd-mm-yy');
BEGIN
FOR employee_update IN c_emp LOOP
declare update_sql varchar2(225);
update_sql := 'UPDATE BI_EMPLOYEE SET '
|| employee_update.column_name
|| '= employee_update.new_value WHERE emp_id = '
|| employee_update.employee_id;
END LOOP;
END;
Its giving me foloowing errors
Error(17,13): PLS-00103: Encountered the symbol "=" when expecting one of the following: constant exception table long double ref char time timestamp interval date binary national character nchar The symbol "" was substituted for "=" to continue.
Error(22,5): PLS-00103: Encountered the symbol "UPDATE" when expecting one of the following: begin function pragma procedure subtype type current cursor delete exists prior The symbol "begin" was substituted for "UPDATE" to continue.
Error(31,6): PLS-00103: Encountered the symbol ";" when expecting one of the following: loop

a- This should be like this:
to_date('30-Apr-2012','dd-mon-yyyy');
b- You can do it like this:
CREATE OR REPLACE
PROCEDURE SP_RUN_EMPLOYEE_UPDATES IS
update_sql varchar2(225);
CURSOR c_emp IS
SELECT *
FROM BI_EMPLOYEE_UPDATE
WHERE EFFECTIVE_DATE = to_date('30-Apr-2012','dd-mon-yyyy');
BEGIN
FOR employee_update in c_emp LOOP
update_sql := 'UPDATE BI_EMPLOYEE SET ' || employee_update.column_name ||
'= :1 WHERE emp_id = :2' ;
execute immediate update_sql using employee_update.new_value, employee_update.employee_id;
END LOOP;
END SP_RUN_EMPLOYEE_UPDATES;

Related

Is there a fast PLSQL function for returning a comma-delimited list of column names for a given schema.table?

I'm trying to set up some simple utilities in a PL/SQL environment. Eventually, I'd expect the hardcoded MYTABLE to be replaced by a bind variable.
I've started with the following, which returns an error:
DECLARE
TYPE colNames_typ IS TABLE OF all_tab_cols.column_name%type index by PLS_INTEGER;
v_ReturnVal colNames_typ;
v_sql VARCHAR2(32000);
BEGIN
v_sql :='SELECT column_name FROM all_tab_cols WHERE table_name = ''MYTABLE'' ' ;
EXECUTE IMMEDIATE (v_sql)
INTO v_returnVal;
-- Convert assoc array to a comma delimited list.
END;
The error returned:
PLS-00597: expression 'V_RETURNVAL' in the INTO list is of wrong type
I cant think of a more 'right' type than a table of entries with the exact same variable type as the source.
Any help would be awesome!
Thanks
Is there a fast PLSQL function for returning a comma-delimited list of column names for a given schema.table?
Use LISTAGG:
DECLARE
v_owner ALL_TAB_COLUMNS.OWNER%TYPE := 'SCHEMA_NAME';
v_table_name ALL_TAB_COLUMNS.TABLE_NAME%TYPE := 'TEST_DATA';
v_columns VARCHAR2(32000);
BEGIN
SELECT LISTAGG( '"' || column_name || '"', ',' )
WITHIN GROUP ( ORDER BY COLUMN_ID )
INTO v_columns
FROM all_tab_columns
WHERE owner = v_owner
AND table_name = v_table_name;
DBMS_OUTPUT.PUT_LINE( v_columns );
END;
/
(Note: you also need to pass the owner or, if you have two tables with identical names in different schemas then, you will get columns for both.)
(Note 2: I am assuming you want a list of column names to put into a dynamic query; if so, then you want to surround the column identifiers with double-quotes. If you don't and a column identifier is case-sensitive then you will get an incorrect name as Oracle will implicitly convert unquoted identifiers to upper case when it parses them in a query. If you don't want the quotes then use SELECT LISTAGG( column_name, ',' ).)
Which, if you have the table:
CREATE TABLE test_data (
A NUMBER,
B DATE,
C VARCHAR2(20),
E TIMESTAMP,
Z INTERVAL DAY TO SECOND,
Q CHAR(5)
);
Outputs:
"A","B","C","E","Z","Q"
db<>fiddle here
Not sure if this is what is being asked:
create or replace function get_cols_string(target_owner all_tab_cols.owner%type, target_table_name all_tab_cols.table_name%type) return varchar2 is
outputString varchar2(32767);
oneMore boolean := false;
BEGIN
for current_col in
(SELECT column_name FROM all_tab_cols
WHERE owner = target_owner and table_name = target_table_name) loop
if(oneMore) then
outputString := outputString || ', ';
end if;
outputString := outputString || current_col.column_name;
oneMore := TRUE;
end loop;
return outputString;
END;
/
Rem Test the above with simple cases
create table tab1 (c1 number);
create table tab2 (c1 number, c2 number);
set serveroutput on
declare
owner_name varchar2(32767) := 'SYS';
table_name varchar2(32767) := 'TAB1';
begin
dbms_output.put_line('For: ' || owner_name || '.' || table_name);
dbms_output.put_line(get_cols_string(owner_name, table_name));
end;
/
declare
owner_name varchar2(32767) := 'SYS';
table_name varchar2(32767) := 'TAB2';
begin
dbms_output.put_line('For: ' || owner_name || '.' || table_name);
dbms_output.put_line(get_cols_string(owner_name, table_name));
end;
/
declare
owner_name varchar2(32767) := 'SYS';
table_name varchar2(32767) := 'ALL_TAB_COLS';
begin
dbms_output.put_line('For: ' || owner_name || '.' || table_name);
dbms_output.put_line(get_cols_string(owner_name, table_name));
end;
/
You asked "is there a reason why the previous approach failed" - well yes. The error stems from Oracle being a very strict typing thus making your assumption that there is not "a more 'right' type than a table of entries with the exact same variable type as the source" false. A collection (table) of type_a is not a variable type_a. You attempted to store a variable of type_a into a collection of type_a, thus giving you the wrong type exception.
Now that does not mean you were actually far off. You wanted to store a collection of type_a variables, returned by the select, into a collection of type_a. You can do that, you just need to let Oracle know. You accomplish it with BULK COLLECT INTO. The following shows that process and creates your CSV of column names.
Note: #MTO posted the superior solution, this just shows you how your original could have been accomplished. Still it is a useful technique to keep in your bag of tricks.
declare
type colnames_typ is table of all_tab_cols.column_name%type;
k_comma constant varchar2(1) := ',';
v_returnval colnames_typ;
v_sql varchar2(32000);
v_sep varchar2(1) := ' ';
v_csv_names varchar2(512);
begin
v_sql := 'select column_name from all_tab_cols where table_name = ''MYTABLE'' order by column_id';
execute immediate (v_sql)
bulk collect into v_returnval;
-- Convert assoc array to a comma delimited list.
for indx in 1 .. v_returnval.count
loop
v_csv_names := v_csv_names || v_sep || v_returnval(indx);
v_sep :=k_comma;
end loop;
v_csv_names := trim(v_csv_names);
dbms_output.put_line(v_csv_names);
end;

Execute Immediate Statement issue - Variable field name and table name

Hi there I am trying to build a function to reset a sequence to synch with table ID's which have gotten out of synch with the sequence. Function is as follows:
create or replace
FUNCTION P_FNC_SEQUENCERESET(sourceTable IN VARCHAR2, idField IN VARCHAR2, seqname VARCHAR2) RETURN NUMBER
IS
ln NUMBER;
ib NUMBER;
maxId NUMBER;
newValue NUMBER;
diffValue NUMBER;
interimValue NUMBER;
sqlStmt VARCHAR2(2000);
BEGIN
-- Get the maximum of the id field
EXECUTE IMMEDIATE 'SELECT MAX(' || idField || ') INTO ' || maxId || ' FROM ' || sourceTable;
...code continues...
My understanding of the EXECUTE IMMEDIATE statement leads me to believe that this should be possible, however when executed I get this error:
ORA-00936: missing expression
ORA-06512: at "PSALERT_ADMIN.P_FNC_SEQUENCERESET", line 16
ORA-06512: at line 11
It would need to look something like this:
EXECUTE IMMEDIATE 'SELECT ' || idField ||' FROM ' || sourceTable into maxid;
the keyword "into" is not part of the string that is dynamically executed, but it is part of the syntax of the "execute immediate" statement
https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/executeimmediate_statement.htm

oracle returning ID in execute immediate giving "missing keyword" error

Following is an oracle procedure
create or replace
PROCEDURE INSERT_COMMON(
ENTITY_NAME IN VARCHAR2
, INSERT_QUERY IN varchar2
)
AS
NEW_ID NUMBER;
BEGIN
-- execute insert
DBMS_OUTPUT.PUT_LINE('INSERT_QUERY: ' || INSERT_QUERY);
-- execute IMMEDIATE INSERT_QUERY returning ID into NEW_ID;
-- above gives me a syntax error so using as below
execute IMMEDIATE INSERT_QUERY || ' returning ID into NEW_ID';
DBMS_OUTPUT.PUT_LINE('NEW_ID: ' || NEW_ID);
END INSERT_COMMON;
and Pl/SQL I am using
DECLARE
ENTITY_NAME VARCHAR2(200);
INSERT_QUERY VARCHAR2(200);
BEGIN
ENTITY_NAME := 'company';
INSERT_QUERY := 'INSERT INTO COMPANY (NAME) VALUES (''A Company 2'')';
INSERT_COMMON(ENTITY_NAME,INSERT_QUERY);
END;
This gives me following error
Error report:
ORA-00905: missing keyword
ORA-06512: at "SYSTEM.INSERT_COMMON", line 20
ORA-06512: at line 8
00905. 00000 - "missing keyword"
However, I have tested and following works fine
DECLARE
NEW_ID NUMBER;
BEGIN
INSERT INTO COMPANY (NAME) VALUES ('A Company 2') returning ID into NEW_ID;
DBMS_OUTPUT.PUT_LINE('NEW_ID: ' || NEW_ID);
END;
You need to specify a bind variable you're returning into in the dynamic sql statement, but you also then need to add the returning into <variable> clause to the execute immediate statement.
I believe (untested, since you didn't provide the statements to set up your table and associated triggers) that the following should sort your issue:
create or replace procedure insert_common (entity_name in varchar2,
insert_query in varchar2)
as
new_id number;
begin
-- execute insert
dbms_output.put_line ('INSERT_QUERY: ' || insert_query);
-- above gives me a syntax error so using as below
execute immediate insert_query || ' returning ID into :NEW_ID' returning into new_id;
dbms_output.put_line ('NEW_ID: ' || new_id);
end insert_common;
/

Cursor Operation in Netezza

CREATE OR REPLACE PROCEDURE SP_NEW_PROCEDURE1( )
RETURNS REFTABLE(employees)
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
l_conditions varchar(1000);
p_rec RECORD;
BEGIN
FOR P_REC IN select empid, mgrid, empname, salary from employees where mgrid = 7
LOOP
l_conditions := 'insert into ' ||
REFTABLENAME ||
' VALUES (' ||
P_REC.EMPID ||
',' ||
P_REC.MGRID ||
',' ||
P_REC.EMPNAME ||
',' ||
P_REC.SALARY ||
' ) ; ' ;
execute immediate l_conditions;
l_conditions := ' ';
END LOOP;
RETURN REFTABLE;
END;
END_PROC;
When I run this:
select SP_NEW_PROCEDURE1()
I get the errors:
ERROR [01000] NOTICE: Error occurred while executing PL/pgSQL function SP_NEW_PROCEDURE1
ERROR [01000] NOTICE: line 24 at execute statement
ERROR [42S22] ERROR: Attribute 'DAN' not found
Can someone help whats wrong ...thanks
This has nothing do with the cursor itself, and everything to do with how you are building your dynamical SQL string.
When building dynamic SQL in a Netezza stored procedure, you can use the quote_ident and quote_literal helper functions to let the system know whether you are passing it a literal, or whether you are passing it an identifier. There is an example in the online documentation here. Essentially all they do is figure out the escaped quotation notation needed.
Since you are trying to put the values stored in the columns of your P_REC record into the VALUES part of an insert statement, you could use quote_literal like this:
CREATE OR REPLACE PROCEDURE SP_NEW_PROCEDURE1( )
RETURNS REFTABLE(employees)
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
l_conditions varchar(1000);
p_rec RECORD;
BEGIN
FOR P_REC IN select empid, mgrid, empname, salary from employees where mgrid = 7
LOOP
l_conditions := 'insert into ' ||
REFTABLENAME ||
' VALUES (' ||
quote_literal(P_REC.EMPID) ||
',' ||
quote_literal(P_REC.MGRID) ||
',' ||
quote_literal(P_REC.EMPNAME) ||
',' ||
quote_literal(P_REC.SALARY ) ||
' ) ; ' ;
execute immediate l_conditions;
l_conditions := ' ';
END LOOP;
RETURN REFTABLE;
END;
END_PROC;
That being said, using a cursor to loop over records to insert a row one at a time is horribly inefficient in an MPP database like Netezza. Assuming this question is a follow-on from your question about an alternative to a recursive CTE to explore hierarchies, there's nothing wrong with looping in general, but try to avoid doing it record by record. Here is a version that will exploit the MPP nature of the system. For the record, if you are going to return your result set to a REFTABLE, then your only choice is Dynamic SQL.
CREATE OR REPLACE PROCEDURE SP_NEW_PROCEDURE1( )
RETURNS REFTABLE(employees)
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
l_conditions varchar(1000);
p_rec RECORD;
BEGIN
-- FOR P_REC IN select empid, mgrid, empname, salary from employees where mgrid = 7
-- LOOP
l_conditions := 'insert into ' ||
REFTABLENAME ||
' select empid, mgrid, empname, salary from employees where mgrid = 7 ; ' ;
execute immediate l_conditions;
l_conditions := ' ';
-- END LOOP;
RETURN REFTABLE;
END;
END_PROC;
I suspect that you are building a query that is meant to insert a literal 'DAN' but which does not include the required quote marks, hence it is referencing DAN -- the optimiser is therefore trying to find an attribute of that name.
So the fix is to include the quotation marks when you build the SQL insert statement, or (preferably) to just use static SQL to insert the values instead of execute immediate.
When in doubt, always look at the data, as this would probably have been obvious to you if you inspected the value of l_conditions.

SQLPLUS How can I fix this procedure to make it work correctly?

Alright. I copied this code from class, but I must have been half asleep at the time because it doesn't actually work.
Create or replace procedure display_row is
(p_itemid IN item.itemid%TYPE) is
v_itemid Number;
v_itemdesc varchar2(30);
v_category varchar2(30);
Begin
Select *
Into v_itemid, v_itemdesc, v_category
From item
Where itemid = p_itemid;
Dbms_output.put_line(v_itemid || ' ' || v_itemdesc || ' ' || v_category);
End;
How can I fix this procedure so that it actually works?
Here's the user errors:
LINE POSITION TEXT
2 6 PLS-00103: Encountered the symbol "(" when expecting one of
the following:
begin function package pragma procedure subtype type use
<an identifier> <a double-quoted delimited-identifier> fo
rm
current cursor external language
2 37 PLS-00103: Encountered the symbol "IS" when expecting one of
the following:
return
Remove the is operator from first sentence
Create or replace procedure display_row is
<--Here
Your procedure should look like
Create or replace procedure display_row
(p_itemid IN item.itemid%TYPE) is
v_itemid Number;
v_itemdesc varchar2(30);
v_idemdesc varchar2(30);
Begin
Select *
Into v_itemid, v_itemdesc, v_category
From item
Where itemid = p_itemid;
Dbms_output.put_line(v_itemid || ' ' || v_itemdesc || ' ' || v_category);
End;

Resources