concatenating 2 strings issue in Oracle - oracle

I have another issue that I do not know what has caused it. I am new to Oracle and I am tumbling on concatenating 2 strings. I have a function that is going to take a DateStart, DateStop and other variables then turn them into a SQL stmt. The function was compiling fine but has the following error when executed:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small ORA-06512 at line ...
At first I thought that one of the strings might be missing a quote, but both of the strings are fine as the function did not generate error when I printed the single one out. It had issue only when both of them are combined!
It may be something obvious that I have overlooked, I have even tried concat instead of the operator "||", but I was not able to pin point the cause of the issue. If someone has come across this issue before, I would appreciate it if you could give me a hint of what I was doing incorrectly.
This is the function that caused me issue, I have trimmed out all other irrelevant codes so that we could zero in the problem. The function compiled correctly but had issue when executed.
Thanks!
create or replace
FUNCTION ABC(
DateStart IN VARCHAR2 ,
DateStop IN VARCHAR2 ,
ZipCode IN VARCHAR2 ,
PracticeID IN VARCHAR2)
RETURN VARCHAR2
IS
v_code NUMBER;
v_errm VARCHAR2(64);
sqlstmt VARCHAR2(1000);
sqlstmt2 VARCHAR2(500);
sConditionClause VARCHAR2(500);
s_Quote VARCHAR(1) := chr(39);
BEGIN
sqlstmt2 :='SELECT TO_CHAR("Date", ''yyyy-mm-dd'') AS "Date" ,
substr(trim("Postal"),1,5) AS "ZipCode" ,
count ("Patient") AS "Total" ';
sConditionClause := ' FROM "ABC_TABLE" WHERE "Date">=To_Date('
||s_Quote || trim(DateStart) ||s_Quote
||','
||s_Quote||'mm/dd/yyyy'||s_Quote||')AND "Date"<=To_Date('
||s_Quote || trim(DateStop) ||s_Quote
||','||s_Quote||'mm/dd/yyyy'||s_Quote||') ';
sqlstmt := trim(sqlstmt2)||trim(sConditionClause);
RETURN sqlstmt;
END;

One of your variables (sqlstmt2, sConditionClause, or sqlstmt) is too small for the string you're trying to assign to it. If you had included the line number that the error is occurring on, we'd know which one.
Incidentally, you can use two single quotes together to add a single quite to a string, rather than having to use s_Quote as you are.
You could avoid this issue altogether by skipping the local variables:
create or replace
FUNCTION ABC(
DateStart IN VARCHAR2,
DateStop IN VARCHAR2)
RETURN VARCHAR2
IS
BEGIN
RETURN 'SELECT TO_CHAR("Date", ''yyyy-mm-dd'') AS "Date" ,
substr(trim("Postal"),1,5) AS "ZipCode" ,
count ("Patient") AS "Total"
FROM "ABC_TABLE" WHERE "Date">=To_Date('''
|| trim(DateStart)
||''',''mm/dd/yyyy'') AND "Date"<=To_Date('''
|| trim(DateStop)
||''',''mm/dd/yyyy'')';
END ABC;

This is what I found out: The function above may not have issue and the concatenation was fine. The issue was from the unit test Run PL/SQL provided by SQL Developer. It defaulted the return value string with a length of 200 char?!. I was not familiar enough with SQL Developer to discover it and blamed on my own function. As soon as I changed the length of the return string to varchar2(5000) everything run like a charm

Related

Input sanitization - Numeric values

