PL/SQL Remove Characters While Loop - plsql

Im working on an assignment, the goal being to write a procedure that has 2 inputs a string and a char and remove the char from the string.
Example: RemoveAll(123-456-789, '-') will output: 123456789.
create or replace procedure MyRemoveAll
(p_text varchar2, p_char_1 char) as
v_temp varchar2(100);
BEGIN
v_temp := p_text;
WHILE instr(v_temp, p_char_1) != 0 LOOP
v_temp := substr(v_temp, 1, instr(v_temp, p_char_1)-1)
|| substr(p_text, instr(v_temp, p_char_1)+1);
END LOOP;
dbms_output.put_line (v_temp);
END;
/
exec MyRemoveAll('123456789', '*');
exec MyRemoveAll('123-456789', '-');
exec MyRemoveAll('123*456*789', '*');
I don't get any errors when the procedure is created, and the first two executions work correctly. I've tried just running:
dbms_output.put_line (instr(123*456*789, *));
Which gave me an output of 4. So it should register for the condition of the while loop, but SQL Plus just stops at the loop. (Like it's doing an infinite loop, but it isn't).
Anyone have any ideas?

I think you need to replace p_text with v_temp in your second substr call. That way each time you loop through you are working with the updated v_temp and not the original p_text.
Here is what it would look like:
create or replace procedure MyRemoveAll
(p_text varchar2, p_char_1 char) as
v_temp varchar2(100);
BEGIN
v_temp := p_text;
WHILE instr(v_temp, p_char_1) != 0 LOOP
v_temp := substr(v_temp, 1, instr(v_temp, p_char_1)-1)
|| substr(v_temp, instr(v_temp, p_char_1)+1);
END LOOP;
dbms_output.put_line (v_temp);
END;
/
All three of your examples work with this code.
Bobby

Related

How to use for loop in sql developer or oracle

