Need help in execute immediate update query - oracle

I have this query and it's not updating into the database. The given "where" clause is valid. When I run the query independently, it works fine but in this Procedure it's not working. There is no exception or no error. Could you guys help me in figuring out where the problem is?
EXECUTE IMMEDIATE 'UPDATE ' || dest || ' SET COUNTRY_CODE = :v1 WHERE col_id = :v2'
USING l_vc_CountryCode, l_vc_ColId;
if SQL%ROWCOUNT > 1 THEN
inserts := inserts + 1;
counter := counter + 1;
IF counter > 500 THEN
counter := 0;
COMMIT;
END IF;
END IF;
I didn't write the commit code before. Just to clarity.

I suppose that col_id is the primary key. So in the update statement
EXECUTE IMMEDIATE 'UPDATE ' || dest || ' SET COUNTRY_CODE = :v1 WHERE col_id = :v2'
USING l_vc_CountryCode, l_vc_ColId;
you are always updating at most one row and thus the condition
SQL%ROWCOUNT > 1
is never true ( 1 is not > 1 )
So if you don't have any other commit statement in your procedure, you will never commit those updates.
By the way: what is the purpose of this
if SQL%ROWCOUNT > 1 THEN
inserts := inserts + 1;
counter := counter + 1;
IF counter > 500 THEN
counter := 0;
COMMIT;
END IF;
END IF;
why don't you just commit at the end of your work?

The following code works okay (ie updates the row).
I suspect your error is elsewhere.
For example, if you don't initialise COUNTER, the increment will still leave it as null and it will never commit.
Or, l_vc_ColId may be the wrong datatype and suffering from an invalid conversion.
declare
v_emp_id number := 7839;
v_name varchar2(4) := 'DING';
v_tab varchar2(3) := 'EMP';
begin
execute immediate 'update '||v_tab||
' set ename = :v_name Where empno = :v_emp_id'
using v_name, v_emp_id;
dbms_output.put_line('C:'||sql%rowcount);
end;

you may want to reconsider your design if your using dynamic sql to change the "dest" table in thousands of updates.
Much better to know your dest and use bind variables for the where conditions. then you can commit every x rows using mod or similar:
if (mod(v_ctr, 1000) = 0) then
commit;
end if;
But for your example, Marcin is correct, if you are updating only 1 row at a time, then
if SQL%ROWCOUNT > 1
will never be true;
EDIT:
A simple example knowing your "dest" table:
declare
cursor sel_cur is
select col1, col2, from sourceTable where col3 = 'X';
v_ctr pls_integer := 0;
begin
for rec in sel_cur
loop
v_ctr := v_ctr + 1;
-- implicit bind variables used
update destTable
set col1 = rec.col1,
col2 = rec.col2
where col3 = 'Z';
if (mod(v_ctr, 1000) = 0) then
commit;
end if;
end loop;
exception
when others then rollback;
raise;
end;
If using dynamic SQL, a simple example using explicit bind variables (USING clause) from Oracle docs:
CREATE OR REPLACE PROCEDURE raise_emp_salary (column_value NUMBER,
emp_column VARCHAR2, amount NUMBER) IS
v_column VARCHAR2(30);
sql_stmt VARCHAR2(200);
BEGIN
-- determine if a valid column name has been given as input
SELECT COLUMN_NAME INTO v_column FROM USER_TAB_COLS
WHERE TABLE_NAME = 'EMPLOYEES' AND COLUMN_NAME = emp_column;
sql_stmt := 'UPDATE employees SET salary = salary + :1 WHERE '
|| v_column || ' = :2';
EXECUTE IMMEDIATE sql_stmt USING amount, column_value;
IF SQL%ROWCOUNT > 0 THEN
DBMS_OUTPUT.PUT_LINE('Salaries have been updated for: ' || emp_column
|| ' = ' || column_value);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('Invalid Column: ' || emp_column);
END raise_emp_salary;
/
For more reading, see here.
Hope this helps, happy coding

Execute immediate needs explicit commit. I guess you checked that ?

Related

Pass a table name and time stamp variable to a PL/SQL

