Issue with FOR loop applied with SUBSTR & INSTR operations - sql

I've written a small program to find the position of last space in a string. If found, take all the characters before that space and keep on doind this operation until the result is of length less than or equal to 20.
But it seems the program is not working as expected.
Here is the program.
declare
v_building_nam varchar2(100) :='lnkilap Mahallesi Üntel Sok. B1Blok';
begin
FOR i in 1..10
LOOP
EXIT when length(v_building_nam) <=20;
v_building_nam := substr(v_building_nam,1,instr(v_building_nam,' ',-1));
END LOOP;
dbms_output.put_line(v_building_nam);
END;
Output :
lnkilap Mahallesi Üntel Sok.
It seems the operation just working for the first time and the loop then runs upto 10 times without the change in value. Could anyone please let me know if I'm doing anything wrong?

set serveroutput on;
declare
v_building_nam varchar2(100) :='lnkilap Mahallesi Üntel Sok. B1Blok';
begin
FOR I IN 1..10
LOOP EXIT WHEN LENGTH(V_BUILDING_NAM) <=20;
DBMS_OUTPUT.PUT_LINE(I);
v_building_nam := substr(v_building_nam,1,instr(v_building_nam,' ',-1)-1); --calculate previous position of space
DBMS_OUTPUT.PUT_LINE(V_BUILDING_NAM);
END LOOP;
dbms_output.put_line(v_building_nam);
END;
/
you are taking space as your last position you should take less 1 position

Isn't this the same as:
Find the first space in a string.
If the position > 20 take the first 20 characters. If the position < 20 take all characters up until the space.
declare
l_position pls_integer;
l_string varchar2(100) := 'Thisissomestringwith spaces';
begin
l_position := least(instr(l_string,' ')-1,20);
dbms_output.put_line(substr(l_string,1,l_position));
end;

Related

PL/SQL CREATE PROCEDURE - factorial

I don't know what's wrong with my code block to create a procedure to find out the factorial of a number. Thank you.
Question 1: write a stored procedure that gets an integer number n and calculates and displays its factorial.
SET SERVEROUTPUT ON;
CREATE OR REPLACE PROCEDURE factorial_number(
n NUMBER) AS
factorial NUMBER;
num Number;
BEGIN
FOR i IN REVERSE 1..n LOOP
num := i - 1;
factorial := factorial * num;
END LOOP;
DBMS_OUTPUT.PUT_LINE (factorial);
EXCEPTION
WHEN OTHERS
THEN DBMS_OUTPUT.PUT_LINE ('Error!');
END;
/
BEGIN
factorial_number(5);
END;
/
You're failing to initialize the local variable factorial. Uninitialized variables are initially null and multiplying null by any value produces null.
I don't see why you'd want your loop to go in reverse order. It doesn't matter since multiplication is communitive but it it unlikely to make your code easier to read/ debug/ follow.
You don't want to subtract 1 from the value you are multiplying by on each iteration of the loop. When i = 1, for example, you're multiplying factorial by 0 which means that (assuming you initialize factorial), you'd always end up with 0. You want to multiply by i so there is no need for the local variable num.
If I make those fixes
CREATE OR REPLACE PROCEDURE factorial_number(
n NUMBER)
AS
factorial NUMBER := 1; -- Initialize
BEGIN
FOR i IN 1..n LOOP -- Loop normally
dbms_output.put_line( 'Beginning iteration ' || i || ' of loop. ' ||
'Factorial = ' || factorial ||
' multiplying by ' || i );
factorial := factorial * i; -- Multiply by i not i-1
END LOOP;
DBMS_OUTPUT.PUT_LINE (factorial);
EXCEPTION
WHEN OTHERS
THEN DBMS_OUTPUT.PUT_LINE ('Error!');
END;
Then
BEGIN
factorial_number(5);
END;
will print out 120 (5*4*3*2*1).
I'm also adding an additional dbms_output line to print out the current state of the variables on each iteration of the loop. That's a relatively old-school method of debugging. In the modern world, you'd walk through the code with a debugger where you can see the values of your local variables but introductory classes may not teach debugger usage initially.
How about
SQL> CREATE OR REPLACE PROCEDURE factorial_number (n NUMBER)
2 AS
3 factorial NUMBER := 1;
4 BEGIN
5 FOR i IN 1 .. n
6 LOOP
7 factorial := factorial * i;
8 END LOOP;
9
10 DBMS_OUTPUT.PUT_LINE (factorial);
11 END;
12 /
Procedure created.
SQL>
SQL> BEGIN
2 factorial_number (5);
3 END;
4 /
120
PL/SQL procedure successfully completed.
SQL>

