While loops with Strings not Integers - oracle

I'm having trouble finding any information on oracle plSQL while loops with strings online. All seem to be integer. When doing my research i feel i understand the integer aspect of while loops in plSQL but no sites that i have visited touched on or had examples of While Loops using strings.
For example: I can use a For loop to print individual letters from the word 'text' but what would stop me from using a While loop to get the same output?
DECLARE
c1 Number:= 1;
c2 Varchar2(4);
BEGIN
FOR c1 in 1..4
LOOP
SELECT substr('text' , c1 , 1 ) into c2 from dual;
dbms_output.put_line(c2);
END LOOP;
END;
If someone could explain how one would print a individual character or even the whole string with a while loop; or possibly point me in the right direction in terms of where to research example while loops with strings online.
Thank you.

You can write a WHILE loop entirely based on characters - no need for a counter of any kind. Something like this:
declare
txt varchar2(100);
begin
txt := 'my text';
while txt is not null loop
dbms_output.put_line(substr(txt, 1, 1)); -- or whatever else you need to do
txt := substr(txt, 2);
end loop;
end;
/
m
y
t
e
x
t
PL/SQL procedure successfully completed.

Yes, it can be written using a WHILE loop. The crucial thing is the length function and the counter. Also, you don't need a select query.
SET SERVEROUTPUT ON
DECLARE
c1 NUMBER := 1;
txt VARCHAR2(20) := 'text';
BEGIN
WHILE c1 <= length(txt) LOOP
dbms_output.put_line(substr(txt,c1,1));
c1 := c1 + 1; --increment the counter
END LOOP;
END;
/
Result
t
e
x
t
PL/SQL procedure successfully completed.

You can use SUBSTR in WHILE loop directly as follows:
SQL>
SQL> set serverout on
SQL> DECLARE
2 C1 NUMBER := 1;
3 C2 VARCHAR2(10) := 'TEXT';
4 BEGIN
5 WHILE SUBSTR(C2, C1, 1) IS NOT NULL LOOP
6 DBMS_OUTPUT.PUT_LINE(SUBSTR(C2, C1, 1));
7 C1 := C1 + 1;
8 END LOOP;
9 END;
10 /
T
E
X
T
PL/SQL procedure successfully completed.
SQL>
Cheers!!

Related

What is the error in my PLSQL program to find vowels in a string entered and to check if its palindrome?

This is the code which I wrote for a PLSQL program to count the number of vowels in a string which is entered by the user and also check whether its a palindrome or not and show that as outptut.
SET SERVEROUTPUT ON
DECLARE
STR VARCHAR(30);
N VARCHAR(30);
REV VARCHAR(30);
L NUMBER(10);
TEMP VARCHAR(30);
C NUMBER(10):=0;
BEGIN
STR:='&STR';
TEMP:=STR;
L:=LENGTH(STR);
FOR I IN 1..L
LOOP
REV:=(REV||SUBSTR(STR,L,1));
N:=SUBSTR(STR,I,1);
IF N IN ('A','E','I','O','U','a','e','i','o','u')
THEN
C:=C+1;
END IF;
L:=L-1;
END LOOP;
DBMS_OUTPUT.PUT_LINE('ENTERED STRING IS '||STR);
DBMS_OUTPUT.PUT_LINE('THE NUMBER OF VOWELS IN THE STRING IS:'||C);
IF(TEMP=REV)
THEN
DBMS_OUTPUT.PUT_LINE('ITS A PALINDROME');
ELSE
DBMS_OUTPUT.PUT_LINE('ITS NOT A PALINDROME');
END IF;
END;
/
I tried this program in SQL*Plus and I am getting this as output
Output
any fixes??!
Here is an alternative solution - rather than a fix of your code.
declare
STR varchar2(30);
REV varchar2(30);
C number(10) := 0;
begin
STR:='&STR';
select reverse(STR) into REV from DUAL;
C := regexp_count(STR, '[AEIOUaeiou]');
DBMS_OUTPUT.PUT_LINE(to_char(C));
if STR = REV then
DBMS_OUTPUT.PUT_LINE('Palindrome.');
end if;
end;
Refer to Oracle 'reverse' function and REGEXP_COUNT
Here is sample output when I run the above code in SQL*Plus
Enter value for str: MADAM
old 6: STR:='&STR';
new 6: STR:='MADAM';
2
Palindrome.
EDIT
My fixes for your code.
DECLARE
STR VARCHAR2(30);
N CHAR(1);
REV VARCHAR2(30);
L NUMBER(10);
C NUMBER(10):=0;
BEGIN
STR:='&STR';
L:=LENGTH(STR);
FOR I IN 1..L
LOOP
N:=SUBSTR(STR,(I * -1),1);
REV:=REV||N;
IF N IN ('A','E','I','O','U','a','e','i','o','u') THEN
C:=C+1;
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE('ENTERED STRING IS '||STR);
DBMS_OUTPUT.PUT_LINE('THE NUMBER OF VOWELS IN THE STRING IS:'||C);
IF(STR=REV) THEN
DBMS_OUTPUT.PUT_LINE('ITS A PALINDROME');
ELSE
DBMS_OUTPUT.PUT_LINE('ITS NOT A PALINDROME');
END IF;
END;
Refer to SUBSTR function.

