Oracle - How to handle 32K+ string length in variables - oracle

I am using oracle 11g. Whenever I encountered strings larger than varchar2 size limit, In sql server I use to split the data into multiple variables as below and then join them while execution. However Oracle seems to be expecting 32K combined size before execution. I am getting "ORA-20000: ORU-10028: line length overflow, limit of 32767 bytes per line" error.
I am using these variables in an oralce script (not stored procs). Last 2 statements are throwing the above error and individually I am able to show the value.
Thanks in advance.
DECLARE
sViewQuery varchar2(32000);
sViewSelectQuery varchar2(32000);
BEGIN
---Assign values of 32,000 letter string (dynamic query)
sViewSelectQuery:='32K string...';
sViewQuery:='32K string..';
DBMS_OUTPUT.PUT_LINE(sViewQuery||sViewSelectQuery);
EXECUTE IMMEDIATE sViewQuery||sViewSelectQuery;
END;

You can use DBMS_SQL Package for this:
DECLARE
stmt DBMS_SQL.VARCHAR2A;
c number;
res number;
BEGIN
stmt(1) := 'create view view_a (';
stmt(2) := 'col_a, ';
stmt(3) := 'col_b, ';
stmt(4) := 'col_c) as '
stmt(5) := 'select ';
stmt(6) := 'col_bb, ';
stmt(7) := 'col_cc + col_ee + DECODE(...), ';
stmt(8) := 'col_dd) ';
stmt(9) := 'from table_b ';
stmt(10) := 'where ... ';
-- each element can have up to 32K characters, number of elements is (almost) unlimited
c := DBMS_SQL.open_cursor;
DBMS_SQL.parse(c, stmt, 1,10, TRUE, DBMS_SQL.NATIVE);
res := DBMS_SQL.execute(c);
DBMS_SQL.close_cursor(c);
END;

You should use a CLOB, Character Large OBject. You can handle 32K+ string length, since CLOB can contain up to 4GB of data.
For more info: http://docs.oracle.com/javadb/10.3.3.0/ref/rrefclob.html

Related

ORA-01008: not all variables bound - "not all variables bound"

This is my plsql block, dynamic SQL. I can't find a reason why it comes up with an error 'no ORA-01008: not all variables bound ORA-06512: at line 23'.
I can't find the error on my EXECUTE IMMEDIATE statement.
DECLARE
form_name VARCHAR2(225) := 'MUST AS';
ad_no VARCHAR2(225) := :ad_no;
sql_stmt VARCHAR2(4000);
sql_output VARCHAR2(4000);
db_table VARCHAR(225) := inp_reminder_pkg.form_db_table(form_name);
col_id VARCHAR(225) := inp_reminder_pkg.get_col_id(form_name);
BEGIN
sql_stmt := '
SELECT :1
FROM #tableName
WHERE advno = :2
AND created = ( SELECT MAX(CREATED)
FROM #tableName
WHERE advno = :2 )'
;
sql_stmt := replace(sql_stmt, '#tableName', db_table);
EXECUTE IMMEDIATE sql_stmt
INTO sql_output
USING col_id, ad_no;
dbms_output.put_line(sql_output);
EXCEPTION
WHEN no_data_found THEN
dbms_output.put_line('no-data');
END;
Let me know what I am missing. thanks you
There are three bind variables (even though two have the same name), you need to send 3 arguments for them in the execute immediate statement.
Note that you probably didn’t mean for the column name to be input as a bind variable, this is something that has to be dynamically executed if you want to have a variable column being selected.
There are two problems with your code:
The dynamic query has two instances of the placeholder :advno so you need to pass two values in the USING clause. Yes this is a bit rubbish, but I guess Oracle felt parsing dynamic SQL is hard enough without asking the compiler to match placeholder names.
We can't pass column names or other identifiers as parameters. We have to break up the statement to reference the column name variable. For the same reason you have the replace() call to substitute the table name.
So you need to change your procedure so it looks like this:
DECLARE
form_name VARCHAR2(225) := 'MUST AS';
ad_no VARCHAR2(225) := :ad_no;
sql_stmt VARCHAR2(4000);
sql_output VARCHAR2(4000);
db_table VARCHAR(225) := inp_reminder_pkg.form_db_table(form_name);
col_id VARCHAR(225) := inp_reminder_pkg.get_col_id(form_name);
BEGIN
sql_stmt := '
SELECT ' || col_id || '
FROM #tableName
WHERE advno = :2
AND created = ( SELECT MAX(CREATED)
FROM #tableName
WHERE advno = :2 )'
;
sql_stmt := replace(sql_stmt, '#tableName', db_table);
EXECUTE IMMEDIATE sql_stmt
INTO sql_output
USING ad_no, ad_no;
dbms_output.put_line(sql_output);
EXCEPTION
WHEN no_data_found THEN
dbms_output.put_line('no-data');
END;