I am writing a below PL/SQL code in SQL developer to delete data from a table with a timestamp column in the where condition. How can I modify this code to pass the table name and the timestamp value to values that I want based on what table and time records I want to delete the data from and create a stored procedure that can be reused.
DBMS_OUTPUT.ENABLE;
DECLARE
counter INTEGER := 0;
stop INTEGER;
BEGIN
dbms_output.put_line('START');
LOOP
counter := counter + 1;
DELETE my_schema.test
WHERE t = '10-JUN-20 04.33.46.000000000 AM'
AND ROWNUM <= 100000;
SELECT COUNT(*)
INTO stop
FROM my_schema.test
WHERE t = '10-JUN-20 04.33.46.000000000 AM';
EXIT WHEN stop <= 0;
COMMIT;
END LOOP;
dbms_output.put_line('Counter: ' || counter);
dbms_output.put_line('Left: ' || stop);
COMMIT;
END;
Adapting your anonymous to a stored procedure will, as indicated, require converting it to dynamic SQL. Always more difficult. And subject to SQL injection. For this you should validate string replacement parameters. I have a couple other changes:
Pass the desired as a timestamp, not a string, this allows/forces the
calling routine to determine the format and necessary conversion, if
any.
Added a parameter for column name as well. This frees naming columns
from the requirement of the procedure.
There is no need to count remaining items. Your loop processes until
that value reaches 0, but this can be determined by the number of
rows deleted on the last pass. Delete sets sql%rowcount to the number
of rows deleted. When the pass deletes 0 rows the process is
complete.
Removed the results display and the commit from the procedure, again
offloading this to the caller.
create or replace
procedure delete_values_by_timestamp
( p_table_name in varchar2
, p_column_name in varchar2
, p_timestamp in timestamp
, p_result_msg out varchar2
)
IS
table_name_parameter_invalid exception;
pragma exception_init(table_name_parameter_invalid, -44002);
column_name_parameter_invalid exception;
pragma exception_init(column_name_parameter_invalid, -44003);
k_nl constant varchar2(1) := chr(10);
k_max_delete_per_interation constant integer := 100000;
k_base_delete varchar2(256) :=
'delete from <table_name>' ||
' where <column_name> <= :1' ||
' and rownum <= :2';
v_delete_sql varchar2 (256) ;
v_rows_deleted integer := 0;
begin
v_delete_sql := replace(replace(k_base_delete,'<table_name>', dbms_assert.sql_object_name(p_table_name))
,'<column_name>',dbms_assert.simple_sql_name(p_column_name));
dbms_output.put_line('Running SQL:' || k_nl || v_delete_sql);
loop
execute immediate v_delete_sql using p_timestamp, k_max_delete_per_interation;
exit when sql%rowcount = 0;
v_rows_deleted :=v_rows_deleted + sql%rowcount;
end loop;
if v_rows_deleted = 0
then
p_result_msg := 'No Data Found';
else
p_result_msg := 'Number of Rows Deleted ' || to_char(v_rows_deleted);
end if;
exception
when table_name_parameter_invalid then
raise_application_error(-20199,'Invalid Table Name (' || p_table_name || ') specified.');
when column_name_parameter_invalid then
raise_application_error(-20198,'Invalid Column Name (' || p_column_name || ') specified.');
end delete_values_by_timestamp;
See example: In the example I reduce the number of rows deleted on each iteration from 100000 to 20. An additionally enhancement would be to pass the number of rows for each iteration as a parameter.
I couldn't test it but you could create a function whcih takes the table name and the timestamp as parameter.
As long you want to delete every record with the given timestamp you don't need to loop for each record.
This function should be just an example.
FUNCTION delete_values_by_timestamp (p_table_name IN VARCHAR2 DEFAULT NULL,
p_timestamp IN VARCHAR2 DEFAULT NULL)
RETURN VARCHAR2
IS
v_count NUMBER := 0;
v_query VARCHAR2 (500) := '';
BEGIN
IF p_table_name IS NOT NULL
THEN
IF p_timestamp IS NOT NULL
THEN
v_query := 'SELECT COUNT(*)
FROM my_schema.' || p_table_name | '
WHERE t = TO_DATE(''' || p_timestamp ||''', ''DD.MM.YYYY HH24:MI:SS.SSSS'')';
EXECUTE IMMEDIATE v_query INTO v_count;
IF v_count > 0
THEN
v_query := 'DELETE FROM my_schema.' || p_table_name || '
WHERE t = TO_DATE(''' || p_timestamp ||''', ''DD.MM.YYYY HH24:MI:SS.SSSS'')';
EXECUTE IMMEDIATE v_query;
ELSE
RETURN 'NO RECORDS FOUND!';
END IF;
ELSE
RETURN 'TIMESTAMP EMPTY!';
END IF;
ELSE
RETURN 'TABLE NAME EMPTY!';
END IF;
END;