This is my code but while I'm running the project, it's showing error like
PLS-00306: wrong number or types of arguments in call to 'TEST_PROCEDURE'
I want to insert multiple records according given date.
CREATE OR REPLACE PROCEDURE TEST_PROCEDURE (
IP_START_DATE IN VARCHAR2,
IP_END_DATE IN VARCHAR2,
IP_MATERIAL_TYPE IN VARCHAR2,
IP_BRM_LIST IN VARCHAR2,
IP_ACTUAL_CONSUMPTION IN VARCHAR2,
IP_YARD_NO IN VARCHAR2,
IP_USER_ID IN VARCHAR2,
IP_USER_IP IN VARCHAR2,
IP_RID IN NUMBER,
IP_OPERATION IN VARCHAR2,
i OUT VARCHAR2,
OUT_RETURN_MSG OUT VARCHAR2,
OUT_RETURN_CODE OUT NUMBER)
IS
BEGIN
OUT_RETURN_MSG := '';
OUT_RETURN_CODE := 0;
BEGIN
IF IP_OPERATION = 'INSERT'
THEN
FOR i IN IP_START_DATE .. IP_END_DATE
LOOP
IF i <= IP_END_DATE
THEN
-- exit loop immediately
INSERT INTO MST_ACTUAL_CONSUMPTION (FROM_DATE,
TO_DATE,
MATERIAL_TYPE,
BRM_TYPE,
ACTUAL_CONSUMPTION,
YARD_NO,
USER_ID,
USER_IP_ADDRESS,
CREATED_DATE)
VALUES (IP_START_DATE,
IP_END_DATE,
IP_MATERIAL_TYPE,
IP_BRM_LIST,
IP_ACTUAL_CONSUMPTION,
IP_YARD_NO,
IP_USER_ID,
IP_USER_IP,
SYSDATE);
EXIT;
END IF;
END LOOP;
END IF;
IF IP_OPERATION = 'UPDATE'
THEN
UPDATE MST_ACTUAL_CONSUMPTION
SET FROM_DATE = IP_START_DATE,
TO_DATE = IP_END_DATE,
MATERIAL_TYPE = IP_MATERIAL_TYPE,
BRM_TYPE = IP_BRM_LIST,
ACTUAL_CONSUMPTION = IP_ACTUAL_CONSUMPTION,
YARD_NO = IP_YARD_NO,
LAST_UPD_TS = SYSDATE,
LAST_UPD_UID = IP_USER_ID
WHERE RID = IP_RID;
-- AND VESSEL_NAME = IP_VESSEL_NAME;
END IF;
OUT_RETURN_CODE := 1;
OUT_RETURN_MSG := 'SUCCESS';
EXCEPTION
WHEN OTHERS
THEN
OUT_RETURN_CODE := 0;
OUT_RETURN_MSG := SQLERRM;
END;
END TEST_PROCEDURE;
You need to select the days between start and end and use that for your loop counter instead of trying to use the variables directly as you currently are. Even though you're passing varchar2 with dates inside them, I still trunc the casted variable for safety to guarantee a whole number.
select trunc(to_date(ip_end_date)) - trunc(to_date(ip_start_date))
into loop_count
from dual;
Change your loop to count by this
if loop_count > 0 then
FOR i IN 1..loop_count loop
and I'm assuming you probably want to change the date for each iteration of the loop to match the day between start and end.
Firstly, you shouldn't be using varchar2 datatype for date datatypes. That is asking for trouble.
Now, since you want to insert records for multiple dates, you can't use for loop iterator for dates the way you do it for numbers as in for i in 1..10.
So, you could use a connect by as a method to generate dates. Use LEVEL pseudocolumn to refer to the current iteration (i in the for loop)
INSERT INTO mst_actual_consumption (
from_date,
TO_DATE, -- don't use this as a column_name as it's a function
material_type,
brm_type,
actual_consumption,
yard_no,
user_id,
user_ip_address,
created_date
)
SELECT
ip_start_date, -- you need to use to_date if you are passing date as strings
ip_end_date,
ip_material_type,
ip_brm_list,
ip_actual_consumption,
ip_yard_no,
ip_user_id,
ip_user_ip,
SYSDATE
FROM
dual
CONNECT BY
level <= ( ip_end_date - ip_start_date ); -- you need to use to_date if you are passing date as strings
I did not understand this part though
IF i <= IP_END_DATE
THEN
-- exit loop immediately
Maybe you were trying this? If so, put it before insert and update
IF ip_end_date <= ip_start_date
THEN
RETURN;
END IF;
EDIT you said:
error has fixed but no records are inserting even one time also
It could be because you are not passing correct dates.As I said in the code comments, you should convert them to dates using to_date function.
For eg: if you are passing the dates as strings in the format dd-mm-yyyy, In the insert, you should use
level <= ( to_date(ip_end_date,'dd-mm-yyyy') - to_date(ip_start_date,'dd-mm-yyyy') )
Same thing has to be done in the select part of insert, if the column in table MST_ACTUAL_CONSUMPTION is of date datatype.
select to_date(ip_start_date,'dd-mm-yyyy'), to_date(ip_end_date,'dd-mm-yyyy')
you can't loop through dates, you can probably do something like this.
create or replace procedure testproc(
startdate in date,
enddate in date
)
as
datediff NUMBER := enddate-startdate;
newdate date := startdate;
begin
FOR i IN 1..datediff LOOP
newdate := newdate+1;
insert into temp values(newdate);
END LOOP;
end;
and for your error it's because you are passing your parameters incorrectly like passing a string where argument requires integer value or you are passing incorrect number of parameters.
create or replace PROCEDURE PROC_ADD_ACTUALCONSUMPTION (
IP_START_DATE IN VARCHAR2,
IP_END_DATE IN VARCHAR2,
IP_MATERIAL_TYPE IN VARCHAR2,
IP_BRM_LIST IN VARCHAR2,
IP_ACTUAL_CONSUMPTION IN VARCHAR2,
IP_YARD_NO IN VARCHAR2,
IP_USER_ID IN VARCHAR2,
IP_USER_IP IN VARCHAR2,
IP_RID IN NUMBER ,
IP_OPERATION IN VARCHAR2,
--S_DATE OUT DATE,
--E_DATE OUT DATE,
--datediff OUT NUMBER,
OUT_RETURN_MSG OUT VARCHAR2,
OUT_RETURN_CODE OUT NUMBER)
IS
BEGIN
OUT_RETURN_MSG := '';
OUT_RETURN_CODE := 0;
-- S_DATE :=TO_DATE(IP_START_DATE,'DD-MM-YYYY HH24:MI:SS');
-- E_DATE :=TO_DATE(IP_END_DATE,'DD-MM-YYYY HH24:MI:SS');
-- datediff :=E_DATE-E_DATE;
BEGIN
IF IP_OPERATION = 'INSERT' THEN
INSERT INTO MST_ACTUAL_CONSUMPTION
(
FROM_DATE,
TO_DATE,
MATERIAL_TYPE,
BRM_TYPE,
ACTUAL_CONSUMPTION,
YARD_NO,
USER_ID,
USER_IP_ADDRESS,
CREATED_DATE
)
select
TO_CHAR((to_date(IP_START_DATE,'DD-MM-YYYY HH24:MI:SS')+ (level-1)),'DD-MM-YYYY') ,
TO_CHAR(to_date(IP_END_DATE,'DD-MM-YYYY HH24:MI:SS'),'DD-MM-YYYY') ,
IP_MATERIAL_TYPE,
IP_BRM_LIST,
IP_ACTUAL_CONSUMPTION,
IP_YARD_NO,
IP_USER_ID,
IP_USER_IP,
SYSDATE
FROM
dual
CONNECT BY
level <= to_date(IP_END_DATE,'DD-MM-YYYY HH24:MI:SS')-to_date(IP_START_DATE,'DD-MM-YYYY HH24:MI:SS')+1;
-- to_date(IP_START_DATE,'DD-MM-YYYY HH24:MI:SS'):=to_date(IP_START_DATE,'DD-MM-YYYY HH24:MI:SS')+1;
END IF;
END PROC_ADD_ACTUALCONSUMPTION;

