Execute immediate error in Oracle - oracle

I want to execute the following command through a cursor where I have column name and column values in a different table.
However the script fails with following error message.
ORA-00933: SQL command not properly ended
execute immediate 'UPDATE EMP_DETAILS_T SET ' || EMP_REC.COLUMN_NAME || ' = ' || '''' || EMP_REC.VALUE ||''' ' || ' where employee_id = ' || ''''|| EMP_REC.EMPLOYEE_ID || ''' ' ;
entire command is as follow.
DECLARE
CURSOR CR_EMP_ATT IS
SELECT COLUMN_NAME,
VALUE,
EMPLOYEE_ID
FROM UPDATE_DATA_T
WHERE EMPLOYEE_ID = P_EMP_ID;
BEGIN
FOR CR_EMP IN CR_EMP_ATT LOOP
BEGIN
EXECUTE IMMEDIATE 'Update EMP_DETAILS_T set ' || CR_EMP.COLUMN_NAME || ' = ' ||''''||CR_EMP.VALUE||''''|| ' where employee_id = ' ||''''|| P_EMP_ID||'''';
DBMS_OUTPUT.PUT_LINE('temp table updated');
END;
END LOOP;
END;
I have placed this query in a procedure to run when needed by employee.

Your value column includes values which contain a single quote. (The ampersand isn't going to be a problem here). You can see how that breaks the statement by displaying it before execution:
dbms_output.put_line('Update EMP_DETAILS_T set ' || CR_EMP.COLUMN_NAME
|| ' = ' ||''''||CR_EMP.VALUE||''''|| ' where employee_id = '
||''''|| P_EMP_ID||'''');
... which reveals:
Update EMP_DETAILS_T set SOME_COLUMN = 'Standard & Poor's' where employee_id = 'ABC123'
... which has unbalanced single quotes; you can even see that in the syntax highlighting.
The simplest fix is to use bind variables for the two values - you can't for the column name - and pass the actual values with the USING clause:
EXECUTE IMMEDIATE 'Update EMP_DETAILS_T set ' || CR_EMP.COLUMN_NAME
|| ' = :VALUE where employee_id = :EMP_ID'
USING CR_EMP.VALUE, P_EMP_ID;

Does your value CR_EMP.VALUE contain any ' character?
That would be another reason to use bind variables, i.e.
EXECUTE IMMEDIATE 'Update EMP_DETAILS_T set '||CR_EMP.COLUMN_NAME||' = ' :val WHERE employee_id = :id'
USING CR_EMP.VALUE, P_EMP_ID;

Related

Best strategy to reset Oracle sequence monthly

I'm looking for the best strategy to reset a sequence which is used to generate unique keys. These keys are build using a prefix value, also generated by a combination of month and year values, like the following example:
2020060100000001 - Year(4d)Month(2d)Sequence(8d)
The sequence value must be restarted at the first second of each new month.
Is there any Oracle event that allows calling a function or procedure based in this situation to do this job?
Can anyone help me with opinions and experiences?
Thanks a lot!
Rodrigo
If you really want to set the value of a sequence you can use something like the following:
PROCEDURE SET_SEQUENCE(pinSequence_owner IN VARCHAR2,
pinSequence_name IN VARCHAR2,
pinNew_next_value IN NUMBER,
pinDebug IN BOOLEAN := FALSE)
IS
strSQL VARCHAR2(4000);
nNext_number NUMBER;
nOriginal_increment NUMBER;
nNew_nextval NUMBER;
nNew_last_number NUMBER;
BEGIN
strSQL := 'SELECT s.LAST_NUMBER, INCREMENT_BY ' ||
'FROM DBA_SEQUENCES s ' ||
'WHERE s.SEQUENCE_OWNER = ''' || pinSequence_owner || ''' AND ' ||
's.SEQUENCE_NAME = ''' || pinSequence_name || '''';
EXECUTE IMMEDIATE strSQL INTO nNext_number, nOriginal_increment;
-- Note that DBA_SEQUENCES.LAST_NUMBER represents the *next* number which will be
-- returned by a call to NEXTVAL.
IF pinNew_next_value NOT IN (nNext_number-1, nNext_number)
THEN
strSQL := 'ALTER SEQUENCE ' || pinSequence_owner || '.' || pinSequence_name ||
' INCREMENT BY ' || TO_CHAR(pinNew_next_value - nNext_number) || ' NOCACHE';
EXECUTE IMMEDIATE strSQL;
strSQL := 'SELECT ' || pinSequence_owner || '.' || pinSequence_name || '.NEXTVAL FROM DUAL';
EXECUTE IMMEDIATE strSQL INTO nNew_nextval;
strSQL := 'ALTER SEQUENCE ' || pinSequence_owner || '.' || pinSequence_name ||
' INCREMENT BY ' || nOriginal_increment || ' NOCACHE';
EXECUTE IMMEDIATE strSQL;
strSQL := 'SELECT s.LAST_NUMBER FROM DBA_SEQUENCES s WHERE s.SEQUENCE_OWNER = ''' || pinSequence_owner ||
''' AND s.SEQUENCE_NAME = ''' || pinSequence_name || '''';
EXECUTE IMMEDIATE strSQL INTO nNew_last_number;
END IF;
END SET_SEQUENCE;
Resetting sequence every month is not a valid approach. Basically you are violating the utilization of sequence. From your question i could understand, you wanted to append year and month to your column. In this case you can simple concatenate while inserting,
year||month||sequence_name.nextval
Eg: 2020||02||sequence_name.nextval
If you still wanted to reset, you can create a trigger to reset the sequence every month,

Concatenating regexp_replace into listagg: Result too long (SQL Error: ORA-01489)

I have created a pl/sql procedure for a package that is performing a reconciliation between sets of tables which should match.
I am using listagg to concatenate the column names of the current table name in the loop into a string used in a dynamic SQL statement that compares two tables (34 sets, looped for each table name).
The procedure worked as expected, but the results were returned unexpectedly from the minus. After researching, I determined that some fields contained a HEX (00) character received in the flat file that populates the data on only the data from one side of the recon. In order to account for the special characters, I added a regexp_replace concatenated in line with the listagg in the column name select so it outputs the complete listagg results with each column name wrapped in a regexp_replace.
It works. However, some tables have over a hundred columns, and the listagg failes for the results being over 4000 characters.
Is there a better way to go about this entire thing?
Here is the code:
Collects column names into the comma-separated list (comma character is concatenated into the string itself for use as a separator in dynamic SQL select below)
execute immediate
'SELECT ' || q'{listagg('regexp_replace(' || column_name || ', ''[^A-Z0-9 ]'', '''')', '||'', '' || ')}' || ' within group (order by rownum) "COLUMN_NAME"
FROM user_tab_cols
where table_name =''' || csrpubtable.table_name || ''''
into v_column_names;
These two dynamic SQL statements perform the reconciliation in both directions. These aren't directly related to the error, but definitely to my question of a better overall way to accomplish the task.
--Insert data to RECON_PUB_TABLES where record exists in FILE but not PROD
execute immediate
'INSERT INTO RECON_PUB_TABLES
SELECT ''' || csrpubtable.table_name || ''', ''FILE'' , ' || v_column_names || ', trunc(sysdate) from ' || csrpubtable.table_name || '
minus
SELECT ''' || csrpubtable.table_name || ''', ''FILE'' , ' || v_column_names || ', trunc(sysdate) from ' || csrpubtable.table_name || '#pub_recon2prod where trunc(' || v_lastupdate_column || ') <= trunc(to_date(''' || v_compare_date || ''', ''dd-MON-yy''))';
--Insert data to RECON_PUB_TABLES where record exists in PROD but not FILE
execute immediate
'INSERT INTO RECON_PUB_TABLES
SELECT ''' || csrpubtable.table_name || ''', ''PROD'' , ' || v_column_names || ', trunc(sysdate) from ' || csrpubtable.table_name || '#pub_recon2prod where trunc(' || v_lastupdate_column || ') <= trunc(to_date(''' || v_compare_date || ''', ''dd-MON-yy''))
minus
SELECT ''' || csrpubtable.table_name || ''', ''PROD'' , ' || v_column_names || ', trunc(sysdate) from ' || csrpubtable.table_name ;
varchar2 is limited to 32k within plsql
if 32 is enough you can try something like this
create or replace procedure conc_col_names(tableName IN varchar2) as
collist varchar2(32767);
begin
for xx in (select * from user_tab_columns where table_name = tableName order by column_name asc) loop
if ( length(collist) > 0) then
collist := collist||',';
end if;
collist := collist||'regexp_replace('||xx.column_name||',''[^A-Z0-9 ]'')';
end loop;
/* add the rest code for comparing rows in the two table here */
end;
/