PL/SQL Nested Loops with cursors

I am working with Oracle PL/SQL. There are two cursors, namely c1 and c2.
v_temp VARCHAR(50);
For s1 IN c1
LOOP
--do something
FOR s2 IN c2
LOOP
--do something
v_temp := s1.s2.xxx; --PLS-00302: component 's2' must be declared
END LOOP;
END LOOP;
s2.xxx gives a column name, and with that column name I hope to assign the value of that column from s1 to v_temp.
For example:
In the first iteration, s2.xxx is 'column1',
I would like to assign s1.column1 to v_temp. In the second iteration, s2.xxx is 'column2', I would then like to assign s1.column2 to v_temp.
I got the error:
Error(191,48): PLS-00302: component 's2' must be declared
while trying to compile. I know that s1.s2.xxx is not valid, but is there another way of writing it that can make it work?
You need to fetch from a REF CURSOR and dynamically append the column_name to the select statement while opening the cursor. Here I am fetching all the column names from USER_TAB_COLUMNS for table EMPLOYEES and assigning their corresponding values to v_temp.
SET SERVEROUTPUT ON;
DECLARE
v_temp VARCHAR(50);
query1 VARCHAR2(1000);
c1 SYS_REFCURSOR;
CURSOR c2
IS
SELECT COLUMN_NAME xxx FROM USER_TAB_COLUMNS WHERE TABLE_NAME = 'EMPLOYEES';
BEGIN
FOR s2 IN c2
LOOP
--do something
query1 := 'SELECT ' ||s2.xxx||' FROM EMPLOYEES';
OPEN c1 FOR query1 ;
LOOP
FETCH c1 INTO v_temp;
DBMS_OUTPUT.PUT_LINE('COLUMN:'||s2.xxx||', VALUE:'|| v_temp);
EXIT
WHEN c1%NOTFOUND;
END LOOP;
CLOSE c1;
END LOOP;
END;
/
Since lengths of all the columns of Employees are < 50 , it is working Fine.The conversion happens implicitly for NUMBER and DATE data types.
Here is a sample Output.
COLUMN:EMPLOYEE_ID, VALUE:100
COLUMN:EMPLOYEE_ID, VALUE:101
COLUMN:EMPLOYEE_ID, VALUE:102
COLUMN:FIRST_NAME, VALUE:Eleni
COLUMN:FIRST_NAME, VALUE:Eleni
COLUMN:LAST_NAME, VALUE:Whalen
COLUMN:LAST_NAME, VALUE:Fay
COLUMN:HIRE_DATE, VALUE:17-06-03
COLUMN:HIRE_DATE, VALUE:21-09-05
I think you need smth like that:
declare
v_temp VARCHAR(50);
v_temp_1 VARCHAR(50);
cursor c2(p VARCHAR) is
SELECT *
FROM tbl
WHERE tbl.column = p;
begin
For s1 IN c1
LOOP
--do something
v_temp_1 := s1.xxx;
FOR s2 IN c2(v_temp_1)
LOOP
--do something
v_temp := s1.xxx;
END LOOP;
END LOOP;
end;