Separating multiple rows of a select query using delimiter (Oracle)

I have a query
SELECT originating_timestamp
FROM sys.x$dbgalertext
WHERE message_text LIKE '%Starting up%'
and to_char(ORIGINATING_TIMESTAMP,'DD-MON-YY') = to_char(systimestamp,'DD-MON-YY');
The output in a linux script is as below:
09-OCT-17 04.59.33.758 AM -05:00 09-OCT-17 05.03.22.645 AM -05:00
there are two rows above each starting by date.
I would like to have the output like
09-OCT-17 04.59.33.758 AM -05:00;09-OCT-17 05.03.22.645 AM -05:00
This is just two rows, there can be many more, I would like it so that every row is separated via delimiter.
I have tried few options like
1) using listagg:
select listagg(originating_timestamp,', ') within group(order by originating_timestamp) csv
from sys.x$dbgalertext
WHERE message_text LIKE '%Starting up%'
and to_char(ORIGINATING_TIMESTAMP,'DD-MON-YY') = to_char(systimestamp,'DD-MON-YY');
But this gives error:
ERROR at line 1:
ORA-01489: result of string concatenation is too long
2) using XMLAGG:
SELECT RTRIM(XMLAGG(XMLELEMENT(E,originating_timestamp,';').EXTRACT('//text()') ORDER BY originating_timestamp).GetClobVal(),',') AS LIST
FROM sys.x$dbgalertext
WHERE message_text LIKE '%Starting up%'
and to_char(ORIGINATING_TIMESTAMP,'DD-MON-YY')= to_char(systimestamp,'DD-MON-YY');
But the output is like :
2017-10-09T04:59:33.758-05:00;2017-10-09T05:03:22.645-05:00;
Which is also not correct.
e.g. Select username from dba_users. Suppose there are 10 users, i want those 10 usernames to be separated via delimiter.
Below two anonymous block example which may be helpful to solve your problem :
SET SERVEROUTPUT ON SIZE 1000000
DECLARE
res VARCHAR2(100);
tmp VARCHAR2(100);
BEGIN
FOR i IN 1..10 LOOP
IF i != 10 THEN
select 'a' ||',' into tmp from dual;
ELSE
select 'a' into tmp from dual;
END IF;
res := concat(res,tmp);
END LOOP;
DBMS_OUTPUT.PUT_LINE(res);
END;
/
Second :
SET SERVEROUTPUT ON SIZE 1000000
DECLARE
res VARCHAR2(1000);
tmp VARCHAR2(1000);
BEGIN
FOR i IN 1..10 LOOP
IF i != 10 THEN
select TO_CHAR(sysdate,'DD-MON-YYYYHH24:MI') || ',' into tmp from dual;
ELSE
select TO_CHAR(sysdate,'DD-MON-YYYYHH24:MI') into tmp from dual;
END IF;
res := concat(res,tmp);
END LOOP;
DBMS_OUTPUT.PUT_LINE(res);
END;
/
You should replace my query select with yours and of course the format of date you wanna get and replace comma with your desire character .
To make it working properly i wanna suggest you to create a cursor :
SET SERVEROUTPUT ON SIZE 1000000
DECLARE
res VARCHAR2(100);
tmp VARCHAR2(100);
cont NUMBER;
CURSOR C_1 IS
SELECT to_char(originating_timestamp,'your date format')
originating_timestamp
FROM sys.x$dbgalertext
WHERE message_text LIKE '%Starting up%'
and to_char(ORIGINATING_TIMESTAMP,'DD-MON-YY') = to_char(systimestamp,'DD-MON-YY');
BEGIN
cont := 0;
FOR C_row IN C_1 LOOP
IF cont != C_1%rowcount THEN
tmp := C_row.originating_timestamp ||',';
res := concat(res,tmp);
ELSE
tmp := C_row.originating_timestamp;
res := concat(res,tmp);
END IF;
cont := cont +1;
END LOOP;
DBMS_OUTPUT.PUT_LINE(res);
END;
/