Can a bind variable be concatenated to a dynamic SQL WHERE clause in order to add an AND statement?

I am attempting to add an AND statement to a dynamic WHERE clause using a bind variable and I am receiving the following Oracle error:
ORA-01830: date format picture ends before converting entire input string bind variable
Here is the offending code:
FUNCTION WhereClause RETURN VARCHAR2
IS
BEGIN
where_sql := 'WHERE TRUNC( ' || parm_rec.SRC_DATE_COLUMN || ' ) < ADD_MONTHS( ' ||
'ADD_MONTHS ( TRUNC ( NVL ( :SYS_OFFSET, SYSDATE ) - ( :DAY_OFFSET )), ' ||
'( :MON_OFFSET * :kNEGATIVE ) ), ' ||
'( :YR_OFFSET * ( :kANNUM * :kNEGATIVE ) ) ) ';
RETURN where_sql;
END WhereClause;
PROCEDURE ArchiveSrcDateFilter
IS
BEGIN
DBMS_OUTPUT.PUT_LINE('ArchiveDynamic - ENTER');
IF parm_rec.SRC_DATE_COLUMN IS NULL THEN parm_rec.SRC_DATE_COLUMN := 'NULL';
END IF;
FOR i in tbl_cur
LOOP
where_sql := WhereClause; -- defines the WHERE clause (where_sql) via function, Spec will not return variable to body?
/*** DYNAMIC SQL DECLARATIONS ***/
arc_sql := 'DECLARE ' ||
/*** DYNAMIC %ROWTYPE SELECT ***/
'CURSOR arc_cur IS ' ||
'SELECT * '||
'FROM ' || i.ARC_TABLE_NAME || '; '|| --obtain ARCHIVE ARC_SCHEMA_NAME.ARC_TABLE_NAME
'TYPE arc_cur_type IS TABLE OF arc_cur%ROWTYPE; ' || -- dynamically set archive record cursor %ROWTYPE for BULK COLECT as table collection
'arc_rec arc_cur_type; ' || -- define archive record as TABLE OF cursor.%ROWTYPE
/*** ARCHIVE PARAMETERS CURSOR - SRC_CREATE_DATE IS NOT NULL***/
'CURSOR parm_cur IS '||
'SELECT :seq_val AS ARCHIVE_ID, '||
'A.*, ' ||
'SYSDATE AS ARCHIVE_DATE ' ||
'FROM ' || srcSchemaTable || ' A ' || -- archive SRC_SCHEMA_NAME.SRC_TABLE_NAME (source table not archive table)
where_sql || ' || :ADD_FILTER ' || ' ; ' ||
/*** DYNAMIC SQL STATEMENT BODY ***/
'BEGIN '||
'IF parm_cur%ISOPEN THEN CLOSE parm_cur; ' ||
'END IF; ' ||
'OPEN parm_cur; ' ||
'LOOP ' ||
'FETCH parm_cur ' ||
'BULK COLLECT INTO arc_rec LIMIT 500; ' ||
'EXIT WHEN arc_rec.COUNT = 0; ' ||
'FORALL i IN 1..arc_rec.COUNT ' ||
'INSERT INTO ' || arcTable ||
' VALUES arc_rec( i );' ||
'DBMS_OUTPUT.PUT_LINE( ''ARC_REC_COUNT: '' || arc_rec.COUNT ); ' ||
'END LOOP; ' ||
'CLOSE parm_cur; ' ||
'DBMS_OUTPUT.PUT_LINE(''SUCCESS...''); '||
'END; ';
DBMS_OUTPUT.PUT_LINE('ArchiveDynamic - INSIDE LOOP: ' || arc_sql );
EXECUTE IMMEDIATE arc_sql
USING seq_val, parm_rec.SYS_OFFSET, parm_rec.DAY_OFFSET, parm_rec.MON_OFFSET, kNEGATIVE, parm_rec.YR_OFFSET, kANNUM, parm_rec.ADD_FILTER;
END LOOP;
END ArchiveSrcDateFilter;
This is the specific piece of code in the ArchiveSrcFilter procedure where the :ADD_FILTER bind variable is located (Note: I have attempted different concatenation iterations for the bind variable without success this was simply my last attempt before posting the issue here) :
'CURSOR parm_cur IS '||
'SELECT :seq_val AS ARCHIVE_ID, '||
'A.*, ' ||
'SYSDATE AS ARCHIVE_DATE ' ||
'FROM ' || srcSchemaTable || ' A ' ||
where_sql || ' || :ADD_FILTER ' || ' ; ' ||
And the EXECUTE IMMEDIATE USING with the last parameter as the bind:
EXECUTE IMMEDIATE arc_sql
USING seq_val, parm_rec.SYS_OFFSET, parm_rec.DAY_OFFSET, parm_rec.MON_OFFSET, kNEGATIVE, parm_rec.YR_OFFSET, kANNUM, parm_rec.ADD_FILTER;
the parm_rec.ADD_FILTER = AND STATUS = 1062
Is it possible to do what I am attempting by concatenating the bind to the where?
I don't understand the odd error message I am receiving given that the code executes without exception if I concatenate the parm_rec.ADD_FILTER object variable or hard code the AND STATUS = 1062.
I can concatenate the parm_rec.ADD_FILTER in place of the bind variable and the code executes without exception, but I have been unsuccessful in attempts to get the bind variable to work.
I'm grateful for any suggestions and/or insight.
Thanks!
No. Column names (i.e. STATUS) and operators (i.e. AND, =) can never be resolved from bind variables.