I've been asked to do input validation in order to prevent sql injection. I've been using dbms assert package functions to do the sanitization. However, when I try to sanitize a number(I'm getting it in varchar2(12 byte)) error is thrown. It's the same case with alphanumeric characters starting with number.
I tried various functions of dbms assert. Nothing seems to work except noop. But, noop is of no use since it does not do any validation.
create or replace procedure employee
(
v_emp_id IN varchar2(12 byte)
)
AS
lv_query CLOB;
BEGIN
if v_emp_id is NOT NULL THEN
lv_query := 'select * from employee where emp_id=''' || dbms_assert.enquote_name(v_emp_id) || '''';
--I also tried below:
-- lv_query := 'select * from employee where emp_id=''' || dbms_assert.simple_sql_name(v_emp_id) || '''';
end if;
END
No source gives more detailed input on dbms_assert package. Please help me in
Whether dbms_assert package can be used to sanitize numeric values(stored in VARCHAR2 variables). If yes, how?
Other ways of sanitizing input. (other than using bind variables)
Thanks.
Oracle 12.2 and higher
If you are on Oracle 12.2 or higher, you can use the VALIDATE_CONVERSION function which would be the simplest solution. Your code could potentially look something like this:
CREATE OR REPLACE PROCEDURE employee (v_emp_id IN VARCHAR2)
AS
lv_query CLOB;
BEGIN
IF v_emp_id IS NOT NULL AND validate_conversion (v_emp_id AS NUMBER) = 1
THEN
lv_query := 'select * from employee where emp_id = ' || v_emp_id;
ELSE
--do something here with an invalid number
null;
END IF;
END;
/
Earlier than Oracle 12.2
If you are not on Oracle 12.2 or higher, you can write your own small function to validate that the value is a number. Using a method similar to what Belayer suggested, just attempt to convert the value to a number using the TO_NUMBER function and if it fails, then you know it's not a number. In my example, I have it as a small anonymous block within the code but you can also make it a standalone function if you wish.
CREATE OR REPLACE PROCEDURE employee (v_emp_id IN VARCHAR2)
AS
lv_query CLOB;
l_is_number BOOLEAN;
BEGIN
--Verify that the parameter is a number
DECLARE
l_test_num NUMBER;
BEGIN
l_test_num := TO_NUMBER (v_emp_id);
l_is_number := TRUE;
EXCEPTION
WHEN VALUE_ERROR
THEN
l_is_number := FALSE;
END;
--Finished verifying if the parameter is a number
IF v_emp_id IS NOT NULL AND l_is_number
THEN
lv_query := 'select * from employee where emp_id = ' || v_emp_id;
ELSE
--do something here with an invalid number
null;
END IF;
END;
/
Well if you cannot change the procedure it means you have no test as that procedure will not compile, so it cannot be executed. However that may be a moot point. You need to define exactly what you mean by "sanitize numeric values". Do you mean validate a string contains a numeric value. If so DBMS_ASSERT will not do that. (Note: The function chooses ENQUOTE_NAME will uppercase the string and put double quotes (") around it thus making it a valid object name.) Further your particular validation may require you define a valid numeric value, is it: an integer, a floating point, is scientific nation permitted, is there a required precision and scale that must be satisfied, etc. As a brute force validation you can simulate the assertion by just convert to number. The following will do that. Like dbms_assert if the assertion is successful it returns the input string. Unlike dbms_assert, however, when the assertion fails it just returns null instead of raising an exception. See fiddle.
create or replace
function assert_is_numeric(value_in varchar2)
return varchar2
is
not_numeric exception;
pragma exception_init (not_numeric,-06502);
l_numeric number;
begin
l_numeric := to_number(value_in);
return value_in;
exception
when not_numeric then
return null;
end assert_is_numeric;

ORA-00904 Invalid Identifier -- Dynamic Oracle function

create or replace
FUNCTION JDT_UDC_Desc
(
V_SY IN VARCHAR2,
V_RT IN VARCHAR2,
V_KY IN VARCHAR2
)
RETURN VARCHAR2
AS
V_DL01 VARCHAR2(30);
BEGIN
EXECUTE IMMEDIATE 'select drdl01
from PRODCTL.F0005
WHERE DRSY = V_SY
AND DRRT = V_RT
AND ltrim(rtrim(drky)) =ltrim(rtrim(V_KY))'
INTO V_DL01
using V_SY,V_RT,V_KY;
END;
Compiled. I click on run and enter below values:
V_SY ='00',
V_RT = '01',
V_KY='04';
And I get below error
ORA-00904 V_KY Invalid Identifier
Can anyone help me understand the reason for this error?
You are passing the literal values 'V_SY', 'V_RT' and 'V_KY' in your statement and it's interpreting them as column names, hence the invalid identifier error. You need to use the variable placeholders like:
EXECUTE IMMEDIATE 'select drdl01
from PRODCTL.F0005
WHERE DRSY = :1
AND DRRT = :2
AND ltrim(rtrim(drky)) =ltrim(rtrim(:3))'
INTO V_DL01
using V_SY,V_RT,V_KY;
First, there does not appear to be any reason to use dynamic SQL here. It should be rare that you need to resort to dynamic SQL-- your code is going to be more efficient and more maintainable.
create or replace
FUNCTION JDT_UDC_Desc
(
V_SY IN VARCHAR2,
V_RT IN VARCHAR2,
V_KY IN VARCHAR2
)
RETURN VARCHAR2
AS
V_DL01 VARCHAR2(30);
BEGIN
select drdl01
into V_DL01
from PRODCTL.F0005
WHERE DRSY = V_SY
AND DRRT = V_RT
AND trim(drky) =trim(V_KY);
return v_dl01;
END;
Second, it would be really helpful if you picked meaningful variable names and meaningful names for your columns and tables. F0005 tells you nothing about what the table contains. v_sy and drsy tell you nothing about what the variable or column is supposed to contain. That is going to make maintaining this code far more difficult that it needs to be.

Use execute immediate in procedure for DML and passing character value in parameter

I have a delete procedure which is taking table name and some values to delete record from that table, hence I have created a procedure with execute immediate which is forming the delete query by taking the parameter and delete.
But when ever I am passing the char value in the parameter it is getting error :
invalid identifier
as query formed with out single quote for the character value. Please let me know how can I pass char value in the procedure to form a string correctly.
Below is the procedure:
CREATE OR replace PROCEDURE Prd_delete(p_tbl_name IN VARCHAR2,
p_sys VARCHAR2,
p_b_id VARCHAR2,
p_c_date NUMBER)
IS
dlt_query VARCHAR2(200);
BEGIN
dlt_query := 'delete from '
||p_tbl_name
||' where system='
||p_sys
|| ' And batch_id='
||p_b_id
|| ' And cobdate='
||p_c_date;
--dbms_output.put_line(dlt_query);
EXECUTE IMMEDIATE dlt_query;
END;
/
Below is the running command :
exec prd_delete ('TBL_HIST_DATA','M','N1',20141205);
Below is the error :
ORA-00904:"N1" invalid identifier.
How to pass this value correctly ? please suggest.
At first place, why do you need PL/SQL for the DELETE. You could do it in plain SQL.
Why is P_C_DATE a NUMBER, What data type is cobdate COLUMN. A date should always be a DATE. If the column data type is DATE, then you will run into more errors. Always pay attention to declaring correct data types.
With dynamic SQL, before directly executing, it is always a good practice to see whether the query is formed correctly using DBMS_OUTPUT. I would also suggest to use quoting string literal technique to make it even easier.
DBMS_OUTPUT.PUT_LINE(dlt_query);
The issue with the query is that you are missing the single-quotation marks around the VARCHAR2 type.
Modify the query to -
dlt_query := 'delete from '||P_TBL_NAME||' where system='||P_SYS||
' And batch_id='||''''||P_B_ID|| '''' ||
' And cobdate='||P_C_DATE;
you are losing the quotes around N1 during concatination
you can fix by adding quotes before and after , eg.
dlt_query := 'delete from '||P_TBL_NAME||' where system='||P_SYS||
' And batch_id='||''''||P_B_ID|| '''' ||
' And cobdate='||P_C_DATE;
If you have to use the EXECUTE IMMEDIATE statement, you should use bind variables:
CREATE OR REPLACE PROCEDURE prd_delete (P_TBL_NAME IN VARCHAR2,
P_SYS VARCHAR2,
P_B_ID VARCHAR2,
P_C_DATE NUMBER) IS
dlt_query VARCHAR2 (200);
BEGIN
dlt_query := 'delete from ' || P_TBL_NAME || ' where system=:1 and batch_id=:2 and cobdate=:3';
BEGIN
EXECUTE IMMEDIATE dlt_query USING P_SYS, P_B_ID, P_C_DATE;
EXCEPTION
WHEN OTHERS THEN
-- catch exception !!
END;
END;
/

Is clob type using bad practice in PL/SQL code when you try to avoid to use magic numbers for string length?

Declaration of PL/SQL variable require setting length for varchar2 like types:
declare
query varchar2(2000);
begin
for i in 1..100 loop
query := query || to_char(i);
end loop;
dbms_output.put_line(query);
end;
/
I run in trap when product requirements changes so loop range become larger (set range to 1..1000 in my example) and I got error:
ORA-06502: PL/SQL: : буфер символьных строк слишком маленький ошибка числа или значения ORA-06512: на line 5
So I need to increase query length everywhere and even with some larger number to predicate future.
This is ugly. I am not expert of PL/SQL but by reading official docs I found clob type which doesn't require length and quick checks show me that code works!
PL/SQL code become less Pascal like and more Python/Ruby/Perl like. I really like it.
I apply type conversion only for dynamically built strings which are out of intensive loop usages.
Is such technique correct or I am wrong?
PS I am freshman to PL/SQL programming so ask such dumb question...
PPS One of working code (simplified):
queryStr := 'create or replace view "DATA_VIEW" ("DT", "MONEY", "EVENT") as ( ';
for eventRec in eventCur
loop
queryStr := queryStr || 'select DT, MONEY, ' || eventRec.ID
|| ' EVENT from ' || eventRec.tbl || ' union ';
end loop;
queryStr := rtrim(queryStr, ' union ') || ')';
execute immediate queryStr;
commit;
The CLOB datatype is your only real option if you want to manipulate strings larger than 4000 bytes in SQL or 32 kB in PL/SQL.
The operations on CLOB are a bit more expensive than on VARCHAR2 for small strings though, so you should only use the CLOB datatype when dealing with large strings. Thus the VARCHAR2 datatype still has its uses, don't go overboard and replace all your strings with CLOBs.
Looking at your example working code, what I understand is you want to dynamically generate a view definition which is made up of probably hundreds of union statements, each querying a different table.
Similar data stored in hundreds of different tables! Now thats a strange database architecture!!
anyways, just blindly replacing varcar2 with clob is not recommeneded from performance point of view.
Why dont you keep a counter/check for the number of records in your 'eventCur'(eventRec)?
Then you can optimally choose a value for that counter above which you could use clob datatype and below which a varchar2 datatype..

ORA-06502: Numeric or Value Error on very basic string function, what gives?

Here's the code... this isn't the complete code. I trimmed it down to where the first error occurred:
FUNCTION get (
p_sql_o OUT VARCHAR2
) RETURN VARCHAR2 AS
str_sql VARCHAR2(4000);
BEGIN
str_sql := ' SELECT * FROM ( SELECT A.*, ROWNUM RNUM FROM ( ' ||
' SELECT item_code, ' ||
' item_desc, ' ||
' monitor, ' ||
' measured, ' ||
' inventory, ' ||
' (measured - inventory) adj_amount, ' ||
' (inventory_cost * measured) measured_cost, ' ||
'inventory';
RETURN str_sql;
EXCEPTION
WHEN OTHERS THEN
RETURN NULL;
END get;
Obviously, the SQL is incomplete, but I'm not running it. I'm simply returning the SQL string, yet I still get an error:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 6
This is baffling. Does anyone have any clue as to why this would be the case?
Since you've said you aren't attempting to dynamically execute the SQL you're creating, the syntax and correctness of what's in there, or the length of what it might return, is clearly irrelevant. You aren't doing anything with your p_sql_o OUT parameter, so that isn't causing this problem either. That only really leaves str_sql as the culprit, and as Justin implied yesterday, it's declared as large enough within the function and the function itself compiles OK - so it looks like it has to be how it's being called that's the problem.
You mentioned it works if you remove the 'inventory', which reduced the length of that string from 201 to 192 characters, so I'm guessing you've got this set to 200 chars in the caller, something like:
declare
str_sql varchar2(200);
p_sql_o varchar2(4000);
begin
str_sql := get(p_sql_o);
end;
/
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 5
Note that the line number this is reporting is 5, not 6 as you had; it's the line in the caller that has the error, not the line the assignment happens inside the function. If I make it str_sql varchar2(250); in the caller declaration then it works (but it might as well be 4000 to match the declaration in the function).
declare
str_sql varchar2(250);
p_sql_o varchar2(4000);
begin
str_sql := get(p_sql_o);
end;
/
PL/SQL procedure successfully completed.
But p_sql_o will still be empty because you never set it. It looks like the OUT parameter is redundant. Perhaps you meant to put the SQL string into that instead, and that is already declared as big enough in the caller; but then it isn't clear what the return value would be - you probably just want to remove the OUT parameter completely, and make sure the variable in the caller that you're putting the return value into is large enough.
Have you tried just running the SQL script against the DB outside of the SP? This will help to determine if the sql syntax is correct and that you are returning what is expected. It looks like you expect to get a response that is 4000 bytes or smaller. Are you sure that is what is being returned? I don't see Where clause. Maybe you are returning multiple rows that exceed your limit? Admittedly, I'm not as familiar with stored procedures, so I could be off.
Here you are using p_sql_o as out parameter and not assigning any value inside your function.
So p_sql_o will hold the value it had previously in the code follows after calling line of this function.
As you defined str_sql can hold only 4000 char, if your concatenated string is going beyond that limit it will give character string buffer too small ORA-06512 error.
When you are concatenating the string to create a select statement, check if you are using the single quotes properly or you are missing them at all. If this is happening then while executing the dynamic select statement you may get this error ORA-06502: PL/SQL: numeric or value error:

Resources