Oracle 11g maximum statement length possible

I am facing the issue with very long statements in Oracle 11g (~220k-symbol selects with a lot of autogenerated IN (...) clauses). Selects are fired by JDBC causing SQLException 17410 No more data from socket.
On the other hand, selects with the same structure but with shorter lengths (~100k symbols) are executed fine.
The problem is that there is no reference in the docs on how to manage maximum statement length. There is just a note: The limit on how long a SQL statement can be depends on many factors, including database configuration, disk space, and memory which is not informative at all.
Can anyone share an experience on how to estimate this maximum length and what database tweaks (if any) can help to increase it?
Having thousands of expressions in IN (...) seems to be a bad design. Better insert such values into a (temporary) table and limit the result by JOIN or IN (SELECT ...)
Anyway, the statement length is almost unlimited. I have an application where I run large statements like this:
DECLARE
cmd DBMS_SQL.VARCHAR2A;
cur INTEGER := DBMS_SQL.OPEN_CURSOR;
res INTEGER;
BEGIN
cmd(1) := 'INSERT INTO';
cmd(cmd.LAST+1) := 'some columns, ';
cmd(cmd.LAST+1) := 'and some more,'; -- each line is limited to 32767 chars
DBMS_SQL.PARSE(cur, cmd, cmd.FIRST, cmd.LAST, TRUE, DBMS_SQL.NATIVE);
res := DBMS_SQL.EXECUTE(cur);
DBMS_SQL.CLOSE_CURSOR(cur);
END;
Note, DBMS_SQL.EXECUTE does not fetch any data. For SELECT the procedure would be
DECLARE
cmd DBMS_SQL.VARCHAR2A;
cur INTEGER := DBMS_SQL.OPEN_CURSOR;
res INTEGER;
refCur SYS_REFCURSOR;
BEGIN
cmd(1) := 'SELECT ';
cmd(cmd.LAST+1) := 'some columns, ';
cmd(cmd.LAST+1) := 'and some more,'; -- each line is limited to 32767 chars
DBMS_SQL.PARSE(cur, cmd, cmd.FIRST, cmd.LAST, TRUE, DBMS_SQL.NATIVE);
res := DBMS_SQL.EXECUTE(cur);
refCur := DBMS_SQL.TO_REFCURSOR(cur);
FETCH refCur BULK COLLECT INTO ...;
CLOSE refCur;
END;
or use DBMS_SQL.EXECUTE_AND_FETCH if you prefer. Procedure DBMS_SQL.RETURN_RESULT may also help.
So far I never faced any limitation with this method.

PLSQL Get Variable By Loop Index

I am trying to reach columns by loop index of a cursor record. Currently i am using multiple variables for each columns thus I think that I should refactor my codes. In java, I'd use reflection to do that but I stuck in PLSQL. Code will explain better
input is passed into from loop.
i_member_awd_rowtype in member_awd%rowtype
.
.
.
--many more inputs
l_step1_company := i_member_awd_rowtype.flt_company;
l_step1_subclass := i_member_awd_rowtype.class_code;
l_step2_company := i_member_awd_rowtype.ret_flt_company;
l_step2_subclass := i_member_awd_rowtype.ret_class_code;
l_step3_company := i_member_awd_rowtype.step3_company;
l_step3_subclass := i_member_awd_rowtype.step3_class_code;
l_step4_company := i_member_awd_rowtype.step4_company;
l_step4_subclass := i_member_awd_rowtype.step4_class_code;
l_step5_company := i_member_awd_rowtype.step5_company;
l_step5_subclass := i_member_awd_rowtype.step5_class_code;
l_step6_company := i_member_awd_rowtype.step6_company;
l_step6_subclass := i_member_awd_rowtype.step6_class_code;
l_step7_company := i_member_awd_rowtype.step7_company;
l_step7_subclass := i_member_awd_rowtype.step7_class_code;
l_step8_company := i_member_awd_rowtype.step8_company;
l_step8_subclass := i_member_awd_rowtype.step8_class_code;
I need to assign single class, company and subclass variables in a loop such as
/*for i in 1 .. 8 loop
l_step_subclass :=
end loop;*/
Is there any way to write as I wanted in PLSQL? Thanks.
The following code block containing a Dynamic SQL statement might be used, assuming you wanna gather the records for each identity column(id) of the table :
SQL> set serveroutput on
SQL> declare
v_tbl varchar2(31) := 'member_awd';
v_sql varchar2(250);
v_id member_awd.id%type := :p_id;
type typ is table of varchar2(32767) index by binary_integer;
l_step_subclass typ;
begin
for c in ( select column_id, column_name
from user_tab_columns
where table_name = upper(v_tbl) order by column_id )
loop
v_sql := 'select '||c.column_name||' from '||v_tbl||' where id = :i_id ';
execute immediate v_sql into l_step_subclass(c.column_id) using v_id;
dbms_output.put_line(l_step_subclass(c.column_id));
end loop;
end;
/
where the variable l_step_subclass is converted to the array type. This way, all values for each individual column are assigned to this column local variable.