Can't Solve ora-06550 pls-00103 ERROR in Qyery

I wrote the following query to Update Table EMPSHIFT VALUES From SCHEDULEEMPLOYEES
Table but get the following Error ora-06550 pls-00103 and can't solve it
so what is the problem
DECLARE
day_date DATE:=TO_DATE('01/04/2017','DD/MM/YYYY');
BEGIN
LOOP
FOR employees IN (SELECT EmpID FROM EMPSHIFT)
LOOP
EXECUTE IMMEDIATE
' UPDATE EMPSHIFT SET EMPSHIFT."'||TO_CHAR(day_date)||'" =
(
SELECT SCHEDULEEMPLOYEES.SCHEDULEID ||'-'|| SCHEDULEEMPLOYEES.SHIFTS
FROM SCHEDULEEMPLOYEES INNER JOIN EMPSHIFT ON SCHEDULEEMPLOYEES.EMPLOYEEID = EMPSHIFT.EMPLOYEEID
WHERE SCHEDULEEMPLOYEES.DAYDATE = '||TO_CHAR(day_date)||' and EMPSHIFT.EMPLOYEEID = ' || employees.EmpID ||'
)
WHERE EMPSHIFT.EMPLOYEEID =' ||employees.EmpID ||';';
day_date = day_date + 1;
EXIT
WHEN day_date >TO_DATE('30/04/2017','DD/MM/YYYY');
END LOOP;
END LOOP;
END;
1) As others mentioned,"day_date = day_date + 1;" has missing the correct assignment operator ":=".
2) The "EXECUTE..." part is not required here. Why are You using it?
3) What is your goal? The current structure looks "weird". The first loop statement has no control of the flow, only the inner one has, but its loop iterations is only based on the employees count, not the dates.
4) Is the update statement correct? I mean the "set empshift.<..>. I doubt, he has an attribute named "01/04/2017".
Created an example,:
declare
l_day_date date:=to_date('01/04/2017','DD/MM/YYYY');
l_res varchar2(400);
begin
loop
for l_emp in (select emp_id from empshift_test_v)
loop
dbms_output.put_line('the emp_id is :'||l_emp.emp_id);
--update empshift_test_v etv
--set etv.empshift_code/*<correct_att_name>*/ = (
select
nvl((select
sct.sch_id ||'-'|| sct.shifts shift_code
from
SCHEDULEEMPLOYEES_TEST_V sct,
empshift_test_v etv1
where
sct.day_date = l_day_date and
sct.emp_id = etv1.emp_id and
etv1.emp_id = l_emp.emp_id),'no_info')
into
l_res
from
empshift_test_v etv
where
etv.emp_id = l_emp.emp_id;
dbms_output.put_line('day_date is :'||to_char(l_day_date,'DD/MM/YYYY'));
dbms_output.put_line('l_res is :'||l_res);
end loop;
l_day_date := l_day_date + 1;
exit when l_day_date >to_date('30/04/2017','DD/MM/YYYY');
end loop;
end;
WHERE views "EMPSHIFT_TEST_V" and "SCHEDULEEMPLOYEES_TEST_V" has info like:
enter image description here
Hope it helps.
UPDATE:
Modified it according to you data.
declare
l_day_date date:=to_date('01/04/2017','DD/MM/YYYY');
l_res number;
l_stmt varchar2(4000);
begin
loop
for l_emp in (select emp_id from empshift_test)
loop
dbms_output.put_line('the emp_id is :'||l_emp.emp_id);
begin
select
sct.shift
into
l_res
from
SCHEDULEEMPLOYEES_TEST sct,
empshift_test etv
where
sct.daydate = l_day_date and
sct.emp_id = etv.emp_id and
etv.emp_id = l_emp.emp_id;
exception
when NO_DATA_FOUND then
l_res := 0;
end;
dbms_output.put_line('day_date is :'||to_char(l_day_date,'DD/MM/YYYY'));
dbms_output.put_line('l_res is :'||l_res);
if l_res > 0 then
l_stmt := 'update empshift_test emp
set emp."'||to_char(l_day_date,'DD/MM/YYYY')||'" = '||l_res||'
where emp.emp_id = '||l_emp.emp_id||';';
dbms_output.put_line('l_stmt is :'||l_stmt);
execute immediate l_stmt;
end if;
end loop;
l_day_date := l_day_date + 1;
exit when l_day_date >to_date('30/04/2017','DD/MM/YYYY');
end loop;
end;
But there is a catch: if you run the DML statement manually - it works, but with execute immediate - it throws error ora-00933. He cant read the number column for some reason.
Another good stack question to solve:)
So the plan is:
1) Change the table structure; or
2) Solve the problem, when calling a attribute, named as number (with symbols like "/") using execute immediate.