Why is the last element in associative array (index by table) not printed by?

This seems like a basic thing but i am not able to figure out.
I have a PLSQL code block like below. It creates a simple sparse associative array and stores 5 elements at subscripts as -1,0,1,2,100.
Printing ARRAY.LAST gives 100 and ARRAY.COUNT gives 5. In my while loop it prints all elements properly.
But For loop going from ARRAY.FIRST to ARRAY.LAST prints only the elements at consecutive subscripts till 2, even though ARRAY.LAST gives 100
DECLARE
TYPE assoc_array IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
table1 assoc_array;
i BINARY_INTEGER;
BEGIN
table1(-1) := 100;
table1(0) := 101;
table1(1) := 102;
table1(2) := 103;
table1(100) := 104;
i := table1.FIRST;
dbms_output.put_line(table1.FIRST);
dbms_output.put_line(table1.LAST);
dbms_output.put_line(table1.COUNT);
i := table1.first;
while (i is not null)
loop
dbms_output.put_line( table1(i) );
i := table1.next(i);
end loop;
dbms_output.put_line( '***');
for i IN table1.FIRST .. table1.LAST
loop
dbms_output.put_line(table1(i) );
end loop;
END;
Output looks like:
-1
100
5
100
101
102
103
104
***
100
101
102
103
Add this snippet to your block to see the reason, which is ORA-01403:
...
EXCEPTION
WHEN no_data_found THEN
dbms_output.put_line(sqlerrm);
END;
Your loop doesn't loop through the effective array indexes. It loops from -1 .. 100, and when you try to acces table1(3), well, you cannot.
Alternatively, you could run this:
FOR i IN table1.FIRST .. table1.LAST LOOP
IF table1.EXISTS(i) THEN
dbms_output.put_line(table1(i));
END IF;
END LOOP;
But that's not a good idea, because it's quite inefficient to loop through all integer values that you already know are not indexes of your associative array.
ARRAY.FIRST is the smallest index number in your array, so in your case it will be the -1. On the other hand ARRAY.LAST took the greatest index number (100).
Then the FOR loop from the smallest index to the greatest with the step of 1. But when it reachs a non existing index it will throw an exception.
So just add one IF statement inside your loop to check that the element at a particular index exists.
IF array.EXISTS(i) THEN
-- do something
null;
END IF;

Unable to get large data i.e. XML from oracle table using Procedure