Passing dynamic input parameters to 'execute Immediate'

I have a table where I am storing certain conditions along with input parameters as shown below:
CONDITION | INPUT_PARAMS
---------------------------------------------------------
:p_end_date < :p_start_date | v_end_date, IN v_start_date
:p_joining_day = 'MONDAY' | v_joining_day
I want to use execute immediate to evaluate the conditions.
select condition, input_param
into v_execute_condition, v_input_param
From table;
v_execute_statement :=
'IF '||v_execute_condition ||' '||
'THEN :o_flag := ''Y'';' ||' '||
'ELSE :o_flag := ''N'';' ||' '||
'END IF;';
v_execute_statement := 'BEGIN '||v_execute_statement||' END;';
dbms_output.put_line(v_execute_statement);
EXECUTE IMMEDIATE v_execute_statement USING IN input_param OUT v_flag;
This gives me an error. If I do not pass input parameters dynamically it works.
How can I pass the list of input parameters dynamically?
You can't supply a string list of bind values as a using parameter, so the only way I can see to do this is with nested dynamic SQL calls, which is a bit messy, and means having to declare (and bind) all possible parameters in the inner. nested, dynamic statement.
declare
v_execute_statement varchar2(4000);
v_flag varchar2(1);
v_start_date date := date '2018-01-01';
v_end_date date := date '2018-01-31';
v_joining_day varchar2(9) := 'MONDAY';
begin
-- loop over all rows for demo
for rec in (
select condition, input_params
From your_table
)
loop
v_execute_statement := q'[
DECLARE
v_start_date date := :v_start_date;
v_end_date date := :v_end_date;
v_joining_day varchar2(9) := :v_joining_day;
BEGIN
EXECUTE IMMEDIATE q'^
BEGIN
IF ]' || rec.condition || q'[ THEN
:o_flag := 'Y';
ELSE
:o_flag := 'N';
END IF;
END;^'
USING ]' || rec.input_params || q'[, OUT :v_flag;
END;]';
dbms_output.put_line('Statement: ' || v_execute_statement);
EXECUTE IMMEDIATE v_execute_statement
USING v_start_date, v_end_date, v_joining_day, OUT v_flag;
dbms_output.put_line('Result flag: ' || v_flag);
end loop;
end;
/
I've used the alternative quoting mechanism here to reduce confusion from escaped single quotes. There are two nested levels of quoting - the outer one delimited by q'[...]' and the inner one delimited by q'^...^', but you can use other characters if those are a problem because of your actual table contents. Escaping those quotes for two levels would be quite ugly and hard to follow/get right; and you'd also have to worry about further escaping quotes in your condition strings, which would already be a problem with your existing code for the second sample you provided as that contains a text literal within it.
With your two sample table rows and the dummy date/day values I showed above the output from running that is:
Statement:
DECLARE
v_start_date date := :v_start_date;
v_end_date date := :v_end_date;
v_joining_day varchar2(9) := :v_joining_day;
BEGIN
EXECUTE IMMEDIATE q'^
BEGIN
IF :p_end_date < :p_start_date THEN
:o_flag := 'Y';
ELSE
:o_flag := 'N';
END IF;
END;^'
USING v_end_date, IN v_start_date, OUT :o_flag;
END;
Result flag: N
Statement:
DECLARE
v_start_date date := :v_start_date;
v_end_date date := :v_end_date;
v_joining_day varchar2(9) := :v_joining_day;
BEGIN
EXECUTE IMMEDIATE q'^
BEGIN
IF :p_joining_day = 'MONDAY' THEN
:o_flag := 'Y';
ELSE
:o_flag := 'N';
END IF;
END;^'
USING v_joining_day, OUT :o_flag;
END;
Result flag: Y
The first thing to note in the generated statement is the declare section, which has to list all the possible variable names you might have in input_params, and set them from new bind variables. You must know these already in the main block/procedure, either as local variables or more likely procedure arguments; but they all have the be duplicated here, since at this point you don't know which will be needed.
Then that statement has its own inner dynamic SQL which is essentially what you were originally doing, but concatenates in the input_params string as well as condition.
The important part here is the quoting. In the first one, for example, both :p_end_date and :p_start_date are inside the second level of quotes, within the q'^...^', so they are bound for the inner dynamic SQL, with values from the local v_end_date and v_start_date from that inner execute immediate.
That entire generated block is executed with bind values for all the possible variable names, which provide values for the local variables (via v_start_date date := :v_start_date; etc.) while preserving data types; plus the output flag.
That block then executes its internal execute immediate statement using only the relevant local variables, which now have bound values; and the output flag which is still a bind variable from the outer execute immediate, so the outer block can still see its result.
You can see that the second generated statement uses a different condition and bind variables and values to the first, and the flag is evaluated based on the relevant condition and parameters in each case.
Incidentally, you could remove the duplicate reference to :o_flag (which isn't a problem but I find slightly confusing) by using a case expression instead:
v_execute_statement := q'[
DECLARE
v_start_date date := :v_start_date;
v_end_date date := :v_end_date;
v_joining_day varchar2(9) := :v_joining_day;
BEGIN
EXECUTE IMMEDIATE q'^
BEGIN
:o_flag := CASE WHEN ]' || rec.condition || q'[ THEN 'Y' ELSE 'N' END;
END;^'
USING OUT :v_flag, ]' || rec.input_params || q'[;
END;]';