Passsing two VARRAYS to a Procedure to pass them to a Index-By table

I try to pass two VARRAYs into a Procedure but when call it to test it I don't get any kind of respond from my database.
CREATE OR REPLACE PACKAGE PKG_TEST AS
TYPE PNO IS VARRAY(20) OF VARCHAR(20);
TYPE QTY IS VARRAY(20) OF INTEGER;
TYPE indexTest IS TABLE OF NUMBER INDEX BY VARCHAR2(20);
PROCEDURE blatest(i_PNO IN PNO, i_QTY IN QTY);
END PKG_TEST;
/
CREATE OR REPLACE PACKAGE BODY PKG_TEST AS
PROCEDURE blatest(i_PNO IN PNO , i_QTY IN QTY)
IS
V_COUNT_PNO INTEGER;
V_COUNT_QTY INTEGER;
bla_list indexTest;
name VARCHAR(20);
BEGIN
V_COUNT_PNO := i_PNO.COUNT;
V_COUNT_QTY := i_QTY.COUNT;
IF V_COUNT_PNO = V_COUNT_QTY THEN
FOR I IN 1..V_COUNT_PNO LOOP
bla_list(i_PNO(I)) := i_QTY(I);
END LOOP;
name := bla_list.FIRST;
WHILE name IS NOT null LOOP
dbms_output.put_line('Name: ' || name || ' is ' || TO_CHAR(bla_list(name)));
name := bla_list.NEXT(name);
END LOOP;
ELSE
dbms_output.put_line('Amount of Variables is not identical!');
END IF;
END blatest;
END PKG_TEST;
/
PKG_TEST.blatest(PKG_TEST.PNO('P123','P124'), PKG_TEST.QTY(2,3));
/
And if there is any easier way to fill in a Index-By table I dynamicly I am more than happy to read this ^^. Thanks in advance!
Call it as below, then you will get response:
set serveroutput on;
BEGIN
pkg_test.blatest (pkg_test.pno ('P123', 'P124'), pkg_test.qty (2, 3));
END;

I'm using a collection to compare with i/p parameter ,but the last index of the collection is not getting compared