Automatically generate sequences and triggers for all tables in Oracle

In my schema, I've migrated about 250 tables from SQL Server to Oracle. The thing is, no sequences or triggers have been created for any of these tables.
Is there an easy way to generate all the table sequences and triggers rather than manually doing this for every table?
An example of a sequence I need would be:
CREATE SEQUENCE "SYSTEM"."SEC_USERS_ID_SEQ"
MINVALUE 0 MAXVALUE 999999999999999999999999
INCREMENT BY 1
START WITH 23
CACHE 20
NOORDER NOCYCLE NOPARTITION;
And the trigger:
create or replace TRIGGER SEC_USERS_TRIG
before INSERT
ON "SYSTEM"."SEC_USERS"
FOR EACH row
BEGIN
IF inserting THEN
IF :NEW."ID" IS NULL THEN
SELECT SEC_USERS_ID_SEQ.nextval INTO :NEW."ID" FROM dual;
END IF;
END IF;
END;
We can generate scripts using the Oracle data dictionary views (the equivalent of MSSQL INFORMATION_SCHEMA). Find out more.
This example generates CREATE SEQUENCE statements. I have followed your example and accepted the default values, which don't need to be coded. The sequence name is derived from table name concatenated with column name and suffixed with "_SEQ". Watch out for Oracle's thirty character limit on object names!
This loop dynamically queries the table to get the current maximum value of the Primary Key column, which is used to derive the STARTS WITH clause.
declare
curr_mx number;
begin
for lrec in ( select ucc.table_name
, ucc.column_name
from user_constraints uc
join user_cons_columns ucc
on ucc.table_name = uc.table_name
and ucc.constraint_name = uc.constraint_name
join user_tab_columns utc
on utc.table_name = ucc.table_name
and utc.column_name = ucc.column_name
where uc.constraint_type = 'P' -- primary key
and utc.data_type = 'NUMBER' -- only numeric columns
)
loop
execute immediate 'select max ('|| lrec.column_name ||') from ' ||lrec.table_name
into curr_mx;
if curr_mx is null then
curr_mx := 0;
end if;
dbms_output.put_line('CREATE SEQUENCE "'|| user || '"."'
|| lrec.table_name ||'_'|| lrec.column_name || '_SEQ" '
||' START WITH ' || to_char( curr_mx + 1 ) ||';'
);
end loop;
end;
/
This code uses DBMS_OUTPUT, so you can spool it to a file for later use. If you're using an IDE like SQL Developer you may need to enable DBMS_OUTPUT. Follow the guidance in this StackOverflow answer.
If you can guarantee that all your tables have a primary key which is a numeric column called ID then you can simplify the select statement. Contrariwise, if some of your primary keys are compound constraints you will need to handle that.
Obviously I plumped for generating sequences because they're simpler. Writing the more complex trigger implementation is left as an exercise for the reader :)
thanks for the script. I altered it a little bit and did the trigger implementation. Feel free to use it.
declare
curr_mx number;
counter number;
seq_name varchar2 (30);
trigger_name varchar2 (30);
begin
for lrec in ( select ucc.table_name
, ucc.column_name
from user_constraints uc
join user_cons_columns ucc
on ucc.table_name = uc.table_name
and ucc.constraint_name = uc.constraint_name
join user_tab_columns utc
on utc.table_name = ucc.table_name
and utc.column_name = ucc.column_name
where uc.constraint_type = 'P' -- primary key
and utc.data_type = 'NUMBER' -- only numeric columns
)
loop
execute immediate 'select (max ('|| lrec.column_name ||')+1) from ' ||lrec.table_name
into curr_mx;
IF curr_mx is null THEN
curr_mx := 0;
END IF;
IF counter is null THEN
counter := 0;
END IF;
/* check length of sequence name, 30 is max */
IF length(lrec.table_name ||'_'|| lrec.column_name || '_SEQ') > 30 THEN
IF length(lrec.column_name || '_SEQ') > 30 THEN
seq_name := counter || '_PKA_SEQ';
ELSE
seq_name := lrec.column_name || '_SEQ';
END IF;
ELSE
seq_name := lrec.table_name ||'_'|| lrec.column_name || '_SEQ';
END IF;
/* check length of trigger name, 30 is max */
IF length(lrec.table_name || '_PKA_T') > 30 THEN
trigger_name := counter || '_PKA_T';
ELSE
trigger_name := lrec.table_name || '_PKA_T';
END IF;
counter := counter +1;
dbms_output.put_line(
'CREATE SEQUENCE "' || seq_name || '"'
||' START WITH ' || to_char( curr_mx + 1 ) ||';'
);
dbms_output.put_line('/');
dbms_output.put_line(
'CREATE OR REPLACE TRIGGER "' || trigger_name || '"'
|| ' BEFORE INSERT ON "' || lrec.table_name || '"'
|| ' FOR EACH ROW '
|| ' BEGIN '
|| ' :new."' || lrec.column_name || '" := "' || seq_name || '".nextval;'
|| ' END;'
);
dbms_output.put_line('/');
end loop;
end;
I also checked if the names of the sequences and triggers are longer than 30 characters because oracle won´t accept these.
EDIT:
Had to put '/' after each line so you can execute all statements at one run.