Oracle: update table using dynamic column names

I am using Oracle 11g. My tables include columns like name and l_name (lowercase of name column). I am trying to iterate through all the columns in my table space to set the l_ columns to lowercase of their respective uppercase columns. Here is what I tried:
for i in (select table_name from user_tables) loop
SELECT SUBSTR(column_name,3) bulk collect into my_temp_storage FROM user_tab_columns WHERE table_name = i.table_name and column_name like 'L\_%' escape '\';
for j in (select column_name from user_tab_columns where table_name = i.table_name) loop
for k in 1..my_temp_storage.count
loop
if(j.column_name like 'L\_%' escape '\' and SUBSTR(j.column_name,3) = my_temp_storage(k)) then
DBMS_OUTPUT.PUT_LINE( 'update ' || i.table_name || ' set ' || j.column_name || ' = LOWER(' ||my_temp_storage(k)|| ') where ' || j.column_name || ' is not null');
execute immediate 'update ' || i.table_name || ' set ' || j.column_name || ' = LOWER(' ||my_temp_storage(k)|| ') where ' || j.column_name || ' is not null';
end if;
end loop;
end loop;
end loop;
I am storing all the names of columns in uppercase in my_temp_storage and updating the table with the LOWER value of the columns in my_temp_storage. This gave me an error saying:
Error report -
ORA-00900: invalid SQL statement
ORA-06512: at line 8
00900. 00000 - "invalid SQL statement"
*Cause:
*Action:
But the DBMS output seemed to be fine:
`update EMPLOYEE set L_NAME = LOWER(NAME) where L_NAME is not null`
Could you help me with the way I did or any other way it can be done?
The program could certainly be simplified:
begin
for i in (select table_name, column_name from user_tab_columns
where column_name like 'L\_%' escape '\')
loop
l_sql := 'update ' || i.table_name || ' set ' || i.column_name
|| ' = LOWER(' ||substr(i.columm_name,3)
|| ') where ' || i.column_name || ' is not null';
execute immediate l_sql;
end loop;
end;
It seems an odd database design though. Have you considered virtual columns, and/or function-based indexes, instead of manually maintained columns?

UPDATE statements in oracle pl/sql loop with tablenames as parameters

I have a requirement where I need to run set of UPDATE statements in a for loop.
In the cursor there is a column called PROPERTY_ID which is a number and there are many tables
that have this number appended.
For ex: SELECT * FROM PC_ORG_EXT_111(where 111 is the property_id)
This is the code and it's throwing error.
Can anyone assist me if I'm missing something here.
SET SERVEROUTPUT ON SIZE 1000000
SET LINESIZE 1000
SET PAGESIZE 0
DECLARE
V_PROP_ID VARCHAR(200);
V_CNT NUMBER(25);
V_SQL_STRING VARCHAR2(500);
CURSOR CUR_CON
IS
SELECT * FROM PRE_CONVERSION_UNMERGE_LIST;
BEGIN
FOR REC_CON IN CUR_CON
LOOP
V_PROP_ID := 'PROPARCH.PC_ORG_EXT_' || REC_CON.PROPERTY_ID;
dbms_output.put_line('Property Table Name ' || V_PROP_ID);
EXECUTE IMMEDIATE 'select COUNT(1) from ' ||V_PROP_ID;
EXECUTE IMMEDIATE '
UPDATE SIEBEL.S_ACCNT_POSTN
SET OU_EXT_ID = ' || REC_CON.VALID_SURVIVIR_REC ||'
WHERE OU_EXT_ID = ' || REC_CON.INVALID_SURVIVOR_REC||'
AND OU_EXT_ID IN (SELECT ISAC_ROW_ID
FROM ' || V_PROP_ID || '
WHERE INTEGRATION_ID = '||REC_CON.DELPHI_ID||')
AND POSITION_ID NOT IN
(SELECT POSITION_ID
FROM SIEBEL.S_ACCNT_POSTN
WHERE OU_EXT_ID = '||REC_CON.VALID_SURVIVIR_REC||')';
END LOOP;
END;
Error says :
ORA-00933: SQL command not properly ended
ORA-06512: at line 20
Also let me know if there's a better way of doing it.
Thanks,
Please try this! - Single Quotes has to be Used with escape characters!
EXECUTE IMMEDIATE '
UPDATE SIEBEL.S_ACCNT_POSTN
SET OU_EXT_ID = ''' || REC_CON.VALID_SURVIVIR_REC ||'''
WHERE OU_EXT_ID = ''' || REC_CON.INVALID_SURVIVOR_REC||'''
AND OU_EXT_ID IN (SELECT ISAC_ROW_ID
FROM ' || V_PROP_ID || '
WHERE INTEGRATION_ID = '''||REC_CON.DELPHI_ID||''')
AND POSITION_ID NOT IN
(SELECT POSITION_ID
FROM SIEBEL.S_ACCNT_POSTN
WHERE OU_EXT_ID = '''||REC_CON.VALID_SURVIVIR_REC||''')';

Resources