Printing Non Repeating Characters first

I am new to PL/SQL and I am trying to write a procedure which would print the non repeating characters of an input string first and Repeating characters of the string in the last. For example if the input string is "Array" then the output should be "yArra".
I wrote a part of it for searching the no. of occurrences of a repeating character, but don't know how exactly should it be printed at the first place.
I wrote an algorithm on this on how can this be made to work but finding difficult to code
Thanks in advance for your help!
I am trying to write a procedure which would print the non repeating
characters of an input string first and Repeating characters of the
string in the last.
You can do this using a pure PLSQL code as below:
create or replace procedure prnt_letter(strng varchar2) as
var varchar2(1);
var1 varchar2(1000) := '';
var2 varchar2(1);
var3 varchar2(1000) := '';
strn_len number;
begin
dbms_output.put_line('Input String --> ' || strng);
strn_len := length(strng);
var := substr(strng, 1, 1);
for i in 1 .. strn_len loop
if (var = substr(strng, i, 1)) then
var2 := substr(strng, i, 1);
var3 := var3 || var2;
var := substr(strng, i, 1);
else
var1 := var1 || substr(strng, i, 1);
var := substr(strng, i, 1);
end if;
end loop;
dbms_output.put_line('Output String --> '||var1 || var3);
end;
EDIT:
Here is my revised solution both in PLSQL and SQL. This works for any string.
PLSQL:
create or replace procedure prnt_letter(strng varchar2) as
var1 varchar2(1000) := '';
strn_len number;
begin
dbms_output.put_line('Input String --> ' || strng);
strn_len := length(strng);
SELECT reverse (LISTAGG (vertical, '') WITHIN GROUP (ORDER BY 1 DESC))
into var1
FROM (
SELECT SUBSTR (strng, LEVEL, 1) Vertical
FROM DUAL
CONNECT BY LEVEL <= strn_len
) ;
dbms_output.put_line('Output String --> '||var1 );
end;
Output:
SQL> execute prnt_letter('rajjjjkkmmaaljjjl');
Input String --> rajjjjkkmmaaljjjl
Output String --> rmmllkkjjjjjjjaaa
PL/SQL procedure successfully completed.
SQL> execute prnt_letter('bubble');
Input String --> bubble
Output String --> ulebbb
PL/SQL procedure successfully completed.
SQL:
-- Logic used:
1) The input string is first arranged vertically in separate rows and
then ordered
2) Using LISTAGG, the result was assembled as a single ordered string
3) Using REVERSE the non-repeating string is brought to the starting
of the string.
SELECT reverse (LISTAGG (vertical, '') WITHIN GROUP (ORDER BY 1 DESC)) col1
FROM ( SELECT SUBSTR ('rajjjjkkmmaaljjjl', LEVEL, 1) Vertical
FROM DUAL
CONNECT BY LEVEL <= LENGTH ('rajjjjkkmmaaljjjl')
)
Try and use the REGEXP_COUNT function for the same. You can first provide a filter where this result >1 to find repeating characters and then concatenate them with the ones whose count = 1.
Check how to use regexp_count
I think the solution can be acheived by just using pure SQL rather than using PLSQL. Hope below snippet helps.
SELECT a.COL
||REPLACE('&Enter_text',a.col,'') output
FROM
(SELECT regexp_count('&Enter_text',SUBSTR('&Enter_text',level,1)) col1,
SUBSTR('&Enter_text',level,1) col
FROM DUAL
CONNECT BY level <=LENGTH('&Enter_text')
)a
WHERE a.col1 = 1;
That's fun, so I came out with something easily understandable using associative arrays as Hashmap; there's something subtle also with the non-case-sensitiveness:
CREATE OR REPLACE FUNCTION f(p_str in varchar2)
RETURN varchar2
AS
TYPE map_v IS TABLE OF integer INDEX BY varchar2(1);
l_dup map_v;
i PLS_INTEGER;
l_c varchar2(1);
l_tc varchar2(1);
l_nb_occurrences integer := NULL;
l_out_sngl varchar2(2000) := '';
l_out_dupl varchar2(2000) := '';
BEGIN
-- l_dup('a'):=0;
-- l_dup('b'):=0;
-- first loop to count occurrences
i:=1;
LOOP
l_c := lower(substr(p_str, i, 1));
begin
l_nb_occurrences := l_dup(l_c);
l_dup(l_c) := l_nb_occurrences + 1;
dbms_output.put_line(l_c||':incr:'||i);
exception
when no_data_found then
l_dup(l_c) := 1;
dbms_output.put_line(l_c||':pushed:'||i);
when others then
raise;
end;
i := i+1;
EXIT WHEN i > length(p_str);
END LOOP;
-- second loop for building output
i:=1;
LOOP
l_c := lower(substr(p_str, i, 1));
l_tc := substr(p_str, i, 1);
begin
l_nb_occurrences := l_dup(l_c);
dbms_output.put_line(l_c||':xx:'||i||'||'||l_nb_occurrences);
if l_nb_occurrences = 1 then
l_out_sngl := l_out_sngl || l_tc;
else
l_out_dupl := l_out_dupl || l_tc;
end if;
exception
when no_data_found then
dbms_output.put_line('why? there should be (see first loop).');
when others then
raise;
end;
i := i+1;
EXIT WHEN i > length(p_str);
END LOOP;
return l_out_sngl || l_out_dupl;
exception
when others then
dbms_output.put_line(sqlerrm);
END f;
/
Which gives results:
select f('Array') from dual;
-- yArra
select f('Bubbles') from dual;
-- ulesBbb
Try this function...
CREATE OR REPLACE FUNCTION non_repeating_char_first (v_input IN varchar2)
RETURN varchar2
IS
str1 varchar2 (100);
str2 varchar2 (100);
v_match number;
v_output varchar2 (100);
BEGIN
str1 := '';
str2 := '';
FOR i IN 1 .. LENGTH (v_input)
LOOP
SELECT REGEXP_COUNT (v_input,
SUBSTR (v_input, i, 1),
1,
'i')
INTO v_match
FROM DUAL;
IF v_match =1 THEN
str1 :=str1||SUBSTR (v_input, i, 1);
else
str2 :=str2||SUBSTR (v_input, i, 1);
END IF;
END LOOP;
v_output:=str1||str2;
RETURN v_output;
END;