create or replace procedure save_pod_place_tag1 ( p_START_DATE in varchar2,
p_END_DATE in varchar2,
p_USER_ID in number,
p_TAG_ID in out number,
p_status out varchar2,
p_status_dtl out varchar2) is
v_rec_cnt number;
v_ID Number;
v_CNT Number;
type l_START_DATE is table of varchar2(100);
type l_END_DATE is table of varchar2(100);
v_st_dt l_START_DATE := l_START_DATE();
v_ed_dt l_END_DATE := l_END_DATE();
begin
v_ID := pod_unique_val_seq.nextval;
select to_char(start_date,'dd-mon-yyyy'),to_char(end_date,'dd-mon-yyyy')
bulk collect into v_st_dt,v_ed_dt
from pod_place_tag_tb;
for i in v_st_dt.first .. v_st_dt.count loop
if nvl(v_st_dt(i),chr(0)) < nvl(p_START_DATE,chr(0)) or nvl(v_st_dt.last,chr(0)) < nvl
(p_START_DATE,chr(0)) then
dbms_output.put_line('START_DATES are' ||' ' ||v_st_dt(i));
dbms_output.put_line('INSERTION IS ALLOWED');
else
dbms_output.put_line('INSERTION NOT ALLOWED');
end if;
end loop;
The last start _date is not getting compared and I get the o/p insertion not possible even i am passing proper p_START_DATE
I think this in roots is wrong... you cant get compare on varchar2 type expecting result as date, because varchar2 orders in other way.
example
select to_char(sysdate+level,'dd-mon-yyyy') dates from dual connect by level<=100 order by dates;
Result
01-aug-2014
01-jul-2014
01-jun-2014
01-sep-2014
02-aug-2014
02-jul-2014
02-jun-2014
03-aug-2014
03-jul-2014
03-jun-2014
04-aug-2014
04-jul-2014
04-jun-2014
...
select trunc(sysdate)+level dates from dual connect by level<=100 order by dates ASC
result
2014/05/25/
2014/05/26/
2014/05/27/
2014/05/28/
...
check anonimos block
declare
v_date1 date;
v_date2 date;
v_check_date boolean;
v_char_date1 varchar2(200);
v_char_date2 varchar2(200);
v_check_char_date boolean;
v_start_date date:=to_date('01.01.2014','dd.mm.yyyy');
v_count pls_integer:=0;
begin
for i IN 1 .. 300
LOOP
v_char_date1:=to_char(v_start_date+i,'dd-mon-yyyy');
v_date1 :=v_start_date+i;
for i1 IN 1..300
LOOP
v_char_date2:=to_char(v_start_date+i1,'dd-mon-yyyy');
v_date2 :=v_start_date+i1;
IF v_date1<v_date2 THEN
v_check_date:=true;
ELSIF v_date1>v_date2 THEN
v_check_date:=false;
END IF;
IF v_char_date1<v_char_date2 THEN
v_check_char_date:=true;
ELSIF v_char_date1>v_char_date2 THEN
v_check_char_date:=false;
END IF;
IF (v_check_char_date AND not v_check_date) THEN
dbms_output.put_line ('v_date1='||v_date1||' > '||'v_date2='||v_date2||' AND '||'v_char_date1='||v_char_date1||' < '||'v_char_date2='||v_char_date2);
exit;
ELSIF (not v_check_char_date AND v_check_date) THEN
dbms_output.put_line ('v_date1='||v_date1||' < '||'v_date2='||v_date2||' AND '||'v_char_date1='||v_char_date1||' > '||'v_char_date2='||v_char_date2);
exit;
END IF;
v_count:=v_count+1;
END LOOP;
IF (v_check_char_date AND not v_check_date) OR (not v_check_char_date AND v_check_date) THEN
exit;
END IF;
END LOOP;
dbms_output.put_line ('count='||v_count);
end;

oracle value exists in collection