Oracle 12c CLOB data type is not working as expected

I have This Oracle 12c Procedure
CREATE OR REPLACE PROCEDURE LOGINCHECK(SQLQRY IN CLOB)
AS
C INTEGER;
N INTEGER;
RC SYS_REFCURSOR;
stmt clob:= To_Clob('begin ' || sqlqry || '; end;');
BEGIN
C := SYS.DBMS_SQL.OPEN_CURSOR;
SYS.DBMS_SQL.PARSE(C,stmt ,DBMS_SQL.native);
N := SYS.DBMS_SQL.EXECUTE(C);
SYS.DBMS_SQL.GET_NEXT_RESULT(C,RC);
SYS.DBMS_SQL.RETURN_RESULT(RC);
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
when OTHERS then
RAISE;
END LOGINCHECK;
I Call This Procedure in Anonymous Block Like This (Download XML Data from here: Link)
declare stmt clob := 'INWARDPKG.MACHINEINWARD_VALIDATING(XMLDOC => XMLTYPE.CREATEXML(paste xml from link))'; --The parameter value is a xml you can download it from above link
begin
LOGINCHECK(SQLQRY => STMT);
end;
But I am getting Error PLS-00172: string literal too long.
If i reduce xml size to 40-50 elements like remove some elements. this works fine.
In your first line declare stmt clob := 'INWARDPKG.MACHINEINWARD_VALIDATING... you are defining your CLOB. Since you are using a string literal to define your CLOB, you are facing the limits of string literals (see Oracle 12c Documenation).
To solve your problem you have to build your CLOB step by step, using the DBMS_LOB package and appending strings not longer than 4000 bytes until your CLOB is complete.
The basic idea:
DECLARE
C CLOB := TO_CLOB('First 4000 bytes');
V VARCHAR2(4000);
BEGIN
V := 'Next 4000 bytes';
DBMS_LOB.WRITEAPPEND(C, LENGTH(V), V);
-- more WRITEAPPEND calls until C is complete
DBMS_OUTPUT.PUT_LINE('CLOB-Length: ' || DBMS_LOB.GETLENGTH(C));
END;

Resources