(Oracle) How to get sum value inside loop?

I need value of sum inside loop.
DECLARE
VAR_PCT NUMBER;
CURSOR C1 IS
SELECT A, B FROM TBL;
BEGIN
FOR REC1 IN C1
LOOP
--This where i need the value of sum(rec1.b) to calculate VAR_PCT:=(REC1.B/SUM(REC1.B))*100
DBMS_OUTPUT.PUT_LINE(REC1.A ||'|'|| REC1.B ||'|'||VAR_PCT)
END LOOP;
END;
So, I basically need to figure out how to get the sum of B.
EDIT:
I forgot that I have one more variable that accumulate before the sum
*FOR REC1 IN C1
LOOP*
VAR_X := VAR_X+REC1.B
*--This where i need the value of sum(rec1.b) to calculate VAR_PCT:=(REC1.B/SUM(REC1.B))*100
DBMS_OUTPUT.PUT_LINE(REC1.A ||'|'|| REC1.B ||'|'||VAR_PCT)
END LOOP;*
And the VAR_PCT is value for (VAR_X/SUM(REC1.B))*100
That's why I need calculate it inside the loop.
You can use window function sum(col) over () to find the overall total in the cursor definition itself. This is usually more performant then doing thing procedurally in a loop.
DECLARE
CURSOR C1 IS
SELECT A, B, 100 * b / sum(b) over () VAR_PCT FROM TBL;
BEGIN
FOR REC1 IN C1
LOOP
DBMS_OUTPUT.PUT_LINE(REC1.A ||'|'|| REC1.B ||'|'||REC1.VAR_PCT)
END LOOP;
END;
If the sum is for each A, then use partition by:
DECLARE
CURSOR C1 IS
SELECT A, B, 100 * b / sum(b) over (partition by A) VAR_PCT FROM TBL;
BEGIN
FOR REC1 IN C1
LOOP
DBMS_OUTPUT.PUT_LINE(REC1.A ||'|'|| REC1.B ||'|'||REC1.VAR_PCT)
END LOOP;
END;