I have function (used within a view) with the end result being a list of unique values from a row within a table of comma separated values.
Essentially given a table of:
studentid classes
12345 MATH 1301, HIST 1301, POLS 1301
57495 MATH 2309, HIST 1301
39485 MATH 1301, HIST 1301
I want to see
MATH 1301
MATH 2309
HIST 1301
POLS 1301
The below code works perfect if the source table is small, but when looking at a table of 30,000 rows I get the following error. ORA-06532: Subscript outside of limit
I'm pretty sure my problem is the collection is getting too large since it's getting duplicate values. The duplicate values in themselves only become a problem when the collection becomes too large. How do I keep the duplicate values out of the collection?
I've tried childnames.exists(element) but I believe this only works for seeing if there exists an element at the index value element correct? of I've looked at member of but I don't understand how to implement it.. Is there an easy way to check if a collection element exists? Or am I over looking something simple? Is there a different type other than odcivarchar2list that would allow a larger collection?
CREATE OR REPLACE FUNCTION unique_values_from_csv ( p_del VARCHAR2 := ',')
RETURN SYS.odcivarchar2list
IS
childnames SYS.odcivarchar2list := sys.odcivarchar2list ();
tempvar VARCHAR2(255);
l_idx PLS_INTEGER;
l_list2 VARCHAR2(32767) ;
l_value VARCHAR2(32767);
CURSOR tablewalker_cur
IS
SELECT distinct classes
FROM studentclasses;
BEGIN
FOR recordwalker_rec IN tablewalker_cur
LOOP
l_list2 := recordwalker_rec.classes;
LOOP
l_idx := INSTR (l_list2, p_del);
IF l_idx > 0
THEN
childnames.EXTEND;
tempvar := (LTRIM (RTRIM (SUBSTR (l_list2, 1, l_idx - 1))));
childnames (childnames.COUNT) := tempvar;
l_list2 := SUBSTR (l_list2, l_idx + LENGTH (p_del));
end if;
childnames.EXTEND;
childnames (childnames.COUNT) := (LTRIM (RTRIM (l_list2)));
EXIT;
END LOOP;
END LOOP;
RETURN childnames;
END unique_values_from_csv;
/
create or replace function unique_values_from_csv(p_del varchar2 := ',')
return sys.dbms_debug_vc2coll is
childnames sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll();
l_idx pls_integer;
l_list2 varchar2(32767) ;
procedure add_if_not_member(new_element varchar2) is
begin
if new_element not member of childnames then
childnames.extend;
childnames(childnames.count) := new_element;
end if;
end;
begin
for recordwalker_rec in (select distinct classes from studentclasses)
loop
l_list2 := recordwalker_rec.classes;
loop
l_idx := instr (l_list2, p_del);
if l_idx > 0 then
add_if_not_member(trim(substr (l_list2, 1, l_idx - 1)));
l_list2 := substr(l_list2, l_idx + length(p_del));
else
add_if_not_member(trim(l_list2));
exit;
end if;
end loop;
end loop;
return childnames;
end unique_values_from_csv;
/
Used SYS.DBMS_DEBUG_VC2COLL, which is a TABLE OF VARCHAR2(1000) and should support any number of elements. Although l_list2 varchar2(32767) will limit the results.
MEMBER OF is the correct condition.
Added an ELSE - the original function was only splitting the list in two.
Removed the cursor - for such a small query, another level of indirection isn't worth it.
Used TRIM() instead of LTRIM(RTRIM())
The best solution would be to throw out this function and stop storing non-atomic data in your database.
Here's some sample data and a query using the function:
create table studentclasses(studentid number, classes varchar2(4000));
insert into studentclasses
select 12345, 'MATH 1301,HIST 1301,POLS 1301' from dual union all
select 57495, 'MATH 2309,HIST 1301' from dual union all
select 39485, 'MATH 1301,HIST 1301' from dual;
commit;
select unique_values_from_csv from dual;
COLUMN_VALUE
MATH 1301
HIST 1301
POLS 1301
MATH 2309
select distinct
regexp_substr(classes, '([^,]+)(,\s*|$)', 1, occ, '', 1) as class
from
studentclasses,
(
select level as occ
from dual
connect by level <= (
select max(regexp_count(classes, ','))+1
from studentclasses
)
)
where
regexp_substr(classes, '([^,]+)(,\s*|$)', 1, occ, '', 1) is not null
order by 1

Resources