I am tring to create update statement for a column which datatype is CLOB.
For this i am fetching XML from table and writing it in oracle console for later use.
For some of the data it is working fine.
For some i am getting error as
Exception1 : CREATE_UPDATE_XML_QUERY('600264','700009');
--ORA-06502: PL/SQL: numeric or value error
I have changed datatype of V_XML,V_BLOCK nothing has worked
PROCEDURE CREATE_UPDATE_XML_QUERY
(
MY_ID NUMBER,
MY_ID2 NUMBER
) AS
V_SCREEN_VERSION NUMBER;
V_XML_ID NUMBER;
V_CNT NUMBER;
V_XML CLOB);
V_BLOCK CLOB;
BEGIN
SELECT XML,XMLID INTO V_XML,V_XML_ID
FROM XML_TABLE WHERE ENC_ID = MY_ID AND SCREEN_ID = MY_ID2 ; ----getting excption
V_BLOCK :=
'
SET SERVEROUTPUT ON;
DECLARE
V_XML CLOB ;
BEGIN
';
V_BLOCK := V_BLOCK||'V_XML := '''||V_XML||''';';
V_BLOCK := V_BLOCK||'
UPDATE XML_TABLE SET XML = '||'V_XML'||'
WHERE ENC_ID = '||MY_ID||' AND ENC_TYPE = ''P'' AND SCREEN_ID = '||MY_ID2||' AND XMLID = '||V_XML_ID||';
--DBMS_OUTPUT.PUT_LINE(''V_XML =>''||V_XML);
DBMS_OUTPUT.PUT_LINE(''ROWCOUNT =>''||SQL%ROWCOUNT);
END;
/';
DBMS_OUTPUT.PUT_LINE('--Printing Annomous Block the XML :->>');
DBMS_OUTPUT.PUT_LINE(V_BLOCK);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Exception1 : UPDATE_SCREEN_MASTER_XML('''||MY_ID||''','''||MY_ID2||''','''||V_XML_ID||'''); --'||SQLERRM);--'||SQLERRM);
END CREATE_UPDATE_XML_QUERY;
How can i avoid error.
Is it because my XML is too big.
Well, I've come up with a test case to reproduce this (Oracle 12.2.0.1), and you're right, the problem isn't the DBMS_OUTPUT line.
declare
v_clob clob;
xmlid number;
begin
-- initialize clob and make clob a string of length 32768
dbms_lob.createtemporary(v_clob, true);
for i in 1..32768 loop
v_clob := v_clob || 'x';
end loop;
dbms_output.put_line(length(v_clob));
-- testing:
v_clob := v_clob || 'x'; -- appending a varchar2 works fine
v_clob := v_clob || xmlid; -- appending a number gives ORA-06502
v_clob := v_clob || 'x' || xmlid; -- appending a string+number still gives ORA-06502
v_clob := v_clob || to_clob(xmlid); -- works fine
dbms_lob.append(v_clob, 'x' || xmlid); -- also works fine
dbms_output.put_line(length(v_clob));
dbms_output.put_line(substr(v_clob,1,32767));
end;
/
The problem seems to be that when you concatenate strings with pipes, Oracle can append 2 clobs together if one of them is over 32k, and it can implicitly convert a varchar2 to a clob and append them. But if you try to append a number to a clob over 32k, it fails. It understands how to append varchar2 and number, and clob and clob, and clob and varchar2. But it can't seem to automatically figure out how to do number -> varchar2 -> clob. You can fix it by wrapping the string in to_clob(), avoiding the issue with Oracle's implicit conversion.
Where you get the error depends on the length of your table's XML CLOB value.
If the XML is more than 32k then you'll see the error at line 27 in your code, from trying to concatenate a varchar2 string a number (as #kfinity demonstrated) onto a CLOB; behaviour that isn't explained in the documentation, but is presumably something to do with implicit conversion since just explicitly converting the numbers with to_char(MY_ID) (or to_clob(MY_ID)).
If the XML is less than, but close to, 32k then you'll just scrape past that, but the V_BLOCK CLOB still ends up as greater than 32k, then will still error at line 39 as dbms_output can't handle that.
You can avoid the first problem by using to_char() around the numeric variables, or by using dbms_lob.append instead of concatenation:
...
V_BLOCK :=
'
SET SERVEROUTPUT ON;
DECLARE
V_XML CLOB ;
BEGIN
';
dbms_lob.append(V_BLOCK, 'V_XML := '''||V_XML||''';');
dbms_lob.append(V_BLOCK, '
UPDATE XML_TABLE SET XML = '||'V_XML'||'
WHERE ENC_ID = '||MY_ID||' AND ENC_TYPE = ''P'' AND SCREEN_ID = '||MY_ID2||' AND XMLID = '||V_XML_ID||';
--DBMS_OUTPUT.PUT_LINE(''V_XML =>''||V_XML);
DBMS_OUTPUT.PUT_LINE(''ROWCOUNT =>''||SQL%ROWCOUNT);
END;
/');
...
And you can avoid the second problem, as long as your XML value contains line breaks, by splitting the CLOB into lines, as shown here, but with a slight modification to handle empty lines; with additional variables declared as:
V_BUFFER VARCHAR2(32767);
V_AMOUNT PLS_INTEGER;
V_POS PLS_INTEGER := 1;
then instead of:
DBMS_OUTPUT.PUT_LINE(V_BLOCK);
you can do:
WHILE V_POS < length(V_BLOCK) LOOP
-- read to next newline if there is one, rest of CLOB if not
IF dbms_lob.instr(V_BLOCK, chr(10), V_POS) > 0 THEN
V_AMOUNT := dbms_lob.instr(V_BLOCK, chr(10), V_POS) - V_POS;
IF V_AMOUNT = 0 THEN
V_BUFFER := null; -- first character is a new line (i.e. a blank line)
ELSE
dbms_lob.read(V_BLOCK, V_AMOUNT, V_POS, V_BUFFER);
END IF;
V_POS := V_POS + V_AMOUNT + 1; -- skip newline character
ELSE
V_AMOUNT := 32767;
dbms_lob.read(V_BLOCK, V_AMOUNT, V_POS, V_BUFFER);
V_POS := V_POS + V_AMOUNT;
END IF;
DBMS_OUTPUT.PUT_LINE(V_BUFFER);
END LOOP;
db<>fiddle
#VinayakDwivedi edited to add a function to use instead:
PROCEDURE print_clob_to_output (p_clob IN CLOB)
IS
v_offset NUMBER := 1;
v_chunk_size NUMBER := 10000;
BEGIN
LOOP
EXIT WHEN v_offset > DBMS_LOB.getlength (p_clob);
DBMS_OUTPUT.put_line (
DBMS_LOB.SUBSTR (p_clob, v_chunk_size, v_offset));
v_offset := v_offset + v_chunk_size;
END LOOP;
END print_clob_to_output;
... but this will introduce extra line breaks every 10000 characters.
However, it's worth noting that the PL/SQL block you are generating within that ends up with a line like:
V_XML := '<original xml from table>';
and if that generated code is run it will also error if the original XML is more that 32k. Really that generated code also needs to be broken up to reconstruct your CLOB in chunks - i.e. a loop that takes 32k at a time and concatenates/appends those chunks to reconstitute the full value. It also has whitespace at the start of each line so the DECLARE etc. and more importantly the final / are not at the start of their respective lines, which will also cause problems when trying to run it as-is.
Check out:
https://www.techonthenet.com/oracle/errors/ora06502.php
This suggests that there may be 3 possible causes to this error
Number to big - I doubt if this is your issue, as the max numbers are pretty huge
Conversion error - you are trying to convert a non number to a number
Assigning NULL to a NOT NULL constrained variable - self explantory
without knowing more of the context it is not really possible to determine which of these is your issue.
Hope this helps!

ERROR: Reference the counter as the target of an assignment - PL/SQL

I'm relatively new to SQL.
I'm trying to print a simple pattern through this code
declare
n number(2):=5;
temp number(2):=n;
begin
for a in 1..5
a:=a+1;loop
for b in 1..temp loop
b:=b+1;
dbms_output.put_line(' ');
temp:=temp-1;
end loop;
for c in 1..2*a-1 loop
c:=c+1;
dbms_output.put_line('*');
end loop;
end loop;
end;
/
I keep getting this error:
PLS-00103: Encountered the symbol "A" when expecting one of the following:
* & - + / at loop mod remainder rem <an exponent (**)> ||
multiset
I understand Oracle doesn't allow to reference the counter as the target of an assignment which is why I keep getting error at line 6 but I'm unable to make it work even by declaring another global variable and assigning increment statement in it but it doesn't work either.
Please help.
Thanks!
Modifying the previous answers to actually give you Pascal's triangle, which you mentioned you were attempting in a comment:
set serveroutput on format wrapped
declare
n number(2):=5;
begin
for a in 1..n loop
for b in 1..n-a loop
dbms_output.put(' ');
end loop;
for c in 1..2*a-1 loop
dbms_output.put('*');
end loop;
dbms_output.new_line;
end loop;
end;
/
*
***
*****
*******
*********
PL/SQL procedure successfully completed.
Both your dbms_output.put_line calls needed to be just dbms_output.put, as that was printing each * on a line on its own. But you do need a line break after each time around the a loop, so I've added a dbms_output.newline at the end of that. You were also decrementing temp inside the b loop, which meant it was zero instead of (n-1) for the second time around the a loop; but you don't really need a separate temp variable at all as that is always the same as (n-a)+1 and the +1 just puts an extra space on every line. (I also made the a loop 1..n as I assume you want to change the value of n later in one place only). With n := 8:
*
***
*****
*******
*********
***********
*************
***************
Crucially though you also have to set serveroutput on format wrapped, otherwise the leading spaces you're generating in the b loop are discarded.
You can also do this in plain SQL, though you need to supply the 5 twice, or use a bind or substitution variable:
select lpad(' ', 5 - level, ' ') || rpad('*', (level * 2) - 1, '*') as pascal
from dual
connect by level <= 5
PASCAL
------------------------------
*
***
*****
*******
*********
Your b and c loops are just doing a manual lpad really.
When I took your code in my editor I first noticed, you tried to increase a before starting the loop, and Oracle gives first error at that point. And also it does not allow you to increase counter variable in for loop, (I don't know why) I checked on the internet and found that you can not set increment step for Oracle for loops also you can not set a value for counter variable in for loop.
The code below works fine for me :
declare
n number(2):=5;
temp number(2):=n;
begin
for a in 1..5
loop --a:=a+1;
for b in 1..temp loop
--b:=b+1;
dbms_output.put_line(' ');
temp:=temp-1;
end loop;
for c in 1..2*a-1 loop
--c:=c+1;
dbms_output.put_line('*');
end loop;
end loop;
end;
/
As StephaneM says, loop variables are incremented by the loop itself: you don't need to do a := a + 1, and most of all you CAN'T assign them ! Here is a corrected version:
declare
n number(2):=5;
temp number(2):=n;
begin
for a in 1..5
loop
for b in 1..temp loop
dbms_output.put_line(' ');
temp:=temp-1;
end loop;
for c in 1..2*a-1 loop
dbms_output.put_line('*');
end loop;
end loop;
end;
/

ERROR at line 191: ORA-01489: result of string concatenation is too long [duplicate]

This question already has an answer here:
Oracle - ORA-01489: result of string concatenation is too long [duplicate]
(1 answer)
Closed 7 years ago.
Select TO_CLOB(a)|| TO_CLOB(b)|| TO_CLOB(c) || TO_CLOB(d)
from table1
Above query is not spooling the data properly into text file.
whereas,
Select a||b||c||d
from table1.
is ending to
ERROR at line 191: ORA-01489: result of string concatenation is too long.
Please help !!!
VARCHAR2 are limited to 4000 bytes. If you get this error
ERROR at line 191: ORA-01489: result of string concatenation is too
long.
Then it is pretty clear that the concatenation exceed 4000 bytes.
Now what to do ?
Your first solution to use CLOB instead is correct.
select TO_CLOB(a)|| TO_CLOB(b)|| TO_CLOB(c) || TO_CLOB(d)
It seems like your real problem is saving to file
Above query is not spooling the data properly into text file.
While you did not post how to save the resulting clob to a file, I believe you are not doing it correctly. If you try to save to file the same way as you were doing it with VARCHAR2, you are doing it wrong.
You need to first use dbms_lob.read to read the clob from database, then use utl_file.put_raw to write to file.
DECLARE
position NUMBER := 1;
byte_length NUMBER := 32760;
length NUMBER;
vblob BLOB;
rawlob RAW(32760);
temp NUMBER;
output utl_file.file_type;
BEGIN
-- Last parameter is maximum number of bytes returned.
-- wb stands for write byte mode
output := utl_file.fopen('DIR', 'filename', 'wb', 32760);
position := 1;
select dbms_lob.getlength(yourLob)
into len
from somewhere
where something;
temp := length;
select yourLob
into vlob
from somewhere
where something;
IF len < 32760 THEN
utl_file.put_raw(output, vblob);
-- Don't forget to flush
utl_file.fflush(output);
ELSE -- write part by part
WHILE position < len AND byte_length > 0
LOOP
dbms_lob.read(vblob, byte_length, position, rawlob);
utl_file.put_raw(output,rawlob);
-- You must admit, you would have forgot to flush.
utl_file.fflush(output);
position := position + byte_length;
-- set the end position if less than 32000 bytes
temp := temp - bytelen;
IF temp < 32760 THEN
byte_length := temp;
END IF;
END IF;
END;
How about increasing the value for LONG? You might have to increase the long variable to a higher value. Click here for detailed description.
Example:
SET LONG 100000;
SPOOL test_clob.txt
SELECT to_clob(lpad('A',4000,'A'))
||'B'
||to_clob(lpad('C',4000,'C'))
||'D'
||to_clob(lpad('E',4000,'E'))
||'F'
FROM dual;
SPOOL OFF;
Your second query returns error because,
The concat(||) operator in the query is trying to return varchar2, which has limit of 4000 characters and getting exceeded.
SQL*Plus hardcode linesize 81 when displaying CLOB, there seems no way around it. Therefore if you want to generate a csv file to be loaded to other databases, you will have a problem of parsing these extra newlines.
The ultimate solution is to use PL/SQL. For example, to genarate a comma delimited csv file from table "xyz", use the following code:
set lin 32766
set serveroutput on size unlimited
DECLARE
TYPE arraytable IS TABLE OF xyz%ROWTYPE;
myarray arraytable;
CURSOR c IS
select * from xyz ;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT INTO myarray LIMIT 10000;
FOR i IN 1 .. myarray.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE(
myarray(i).col1||','||
myarray(i).col2||','||
myarray(i).col3||','||
myarray(i).col4;
END LOOP;
EXIT WHEN c%NOTFOUND;
END LOOP;
END;
/
A bonus of this approach is that this even works with LONG datatype!
Try xmlagg function. That worked well for me when I encountered a similar issue.
http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions215.htm