Use Oracle PL/SQL For Loop to iterate through comma delimited string

I am writing a piece of code that would need to iterate on the content of a string, each values being separated with a ,.
e.g. I have my elements
v_list_pak_like varchar2(4000) := 'PEBO,PTGC,PTTL,PTOP,PTA';
How can I get it into an Array / Cursor to iterate on it in my loop?
for x in (elements)
loop
-- do my stuff
end loop;
I am looking for the very simple way, if possible avoiding to declare associative arrays.
Would it be possible to create a function that would return something usable as an input for a for loop (opposite to the while that could be used like in https://stackoverflow.com/a/19184203/6019417)?
Many thanks in advance.
You could do it easily in pure SQL. there are multiple ways of doing it, see Split comma delimited string into rows in Oracle
However, if you really want to do it in PL/SQL, then you could do it as:
SQL> set serveroutput on
SQL> DECLARE
2 str VARCHAR2(100) := 'PEBO,PTGC,PTTL,PTOP,PTA';
3 BEGIN
4 FOR i IN
5 (SELECT trim(regexp_substr(str, '[^,]+', 1, LEVEL)) l
6 FROM dual
7 CONNECT BY LEVEL <= regexp_count(str, ',')+1
8 )
9 LOOP
10 dbms_output.put_line(i.l);
11 END LOOP;
12 END;
13 /
PEBO
PTGC
PTTL
PTOP
PTA
PL/SQL procedure successfully completed.
SQL>
Thanks to Lalit great instructions, I am able to create a function that I can call from my for loop:
Create a type and function
CREATE OR REPLACE TYPE t_my_list AS TABLE OF VARCHAR2(100);
CREATE OR REPLACE
FUNCTION comma_to_table(p_list IN VARCHAR2)
RETURN t_my_list
AS
l_string VARCHAR2(32767) := p_list || ',';
l_comma_index PLS_INTEGER;
l_index PLS_INTEGER := 1;
l_tab t_my_list := t_my_list();
BEGIN
LOOP
l_comma_index := INSTR(l_string, ',', l_index);
EXIT
WHEN l_comma_index = 0;
l_tab.EXTEND;
l_tab(l_tab.COUNT) := TRIM(SUBSTR(l_string,l_index,l_comma_index - l_index));
l_index := l_comma_index + 1;
END LOOP;
RETURN l_tab;
END comma_to_table;
/
Then how to call it in my for loop:
declare
v_list_pak_like varchar2(4000) := 'PEBO,PTGC,PTTL,PTOP,PTA';
begin
FOR x IN (select * from (table(comma_to_table(v_list_pak_like)) ) )
loop
dbms_output.put_line(x.COLUMN_VALUE);
end loop;
end;
/
Notice the default name COLUMN_VALUE given by Oracle that is necessary for the use I want to make of the result.
Result as expected:
PEBO
PTGC
PTTL
PTOP
PTA
declare
type array_type is table of VARCHAR2(255) NOT NULL;
my_array array_type := array_type('aaa','bbb','ccc');
begin
for i in my_array.first..my_array.last loop
dbms_output.put_line( my_array(i) );
end loop;
end;
In the first line you define a table of any type you want.
then create variable of that type and give it values with a kind of constructor.
I initialized it in the declaration but it can be done also in the body of the Pl Sql.
Then just loop over your array from first index to the last.

Resources