01735. 00000 - "invalid ALTER TABLE option" - oracle

I am getting the following error:
00000 - "missing right parenthesis"
when I execute my procedure:
CREATE OR REPLACE PROCEDURE ALTER_TABLE_COLUMN_NOT_NULL(
var_tabname IN VARCHAR2,
var_clname IN VARCHAR2,
var_defvalue IN VARCHAR2 )
IS
l_isnull VARCHAR2(1);
BEGIN
SELECT isnull INTO l_isnull FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = var_tabname AND COLUMN_NAME = var_clname;
IF l_isnull = 'Y' THEN
EXECUTE IMMEDIATE 'ALTER TABLE ' || var_tabname ||
' MODIFY COLUMN (' || var_clname ||
' DEFAULT ' || var_defvalue || ' NOT NULL)';
END IF;
END;
I know that according to the error, the right parenthesis is missing. I tried many ways of rewriting it, but I can't manage to fix it.
I am executing my procedure the following way:
BEGIN
ALTER_TABLE_COLUMN_NOT_NULL('FIRSTNAME', 'PRICE', '-');
END;

Writing dynamic SQL is hard, because compilation errors become runtime errors.
In this case I think the problem is that MODIFY COLUMN is wrong syntax. It's just MODIFY.
You may also run into some problems with your default of '-'. If price is a number that will fail because - is an invalid number. If price is a string you'll need to escape the passed value with additional quotes.
But probably you want to make this generic, so you need to write some more sophisticated handling which tests for datatype of the target column and formats default value appropriately.
"Can u give me a hint or any link how one can determine the datatype of a passed value in plsql?"
It's not the passed value which matters, it's the datatype of the modified column. You can get that from the USER_TAB_COLUMNS view which you're already querying.

Print your query to make sure it written correctly
DBMS_OUTPUT.PUT_LINE('ALTER TABLE ' || var_tabname || ' MODIFY COLUMN (' || var_clname || ' DEFAULT ' || var_defvalue || ' NOT NULL)');

Related

Oracle: Dynamic SQL with Update not working