can I update all the columns in one set

I am just asking if there is an alternative way for the below update.
update test_table set
col 1 = trim(col1),
col 2 = trim(col2),
col 3 = trim(col3),
col 4 = trim(col4),
col 5 = trim(col5),
In my real table there are about 20 columns, maybe more. Is there a fast way where I can TRIM all the columns in one shot ?
seeking to a similar query if exists:
update test_table set all columns = trim(columns)
Edit:
I can do like this :
UPDATE test_table
SET (col1,col2,col3,col4,col5) = (
SELECT col1,col2,col3,col4,col5
FROM test_table)
Is there other way ?
There is no SQL syntax to give you a quick and easy way of doing this that I know of.
One way you could do it is to write a PL/SQL block to select the column names of a given table from a system view e.g. user_tab_cols and write them to a variable to build up the SQL code to run dynamically. To be honest, unless you have a number of tables to do this on or loads of columns it would probably be easier to write the query manually.
EDIT - Here is the code incase you want it
declare
v_table varchar2(50) := 'test_table';
v_sql varchar2(2000) := null;
cursor c_cols(p_table varchar2) is
select c.column_name as col
from user_tab_cols c
where c.table_name = upper(p_table)
order by c.column_id;
begin
-- write first line of sql...
v_sql := 'update ' || v_table || ' set' || chr(10);
-- loop through col names to add each col into update statement...
for l_c_cols in c_cols(v_table) loop
v_sql := v_sql || l_c_cols.col || ' = trim(' || l_c_cols.col || '),' || chr(10);
end loop;
-- remove last comma...
v_sql := substr(v_sql,1,length(v_sql)-2);
-- run dynamic sql...
dbms_output.put_line(v_sql);
begin
execute immediate v_sql;
commit;
end;
end;
Let me know if you have any questions on this.