I have this instruction
UPDATE TABLE1
SET INC =
(select INC from TABLE2
WHERE KEY = 'KEY_VALUE1'
FETCH FIRST 1 ROW ONLY);
This working fine if i run from sqlPlus or if I use in PLSQL but, if I using in Dynamic SQL, not working
sqlStmt:= 'UPDATE TABLE1'
|| 'SET INC = '
|| '(select INC from TABLE2 '
|| 'WHERE KEY = ''' || v_key_value || ''' '
|| 'FETCH FIRST 1 ROW ONLY); ';
BEGIN
EXECUTE IMMEDIATE sqlStmt;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('{"errorcode":"' || SQLERRM);
ROLLBACK;
END;
This instruction return this error:
{"errorcode":"ORA-00933: comando SQL terminato erroneamente
Someone can you help me?
Regards,
Marco
You don't want to have a semicolon at the end of the string you are building and passing to execute immediate.
It's not the cause of your error. But it would be much kinder to the database to write this using bind variables rather than concatenating literals. Of course, since there is no reason to be using dynamic SQL for this sort of update statement, I'm guessing your actual use case is different and that you are actually using bind variables and/or there is actually a reason why bind variables aren't an option.

DB Links as Parameters in a Function

I'm creating a function that accepts two parameters. And one of my parameter will serve as the database link for my statement. I've tried concatenating it. How will I be able to achieve this?
It shows this error
ORA-00923: FROM keyword not found where expected ORA-06512 at
"NOINK.CHECK_SECOND_REF_DIE", line 13.
Below is the code.
drop function check_second_ref_die;
create or replace function check_second_ref_die(lotNumber in VARCHAR2, db_link in VARCHAR2)
return varchar2
is
row_count NUMBER;
sql_statement VARCHAR2(300);
BEGIN
sql_statement := 'SELECT COUNT(*) FROM wcrepo.WCR_WAFER_REFERENCE#lepftds.itg.ti.com
WHERE waferconfigfile = (SELECT waferconfigfile FROM program_setup_rev#' || db_link ||
'WHERE device = (SELECT device FROM noink.lot WHERE lot_num = ' ||lotNumber || ')
AND setup_cnt=0) AND status =' || 'Approved' || 'AND ref_die_type =' || 'Secondary';
execute immediate sql_statement into row_count;
IF (row_count != 0) THEN
RETURN 'TRUE';
ELSE
RETURN'FALSE';
END IF;
END;
This is the code when I try to call the function
SELECT CASE
WHEN check_second_ref_die ('8019572', 'rfabtwdb.dal.make.ti.com') = 'TRUE'
THEN 'EXISTS' ELSE 'NOT EXISTS'
END
AS RESULT
FROM DUAL
AND status =' || 'Approved' || 'AND
This is wrong. Remove the concatenation operators and we have ...
AND status =ApprovedAND
... which is not valid SQL. To reference string literals you need to escape single quotes. The simplest way is to use two of them:
AND status =''Approved'' AND
You'll need to fix all the string literals in your code.
Dynamic SQL is hard because it turns compilation errors into runtime errors. You can make it easier to debug your code by including some simple instrumentation. If your code had this line before the EXECUTE IMMEDIATE you could have seen the executed statement and probably spotted the bloomer for yourself.
dbms_output.put_line(v_sql);

SQLLDR default character length & ways to fix it

I faced with a situation where one the columns data to be uploaded exceeded 255 and it threw Error Field in data file exceeds maximum length error.
I found a way to fix it. Please find link here explaining the logic.
The question is that in a control file, is there any difference between
Comments CHAR(255) "TRIM(:Comments)" ,
and
Comments CHAR "TRIM(:Comments)" ,
when it comes to the internal workings of sqlldr or it means the same right ?
Also while uploading, because the integrity of the data file cannot be trusted, we create a table with all columns as 255 [which i will fix for columns > 255 length] and I never specify the CHAR length in the control file.
I would like to know if the difference between
using default 255 for all columns
vs
keeping little extra than expected length [actual target table column length values] eg. actual expected length [which might range from 10 to 150 etc.] + 50/100
is very significant if i use sqlldr atleast 2000 times a day on small data files with average of 250 records.
Thanks in advance for the clarification.
I think they are the same as internally that is what the buffer size is as well.
After dealing with this enough times I created a real rough utility script that generates a skeleton control file that uses the column's actual size. It gets me 90% of the way there and no column sizing issues. It may be better on memory as its not going to use the full 255 chars available if the column is smaller. Give it a try if you want. It may give you some ideas anyway.
/********************************************************************************************************
Name: GEN_CTL_FILE
Desc: Generates a skeleton control file for loading data via SQL*Loader.
Args: tablename_in IN VARCHAR2, delim_in IN VARCHAR2 DEFAULT '|'
Returns: None.
Usage: utilities.gen_ctl_file('tablename');
Notes: Prints a skeleton control file.
If a template for a fixed-length data file is desired, use 'FIXED' for the delim_in string.
FIXED needs more work to put actual lengths in. For now just placeholders.
Example usage:
set serveroutput on;
execute utilities.gen_ctl_file('test', utilities.TAB);
REVISIONS:
Ver Date Author Description
--------- ---------- --------------- ------------------------------------
1.1 6/6/2013 gary_w - Created procedure.
1.2 10/8/2013 gary_w - Fixed decode statement.
- Added option to generate a fixed-length template.
************************************************************************************************************************/
PROCEDURE GEN_CTL_FILE(tablename_in IN VARCHAR2, delim_in VARCHAR2 DEFAULT '|') IS
ERRNULLTABLENAME CONSTANT NUMBER := -20103; -- User-defined error numbers and messages.
ERRNULLTABLENAMEMSG CONSTANT VARCHAR2(100) := 'A table name is required.';
USAGE CONSTANT VARCHAR2(100) := '* USAGE: UTILITIES.GEN_CTL_FILE(tablename_in IN VARCHAR2, fieldsep_in VARCHAR2 DEFAULT ''|'')';
v_delim VARCHAR2(20) := NVL(delim_in, '|');
CURSOR COL_CUR IS
SELECT COLUMN_NAME,
DECODE(COLUMN_ID, 1, ' ', ',') || RPAD(COLUMN_NAME, 32) || case upper(v_delim)
when 'FIXED' then 'POSITION(99:99) '
else NULL
end|| DECODE(DATA_TYPE,
'VARCHAR2', 'CHAR('||DATA_LENGTH||') NULLIF(' || COLUMN_NAME || '=BLANKS)',
'FLOAT', 'DECIMAL EXTERNAL NULLIF(' || COLUMN_NAME || '=BLANKS)',
'NUMBER', DECODE( DATA_PRECISION,
0, 'INTEGER EXTERNAL NULLIF (' || COLUMN_NAME || '=BLANKS)',
DECODE(DATA_SCALE, 0, 'INTEGER EXTERNAL NULLIF (' || COLUMN_NAME || '=BLANKS)', 'DECIMAL EXTERNAL NULLIF (' || COLUMN_NAME || '=BLANKS)')),
'DATE', 'DATE "MM/DD/YYYY" NULLIF (' || COLUMN_NAME || '=BLANKS)',
data_type)
AS COL_DATA
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = UPPER(tablename_in)
ORDER BY COLUMN_ID;
BEGIN
IF tablename_in IS NULL THEN
RAISE_APPLICATION_ERROR(ERRNULLTABLENAME, ERRNULLTABLENAMEMSG || CR || USAGE);
END IF;
DBMS_OUTPUT.PUT_LINE('--');
DBMS_OUTPUT.PUT_LINE('-- NOTE - When using DIRECT=TRUE to perform block inserts to a table,');
DBMS_OUTPUT.PUT_LINE('-- the table''s triggers will not be used! Plan accordingly to');
DBMS_OUTPUT.PUT_LINE('-- manually perform the trigger actions after loading, if needed.');
DBMS_OUTPUT.PUT_LINE('--');
DBMS_OUTPUT.PUT_LINE('OPTIONS (DIRECT=TRUE)');
DBMS_OUTPUT.PUT_LINE('UNRECOVERABLE');
DBMS_OUTPUT.PUT_LINE('LOAD DATA');
DBMS_OUTPUT.PUT_LINE('APPEND');
DBMS_OUTPUT.PUT_LINE('INTO TABLE ' || UPPER(tablename_in));
DBMS_OUTPUT.PUT_LINE('EVALUATE CHECK_CONSTRAINTS');
if upper(v_delim) != 'FIXED' then
DBMS_OUTPUT.PUT_LINE('FIELDS TERMINATED BY ' || '''' || v_delim || '''');
DBMS_OUTPUT.PUT_LINE('OPTIONALLY ENCLOSED BY ''"'' ');
DBMS_OUTPUT.PUT_LINE('TRAILING NULLCOLS');
end if;
DBMS_OUTPUT.PUT_LINE('(');
-- The cursor for loop construct implicitly opens and closes the cursor.
FOR COL IN COL_CUR
LOOP
IF COL.COLUMN_NAME != 'LOAD_DATE' THEN
IF COL.COLUMN_NAME = 'LOAD_SEQ_ID' THEN
dbms_output.put_line(','||RPAD('LOAD_SEQ_ID', 32)||'CONSTANT 0');
ELSE
DBMS_OUTPUT.PUT_LINE(COL.COL_DATA);
END IF;
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE(')' || CHR(10));
EXCEPTION
WHEN OTHERS THEN
RASIE;
END; -- GEN_CTL_FILE
The output looks like this:
--
-- NOTE - When using DIRECT=TRUE to perform block inserts to a table,
-- the table's triggers will not be used! Plan accordingly to
-- manually perform the trigger actions after loading, if needed.
--
OPTIONS (DIRECT=TRUE)
UNRECOVERABLE
LOAD DATA
APPEND
INTO TABLE TEST
EVALUATE CHECK_CONSTRAINTS
FIELDS TERMINATED BY '|'
OPTIONALLY ENCLOSED BY '"'
TRAILING NULLCOLS
(
COLA CHAR(200) NULLIF(COLA=BLANKS)
,COLB CHAR(100) NULLIF(COLB=BLANKS)
,COLC CHAR(500) NULLIF(COLC=BLANKS)
,COLD DECIMAL EXTERNAL NULLIF (COLD=BLANKS)
,COLE CLOB
)
If you tweak it, please share your changes.

Getting error ORA-00933 with execute immediate update

I have to write a function which edits predefined characteristics of a task. I'm using execute immediate but I'm getting this error:
> Error report -
ORA-00933: SQL command not properly ended
ORA-06512: at "C##U1519.BEARBEITE", line 7
ORA-06512: at line 2
00933. 00000 - "SQL command not properly ended"
This is my code
create or replace procedure bearbeite(Aufg_ID Aufgabe.Aufgaben_ID%TYPE, Eigenschaft VARCHAR2, Wert VARCHAR2)
as
sql_query_string2 VARCHAR2(4000);
begin
--überprüfen
sql_query_string2 := 'UPDATE Aufgabe SET ' || Eigenschaft || ' = ' || Wert || ' where Aufgabe.Aufgaben_ID = ' || Aufg_ID;
EXECUTE IMMEDIATE sql_query_string2;
exception
when no_data_found then
dbms_output.put_line('Kein Wert');
end;
-- test
set serveroutput on
begin
bearbeite(1,'Ort','TH WILDAU');
end;
What should I do in order to make it work? Thanks in advance
Dumping values into query strings is dangerous. The most advertised danger is SQL injection, but that doesn't apply in many cases (where user input might have controls already in place). A bigger issue is unexpected syntax errors. More than one person -- I'm sure -- has encountered a name like O'Neil to their detriment when generating a query string.
I strongly recommend the use of parameters. And this is easy in Oracle:
sql_query_string2 := 'UPDATE Aufgabe SET ' || Eigenschaft || ' = :1 where Aufgabe.Aufgaben_ID = :2';
EXECUTE IMMEDIATE sql_query_string2 USING Wert, Aufg_ID;
This also means that you don't have to worry about whether or not to use single quotes.
The parameters can be named rather than numbered.
Sadly, you cannot use parameters for SQL identifiers -- table names, schema names, column names, functions, key words. So, the column name does have to be incorporated into the string.
You should surround varchar2 argument with single quotes ... which in pl/sql should be escaped and becomes ''
sql_query_string2 := 'UPDATE Aufgabe SET ' || Eigenschaft || ' = ''' || Wert || ''' where Aufgabe.Aufgaben_ID = ' || Aufg_ID;

SQL Error: ORA-00904: "CNPPARMID": invalid identifier

I have a function:
At line execute immediate 'select ' || schemaname || '.' || value1 || '_seq.nextval from dual' into cnpParmId;
Am getting error as SQL Error: ORA-00904: "CNPPARMID": invalid identifier.
I tried to put the cnpParmId inside the quotes, tried into cnpParmId from dual, in all possible ways. But its not working.
Please give me some ideas to solve this issue.
Thanks!!
Your function compiles successfully, and you get the error at runtime:
select test(user, 'T42') from dual;
SQL Error: ORA-00904: "CNPPARMID": invalid identifier
ORA-06512: at "MYSCHEMA.TEST", line 23
You said the error was on the first execute immediate, but that is line 21 not line 23, and if it was that cnpParmId reference it was complaining about then it would cause a compilation error - the function would be created but with errors/warnings, and it wouldn't be possible to call it.
So it's the second execute immediate, at line 23, which is erroring at runtime (reformatted slightly):
execute immediate
'select ''T'' from dual where cnpParmId not in ' ||
'(select value1 from ' || schemaname || '.' || tablename || ')'
into good;
As GolezTrol said, the dynamic statement is executed in a SQL context that has no visibility of any of your PL/SQL variables. It's the same as running the generated statement:
select 'T' from dual where cnpParmId not in (select value1 from myschema.t42);
... directly in SQL*Plus or SQL Developer, which also gets:
SQL Error: ORA-00904: "CNPPARMID": invalid identifier
00904. 00000 - "%s: invalid identifier"
As a variation of GolezTrol's concatenation, you could use a bind variable to prevent hard-parsing each time round your loop, but you also need to provide your primary key column name as value1 also won't be recognised; and that has to be concatenated in:
execute immediate
'select ''T'' from dual where :cnpParmId not in ' ||
'(select ' || value1 || ' from ' || schemaname || '.' || tablename || ')'
into good using cnpParmId;
which compiles and runs.
You could also use not exists rather than not in, which might perform better since you're looking for the (indexed) primary key:
execute immediate
'select ''T'' from dual where not exists (select null from '
|| schemaname || '.' || tablename || ' where ' || value1 || ' = :cnpParmId)'
into good using cnpParmId;
You can also move the query that finds value1 outside the loop; there's no benefit to calling that repeatedly.
It looks like you're doing this because you have primary key values that weren't generated from the sequence. If you're still adding new records like that - e.g. via a trigger that only uses the sequence if the passed key column is null - then you need a hack like this or an insert loop that catches the ORA-01001. But this approach still has a race condition - another session can simultaneously do a manual insert with the same value your function finds, and one of the sessions will get an error.
It would usually be better to only use the sequence; if you are now doing that, or can change to do that, then a one-off adjustment of all your sequences to be higher than the current maximum key value would be simpler.
Using execute immediate, you execute the statement outside of the scope of the function, so it can't use the PLSQL variable. I'd solve this by executing it as a normal query and use SELECT INTO or a cursor to fetch the query result.
But it should also work if you simply substitute the value into the query string yourself, like this:
Change
'select ''T'' from dual where cnpParmId not in ' ||
into
'select ''T'' from dual where ' || cnpParmId || ' not in ' ||

Resources