How to change the Oracle Sequence using loop?

Hope someone can help. When I tried to insert something into a table it give me error saying the primary key is already existed. So I need to reset my sequence so that it is always max(id)+1.
The table is called 'People' with 2 columns (ID, Name). The sequence is called SEQ.
I am thinking of doing a loop. To run select SEQ.nextval from dual for n times. this n= max(id)-SEQ.currval
Wwill this work? and how Can I put it into the syntax?
Thanks a lot.
declare
l_MaxVal pls_integer;
l_Currval pls_integer default - 1;
begin
select max(id)
into l_MaxVal
from people;
while l_Currval < l_Maxval
loop
select my_seq.nextval
into l_Currval
from dual;
end loop;
end;
If this is a one off, you can use the alter sequence
alter sequence sequenceName increment by val ;
whereas val is +1 to the maximum
then call get nextVal, then set the increment back to 1.
I threw the below together to show you how it can be done without looping.
create sequence changeValue start with 18 increment by 1 nocache ;
select changeValue.nextval from dual ;
/
NEXTVAL
----------------------
18
set serveroutput on
declare
maxVal number := 24 ;
curVal number ;
diffVal number ;
incrementVal number ;
procedure alterSequence(seqName in varchar2, incVal in number) as
s varchar2(500);
begin
s := 'alter sequence ' || seqName || ' increment by ' || incVal ;
dbms_output.put_line(s);
execute immediate s;
end alterSequence;
begin
--(done in 11gr2 so if in earlier version select into)
curVal := changeValue.currval ;
dbms_output.put_line('curValue=>' || curVal );
diffVal := maxVal - curVal ;
dbms_output.put_line('diffVal=>' || diffVal );
alterSequence ( 'changeValue' , diffVal + 1 );
incrementVal := changeValue.nextval ;
dbms_output.put_line('incrementVal=>' || incrementVal );
alterSequence ( 'changeValue' , 1 );
curVal := changeValue.currval ;
dbms_output.put_line('curValue=>' || curVal );
end ;
/
curValue=>18
diffVal=>6
alter sequence changeValue increment by 7
incrementVal=>25
alter sequence changeValue increment by 1
curValue=>25
or better yet, as #Dave suggests, just drop and recreate the sequence with the acceptable Start With value.
With this one you can synchronize the sequence whatever it is forward or behind the max of the ID.
Just need to change the parameters in the final of the code.
declare
procedure SYNC_SEQUENCE
( P_IN_SEQ in varchar2
, P_IN_TABLE in varchar2
, P_IN_ID in varchar2
)
is
LV_MAXVAL number := 0;
LV_CURRVAL number := -1;
LV_AUX NUMBER;
begin
execute immediate
'select max('||P_IN_ID||')
from '||P_IN_TABLE into LV_MAXVAL;
execute immediate
'select '||P_IN_SEQ||'.nextval
from dual ' into LV_CURRVAL;
if LV_MAXVAL < LV_CURRVAL then
LV_AUX := (LV_CURRVAL - LV_MAXVAL);
execute immediate
'ALTER SEQUENCE '||P_IN_SEQ||' INCREMENT BY -'||LV_AUX;
execute immediate
'SELECT '||P_IN_SEQ||'.NEXTVAL FROM dual' INTO LV_AUX;
execute immediate
'ALTER SEQUENCE '||P_IN_SEQ||' INCREMENT BY 1';
end if;
while LV_CURRVAL < LV_MAXVAL
loop
execute immediate
'select '||P_IN_SEQ||'.nextval
from dual ' into LV_CURRVAL;
end loop;
end SYNC_SEQUENCE;
begin
SYNC_SEQUENCE('MY_SEQUENCIE_NAME','MY_TABLE_NAME','MY_FIELD_ID_NAME');
end;
/

Resources