PL/SQL Oracle. Best way to implement an IS_CONTAINED operator - oracle

I am newbie so that maybe this question has been made one or two million times, but it is not findable / searchable in the knowledge database.
In Oracle PL/SQL, it is normal to query as follows:
select a,b,c
from table_foo
where c in (select k from table(array_bar));
But I need all the opposite of that. I need a kind of "IS_CONTAINED" operator, like this:
select a,b,c
from table_foo
where AT_LEAST_ONE_OF_THE_ITEMS_IN (select k from table(array_bar)) IS_CONTAINED_IN c;
I have my own ideas to implement it using a function with a loop. But maybe some genius has found a simple way to do it without a function. This is to say, maybe the operator IS_CONTAINED is already invented by Oracle and I haven't found it out.
Sorry if this question is repeated. I promise I have searched for it in the knowledge base. But it seems that nobody in the space-time of this Universe has never needed the super-obvious operator IS_CONTAINED.
SOLUTION:
Thanks to everybody for the suggestions. In the end, I had to use some functions, but I think I got a good solution. The situation is: I have a table of centers. Each center can be in one or more cities, this is to say, it's a 1 to N relationship. But this relationship is done using a single table. This table contains some fields. One of these fields, named 'cities_list', contains all related cities separated by semicolons. It's like this:
CODE DESCRIPTION CITIES_LIST
---- ----------- -----------
0001 Desc 0001 London; Berlin; NY; SF
0002 Desc 0002 Paris; Madrid; Rome
0003 Desc 0003 Berlin; Paris; London
0004 Desc 0004 Madrid;NY;Tokyo
0005 Repe 0005 Rome;Rome;Rome;LA;LA;LA;
0006 One 0006 NY
0007 Desc 0007 Sydney;Tokyo;Madrid
0008 Desc 0008 LA;SF;NY
0009 Desc 0009 Seoul;Beijing;
0010 Error0010 Beijing;;;;OZ;
0011 None 0011 (null)
0012 All 0012 London;Paris;Berlin;Madrid;Rome;NY;SF;LA;Seoul;Beijing;Tokyo;Sydney
Possible cities are: London; Paris; Berlin; Madrid; Rome; NY; SF; LA; Seoul; Beijing; Tokyo; Sydney.
In order to filter records of that table, the user can select, through a combo, one or more of those cities. Selected cities are passed to the PL/SQL query as a string (varchar) of cities separated by a hash sign (#). For instance 'London#Paris#Sydney'.
The PL/SQL has to select the records that have at least one city in common between the field 'cities_list' and the string of cities passed from the combo. First, I put here the PL/SQL code and I will explain it later on:
--1.SELECT AND EXECUTE THIS:
SET SERVEROUTPUT ON;
--2.SELECT AND EXECUTE THIS:
DROP TABLE table_centers; CREATE GLOBAL TEMPORARY TABLE table_centers (code VARCHAR2(10), description VARCHAR2(100), cities_list VARCHAR2(1000));
--3.SELECT AND EXECUTE THIS:
CREATE OR REPLACE TYPE table_TYPE IS TABLE OF VARCHAR2(250);
--4.SELECT AND EXECUTE THIS:
CREATE OR REPLACE FUNCTION VARCHAR_TO_TABLE (input_varchar VARCHAR2, separator VARCHAR2 DEFAULT ';')
RETURN table_TYPE
IS
--VARS
output_table table_TYPE := table_TYPE();
BEGIN
--For better performance, input_varchar is splitted without blanks into output_table using the regular expression [^;]+
SELECT
--The Keyword 'level' in statement 'regexp_substr' refers to a pseudocolumn in Oracle
TRIM(regexp_substr(input_varchar,'[^' || separator || ']+', 1, level))
BULK COLLECT INTO
output_table
FROM DUAL
CONNECT BY
regexp_substr(input_varchar,'[^' || separator || ']+', 1, level) IS NOT NULL;
--Now we have all chunks into the table output_table
RETURN output_table;
END VARCHAR_TO_TABLE;
--5.SELECT AND EXECUTE THIS:
CREATE OR REPLACE FUNCTION INTERSECT_TABLES(input_A VARCHAR2 , separator_A VARCHAR2 , input_B VARCHAR2 , separator_B VARCHAR2)
RETURN NUMBER
IS
--VARS
A table_TYPE;
B table_TYPE;
result BOOLEAN;
BEGIN
--Splits input_A and input_B into tables and checks if there is overlapping
A := VARCHAR_TO_TABLE(input_A, separator_A);
B := VARCHAR_TO_TABLE(input_B, separator_B);
--If intersection is not empty result is TRUE
result := A multiset intersect B is not empty;
-- Returns 1 if intersection is not empty, returns 0 otherwise (Note that functions called from a SQL query cannot take any BOOLEAN parameters)
IF result = TRUE THEN RETURN 1; ELSE RETURN 0; END IF;
END INTERSECT_TABLES;
--6.SELECT AND EXECUTE THIS:
CREATE OR REPLACE PROCEDURE GET_CENTERS (cities_input VARCHAR2 , separator_input VARCHAR2 , out_Cursor OUT sys_refcursor)
AS
BEGIN
OPEN out_Cursor FOR
SELECT tc.code, tc.description, tc.cities_list
FROM table_centers tc
--Has current record some city in common with cities_input? If yes, select current record
WHERE INTERSECT_TABLES(cities_input , separator_input , tc.cities_list , ';') = 1;
END GET_CENTERS;
--7.SELECT AND EXECUTE THIS:
BEGIN
DELETE FROM table_centers; COMMIT;
INSERT ALL
--We'll use following cities: London Paris Berlin Madrid Rome NY SF LA Seoul Beijing Tokyo Sydney
INTO table_centers (code,description,cities_list) VALUES ('0001', 'Desc 0001', 'London; Berlin; NY; SF')
INTO table_centers (code,description,cities_list) VALUES ('0002', 'Desc 0002', 'Paris; Madrid; Rome')
INTO table_centers (code,description,cities_list) VALUES ('0003', 'Desc 0003', 'Berlin; Paris; London')
INTO table_centers (code,description,cities_list) VALUES ('0004', 'Desc 0004', 'Madrid;NY;Tokyo')
INTO table_centers (code,description,cities_list) VALUES ('0005', 'Repe 0005', 'Rome;Rome;Rome;LA;LA;LA;')
INTO table_centers (code,description,cities_list) VALUES ('0006', 'One 0006', 'NY')
INTO table_centers (code,description,cities_list) VALUES ('0007', 'Desc 0007', 'Sydney;Tokyo;Madrid')
INTO table_centers (code,description,cities_list) VALUES ('0008', 'Desc 0008', 'LA;SF;NY')
INTO table_centers (code,description,cities_list) VALUES ('0009', 'Desc 0009', 'Seoul;Beijing;')
INTO table_centers (code,description,cities_list) VALUES ('0010', 'Error0010', 'Beijing;;;;OZ;')
INTO table_centers (code,description,cities_list) VALUES ('0011', 'None 0011', '')
INTO table_centers (code,description,cities_list) VALUES ('0012', 'All 0012', 'London;Paris;Berlin;Madrid;Rome;NY;SF;LA;Seoul;Beijing;Tokyo;Sydney')
SELECT 1 FROM DUAL;
END;
--8.SELECT AND EXECUTE THIS:
SELECT * FROM table_centers;
I have used 'Oracle SQL Developer'. You can select the sentences one by one and execute them with the F9 key. You can also create a Package.
If someone wants to test that code, you can also select and execute with F9 the following query:
--9.SELECT AND EXECUTE THIS:
DECLARE
--VARS
out_Cursor sys_refcursor;
cities_array table_TYPE;
citiesA varchar(1000) := 'London#Paris#Berlin#Madrid#Rome#NY#SF#LA# Seoul # Beijing # Tokyo # Sydney ';
citiesB varchar(1000) := 'London;Paris;Berlin;Madrid;Rome;NY;SF;LA; Seoul ; Beijing ; Tokyo ; Sydney ';
Rcode table_centers.code%TYPE;
Rdescription table_centers.description%TYPE;
Rcities_list table_centers.cities_list%TYPE;
CR char := CHR(13);
TAB char := CHR(9);
BEGIN
--TEST 1
dbms_output.put_line('TEST 1: ' || CR);
cities_array := table_TYPE();
cities_array := VARCHAR_TO_TABLE(citiesA, '#');
--Now we have all cities in the array cities_array
FOR elem in 1 .. cities_array.count LOOP
dbms_output.put_line(TAB || elem || ':' || cities_array(elem) || '.');
END LOOP;
--TEST 2
dbms_output.put_line('TEST 2: ' || CR);
cities_array := table_TYPE();
cities_array := VARCHAR_TO_TABLE(citiesB, ';');
--Now we have all cities in the array cities_array
FOR elem in 1 .. cities_array.count LOOP
dbms_output.put_line(TAB || elem || ':' || cities_array(elem) || '.');
END LOOP;
--TEST 3
dbms_output.put_line('TEST 3: ' || CR);
GET_CENTERS(citiesA, '#', out_Cursor);
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
WHILE out_Cursor%FOUND LOOP
dbms_output.put_line(TAB || 'CITIES:' || Rcities_list || '.');
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
END LOOP;
close out_Cursor;
--TEST 4
dbms_output.put_line('TEST 4: ' || CR);
GET_CENTERS('London#Paris#Sydney', '#', out_Cursor);
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
WHILE out_Cursor%FOUND LOOP
dbms_output.put_line(TAB || 'CITIES:' || Rcities_list || '.');
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
END LOOP;
close out_Cursor;
--TEST 5
dbms_output.put_line('TEST 5: ' || CR);
GET_CENTERS('Madrid', '#', out_Cursor);
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
WHILE out_Cursor%FOUND LOOP
dbms_output.put_line(TAB || 'CITIES:' || Rcities_list || '.');
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
END LOOP;
close out_Cursor;
--TEST 6
dbms_output.put_line('TEST 6: ' || CR);
GET_CENTERS('Gotham City', '#', out_Cursor);
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
WHILE out_Cursor%FOUND LOOP
dbms_output.put_line(TAB || 'CITIES:' || Rcities_list || '.');
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
END LOOP;
close out_Cursor;
--TEST 7
dbms_output.put_line('TEST 7: ' || CR);
GET_CENTERS('', '#', out_Cursor);
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
WHILE out_Cursor%FOUND LOOP
dbms_output.put_line(TAB || 'CITIES:' || Rcities_list || '.');
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
END LOOP;
close out_Cursor;
END;
You can modify TEST 7 and put your own values in the first parameter of the function 'GET_CENTERS'. I have executed this query and I have got these results:
TEST 1:
1:London.
2:Paris.
3:Berlin.
4:Madrid.
5:Rome.
6:NY.
7:SF.
8:LA.
9:Seoul.
10:Beijing.
11:Tokyo.
12:Sydney.
TEST 2:
1:London.
2:Paris.
3:Berlin.
4:Madrid.
5:Rome.
6:NY.
7:SF.
8:LA.
9:Seoul.
10:Beijing.
11:Tokyo.
12:Sydney.
TEST 3:
CITIES:London; Berlin; NY; SF.
CITIES:Paris; Madrid; Rome.
CITIES:Berlin; Paris; London.
CITIES:Madrid;NY;Tokyo.
CITIES:Rome;Rome;Rome;LA;LA;LA;.
CITIES:NY.
CITIES:Sydney;Tokyo;Madrid.
CITIES:LA;SF;NY.
CITIES:Seoul;Beijing;.
CITIES:Beijing;;;;OZ;.
CITIES:London;Paris;Berlin;Madrid;Rome;NY;SF;LA;Seoul;Beijing;Tokyo;Sydney.
TEST 4:
CITIES:London; Berlin; NY; SF.
CITIES:Paris; Madrid; Rome.
CITIES:Berlin; Paris; London.
CITIES:Sydney;Tokyo;Madrid.
CITIES:London;Paris;Berlin;Madrid;Rome;NY;SF;LA;Seoul;Beijing;Tokyo;Sydney.
TEST 5:
CITIES:Paris; Madrid; Rome.
CITIES:Madrid;NY;Tokyo.
CITIES:Sydney;Tokyo;Madrid.
CITIES:London;Paris;Berlin;Madrid;Rome;NY;SF;LA;Seoul;Beijing;Tokyo;Sydney.
TEST 6:
TEST 7:
CITIES:.
The nub of the issue is the function 'INTERSECT_TABLES'. This function uses the sentence " result := A multiset intersect B is not empty; ". A and B are variables of type 'TABLE'. The operator '... multiset intersect ... is not empty' returns TRUE if tables A and B have at least one item (row) with the same value (text or number), regardless of its order or position in each table.
EXPLANATION:
I have created a temporary table named 'table_centers' and I have filled it in with some data. In order to query this table, I have created following functions:
The function 'VARCHAR_TO_TABLE' converts a string (varchar) into a 'table' type variable. You must pass a separator character as a parameter, so that each chunk of the string separated by that character will be one item (=row) of the resulting table. This way, I can use the same function regardless whether cities are separated by a semicolon (;) or by a hash (#). This function uses 'regexp_substr' and BULK COLLECT instead of a LOOP for better performance. The Keyword 'level' in statement 'regexp_substr' refers to a pseudocolumn in Oracle. See Is there a function to split a string in PL/SQL?.
In order to execute the final query to 'table_centers', I have implemented the function 'GET_CENTERS'. It has only one SELECT that selects the records of 'table_centers' that have in their field 'cities_list' at least one city in common with the string 'cities_input', which is passed as a parameter. Both strings are compared by the function 'INTERSECT_TABLES', being these strings previously splitted into tables through the function 'VARCHAR_TO_TABLE'.
The function 'INTERSECT_TABLES' is used in the clause 'WHERE' because the filtering must be done through this function. This is because a 'table' type can not be used inside a SQL query. Otherwise, you'll get an error "collection types can not be used inside a SQL statement". Therefore, using this function in the WHERE clause is mandatory. Also, boolean types can not be used, therefore, the function 'INTERSECT_TABLES' returns the numbers 0 or 1, not FALSE or TRUE.

Perhaps you are looking for multiset conditions. For example:
create or replace type number_tt as table of number;
select 'Yes' as member
from dual
where 1 member of number_tt(1,2,3);
select 'Yes' as subset
from dual
where number_tt(2,3) submultiset of number_tt(1,2,3,4);

Taking William Robertson's answer a step further, to check if at least one member of a set is a member in another set:
create or replace type number_tt as table of number;
/
with t1(id, c) as (
select 1, number_tt(1,2,3) from dual union all
select 2, number_tt(4,5,6) from dual union all
select 3, number_tt(7,8,9) from dual
)
select id, 'Yes' Intersects
from t1
where c multiset intersect number_tt(1,2,3,8) is not empty;
Yields the following results:
ID INTESECTS
1 Yes
3 Yes
Updating based on the sample data provided. Note: converting from string data to sets is left as an exercise for the student ;)
create or replace type varchar30_tt as table of varchar2(30);
/
with t1(id, c) as (
select 1, varchar30_tt('Rome','NY','London') c from dual union all
select 2, varchar30_tt('LA','SF','Torronto') c from dual union all
select 3, varchar30_tt('Paris','London','Rome') c from dual
)
select id
, 'Yes' Intesects
from t1
where c multiset intersect varchar30_tt('SF','LA','NY') is not empty;

You need OR cond -
with array_bar as (select k from table(array_bar))
select a,b,c
from table_foo
where c in array_bar
or b in array_bar
or a in array_bar;

Related

PLSQL Function to sort string that's given as parameter

following issue:
I get a String as such "512, 986, 571, 665" transferred as parameter, basically a set of 3 digit numbers and these need to be returned sorted from highest to lowest.
I have only found answers pertaining such an issue when the String is found in a table and not given as parameter to a function
Here's one option: split input string into rows (that's what subquery in lines #4 - 6 does), and then aggregate them back using listagg with appropriate order by clause (line #3).
SQL> with test (col) as
2 (select '512, 986, 571, 665' from dual)
3 select listagg(val, ', ') within group (order by val) result
4 from (select to_number(trim(regexp_substr(col, '[^,]+', 1, level))) val
5 from test
6 connect by level <= regexp_count(col, ',') + 1
7 );
RESULT
--------------------------------------------------------------------------------
512, 571, 665, 986
SQL>
A programmatic approach would be to use each element in the list as the index of an associative array (so integers only, or it'll break), which will have the effect of sorting it, then loop through the resulting array constructing a new list:
create or replace function sort_number_list
( p_list varchar2 )
return varchar2
as
type aa is table of number index by pls_integer;
sort_tab aa;
sorted_list varchar2(4000);
begin
for i in 1..regexp_count(p_list, ',') +1 loop
sort_tab(regexp_substr(p_list,'[^,]+', 1, i)) := i;
end loop;
for i in indices of sort_tab loop
sorted_list := sorted_list || ', ' || i;
end loop;
return ltrim(sorted_list,', ');
end sort_number_list;
I've used the indices of syntax added in 21c which makes looping around an associative array less verbose. Alternatively you can use the first and next collection methods.
create or replace TYPE t_numlist IS table OF varchar2(32700);
create or replace function sort_str_of_num(p_string in varchar2,p_delimiter in char) return
varchar2 as
v_strnum varchar2(32767):=p_string;
v_delimiter char(1):=p_delimiter;
l_num t_numlist:=t_numlist();
incr integer :=1;
b_bool boolean:= true;
begin
v_strnum := trim(replace(v_strnum,v_delimiter,' '));
loop
l_num.extend;
l_num(incr):= regexp_substr(v_strnum,'[0-9]{1,}');
v_strnum := trim(SUBSTR(v_strnum,instr(v_strnum,' ')+1));
if (instr(v_strnum,' ')+1)=1 then
l_num.extend;
l_num(incr+1):=regexp_substr(v_strnum,'[0-9]{1,}');
exit;
end if;
incr:=incr+1;
end loop;
for i in (select column_value from table(l_num) order by to_number(column_value))loop
if b_bool then v_strnum:=i.column_value;
else v_strnum:=i.column_value||', '||v_strnum;
end if;
b_bool:=false;
end loop;
return v_strnum;
exception
when others then dbms_output.put_line('wrong entry');
end;
This will sort any list of numbers in a string and numbers can have any digit count. Also u can enter any character to be the delimiter between numbers (delimiter must be 1 character) and space character in number list string is ignored.
One way would be to use the ability of xmltable to parse a comma-separated list into XML elements, then use its getstringval() method to convert each element into a string, then finally re-aggregate the strings using listagg:
create or replace function sort_number_list
( p_list varchar2 )
return varchar2
as
sorted_list varchar2(4000);
begin
select listagg(x.column_value.getstringval(), ', ') within group (order by to_number(x.column_value.getstringval()))
into sorted_list
from xmltable(p_list) x;
return sorted_list;
end sort_number_list;
Test:
select sort_number_list('97,3,-123,4')
from dual;
SORT_NUMBER_LIST('97,3,-123,4')
------------------------------------------------------------
-123, 3, 4, 97

Getting PLS-00103 Error. PLS-00103: Encountered the symbol "RETURN"

I am new to PL/sql and I am currently writing some PL/SQL code that has to extract data from two tables. The code that I have so far keeps getting error PLS-00103: Encountered the symbol "RETURN" when expecting one of
the following:
* & = - + ; < / > at in is mod remainder not rem
This is my code at the moment
CREATE OR REPLACE FUNCTION LISTNATION(N_NAME in VARCHAR2, R_NAME IN VARCHAR2, R_REGIONKEY IN NUMBER)
2 RETURN VARCHAR2 IS
3 R_REGIONKEY NUMBER(3);
4 R_NAME VARCHAR2(50);
5 N_NAME VARCHAR2(50);
6
7 BEGIN
8 select r_regionkey, r_name, n_name
9 from region
10 inner join nation
11 on r_regionkey = n_regionkey;
12
13 dbms_output.put_line = (R_REGIONKEY || ' ' || R_NAME || ':' || N_NAME || ',')
14
15 END;
16 /
The immediate cause of the error you're getting(in the original version of the question) is that you're missing missing a semicolon at the end of line 4.
But there are quite a few other issues:
you are using double-quotes " around string literals instead of single quotes '. Those are for identifiers, not strings.
your local variables are the same as the arguments, but you don't need all those arguments anyway.
you aren't selecting into anything; and your query will return multiple rows.
your query doesn't have a where clause so it will look for data in all regions.
you've mangled the dbms_output call; it's missing a closing parenthesis (again, in the original question) and should not have the =.
you're returning test but haven't declared that.
So, to have the function use dbms_output to display the results - which relies on the caller handling that output, which you shouldn't assume - you could maybe do:
CREATE OR REPLACE FUNCTION LISTNATION(P_R_REGIONKEY IN NUMBER)
RETURN VARCHAR2 IS
L_R_NAME REGION.R_NAME%TYPE;
L_N_NAME NATION.N_NAME%TYPE;
BEGIN
FOR l_row IN (
select r.r_name, n.n_name
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
where r.r_regionkey = p_r_regionkey
) LOOP
dbms_output.put_line (P_R_REGIONKEY || ' ' || l_row.R_NAME || ':' || l_row.N_NAME);
END LOOP;
RETURN 'test';
END;
/
That adds a filter to your query, turns it into an implicit cursor, and loops over the results.
db<>fiddle with some made-up data, with table and column names taken from your attempt.
It isn't clear what you actually want to return; you might want a comma-separated list of nation names, in which case look at the listagg() function. For example, you could do something like:
CREATE OR REPLACE FUNCTION LISTNATION(P_R_REGIONKEY IN NUMBER)
RETURN VARCHAR2 IS
L_RESULT VARCHAR2(4000);
BEGIN
select listagg(n.n_name, ',') within group (order by n.n_name)
into l_result
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
where r.r_regionkey = p_r_regionkey;
RETURN l_result;
END;
/
and you could then call that to get back a single list of values.
db<>fiddle
Although using a PL/SQL function wrapper around that query doesn't seem very useful. Presumably this is an exercise though...
is there anyway for this to display it such that it only shows the region key and region once while listing all nations?
You can change the second function to include the region info, concatenating that with the listagg result:
CREATE OR REPLACE FUNCTION LISTNATION(P_R_REGIONKEY IN NUMBER)
RETURN VARCHAR2 IS
L_RESULT VARCHAR2(4000);
BEGIN
select r.r_regionkey || ' ' || r.r_name || ': '
|| listagg(n.n_name, ',') within group (order by n.n_name)
into l_result
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
where r.r_regionkey = p_r_regionkey
group by r.r_regionkey, r.r_name;
RETURN l_result;
END;
/
then call that for each region key you're interested in.
db<>fiddle
The way you've phrased it makes it sound like you don't want to pass in a region key and instead want to see all regions at once; and want to use dbms_output, which isn't ideal; and don't really want to return anything. So you could use a procedure instead, change the cursor query to bring back the region name, and then concatenate in the put_line call inside the loop:
CREATE OR REPLACE PROCEDURE LISTNATION IS
BEGIN
FOR l_row IN (
select r.r_regionkey, r.r_name,
listagg(n.n_name, ',') within group (order by n.n_name) as names
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
group by r.r_regionkey, r.r_name
order by r.r_regionkey
) LOOP
dbms_output.put_line (l_row.R_REGIONKEY || ' ' || l_row.R_NAME || ': ' || l_row.names);
END LOOP;
END;
/
dbms_output:
1 EMEA: FRANCE,UNITED KINGDOM
2 APAC: CHINA
db<>fiddle
There are lots of variations of course, it depends exactly what you want to have happen, but you should be able to adapt one of these approaches. Another is to have a function or procedure generate a ref cursor, but again it's not clear what you want. But using dbms_output isn't a great idea, as the caller may not be using a client that looks for and displays that.

Converting data type Number to varchar2

I'm currently working on creating a stored procedure in PL SQL that will turn a data type number 4 to the varchar2 to A.
SELECT s.sname, g.sid, s.sid, c.cid, g.cid,
CAST(CASE g.grade
WHEN 4 THEN 'A'
WHEN 3 THEN 'B'
WHEN 2 THEN 'C'
WHEN 1 THEN 'D'
WHEN 0 THEN 'F'
ELSE 'No Value'
END AS varchar2(55)) AS Grades
FROM student s, grades g, class c
WHERE c.cid = g.cid
AND g.sid = s.sid
AND g.sid = s.sid;
This is works as is but once I add in this in a create procedure it errors out as Unexpected error
Error code -6502: ORA-06502: PL/SQL: numeric or value error:character to number conversion error.
Also, I have tried adding the below within the procedure and return the same error.
EXIT WHEN c1%NOTFOUND; -- exit check
dbms_output.put_line('Student Name: '|| s_sname);
dbms_output.put_line('Class Name: ' || c_cname);
dbms_output.put_line('Grade: ' || to_char(g_grade, 'ABCDF');
dbms_output.put_line('-----------------');
In the above I'm trying to print the letter grade and not the numerical grade.
You need to paste the entire code. It works for me.
create table testing(i int);
insert into testing values (0);
insert into testing values (1);
insert into testing values (2);
insert into testing values (3);
create or replace procedure p
is
begin
for i in (select CAST(CASE g.grade
WHEN 4 THEN 'A'
WHEN 3 THEN 'B'
WHEN 2 THEN 'C'
WHEN 1 THEN 'D'
WHEN 0 THEN 'F'
ELSE 'No Value'
END AS varchar2(55)) AS Grades
from testing
)
loop
dbms_output.put_line(i.Grades);
end loop;
end;
/
set serveroutput on;
begin
p;
end;
/
D
C
F
B
PL/SQL procedure successfully completed.

The procedure below(when i uncomment the commented parts) shows 'no data found'..?

Hi I'm trying the code the below logic..i need help..
When i run the following procedure i get all the sids along with the corresponding pid for a particular month. but when i uncomment the parts that i have commented here i get the month and year displayed and then a message saying 'no data found'. Where am i going wrong?
create or replace PROCEDURE mas(V_MONTH NUMBER DEFAULT NULL,V_YEAR NUMBER DEFAULT NULL,V_AID VARCHAR2) AS
V_MID VARCHAR2(50);
V_SID VARCHAR2(50);
v_v_month number := nvl(V_MONTH,to_number(to_char(sysdate,'mm')));
v_v_year number := nvl(V_YEAR,to_number(to_char(sysdate,'yyyy')));
v_is_sub PM.IS_SUB%TYPE;
V_DIST_s NUMBER;
V_DIST_t NUMBER;
cursor c1
is
select distinct a.mid,
b.sid
from pmt a
inner join smt b
on (a.mid = b.mid)
where a.AID = V_AID
AND A.AID = B.AID
AND EXTRACT(MONTH FROM A.RDATE)= v_v_month AND
EXTRACT(YEAR FROM A.RDATE)= v_v_year
order by mid;
BEGIN
dbms_output.put_line('month : ' || v_v_month);
dbms_output.put_line('year : ' || v_v_year);
/*
select IS_SUB into v_is_sub from program_master where pid = 'V_AID';
IF v_is_sub = 1 then
select count(*) into V_DIST_S from (select distinct sid from smt where aid = 'v_aid');
select count(*) into V_DIST_T from (select distinct sid from tm where aid = 'v_aid');
if(V_DIST_S = V_DIST_T) then
*/
for rec1 in c1
loop
dbms_output.put_line('MID : ' || rec1.MID);
dbms_output.put_line('SID : ' || rec1.SID);
end loop;
-- else
-- dbms_output.put_line('count of sids do not match');
--end if;
--else
--dbms_output.put_line('No sids available for the mentioned pid.');
--end if;
END MAS;
where pid = 'V_AID';
V_AID is a variable, however, you have enclosed it within single-quotation marks, which makes it a string. So, you are actually looking for the value 'V_AID' and not the value of the variable.
Modify it to:
select IS_SUB into v_is_sub from program_master where pid = V_AID;
And do the same wherever you have enclosed the variable in single-quotation marks.

Oracle PLSQL: Help Required: Parsing String Collection or Concatenated String

Whenever the length of string l_long_string is above 4000 characters, the following code is throwing an error:
ORA-01460: unimplemented or unreasonable conversion requested
Instead of the nested regexp_substr query, when I try to use
SELECT column_value
FROM TABLE(l_string_coll)
it throws:
ORA-22905: cannot access rows from a non-nested table item
How can I modify the dynamic query?
Notes:
- l_string_coll is of type DBMS_SQL.VARCHAR2S, and comes as input to my procedure (here, i have just shown as an anonymous block)
- I'll have to manage without creating a User-defined Type in DB schema, so I am using the in-built DBMS_SQL.VARCHAR2S.
- This is not the actual business procedure, but is close to this. (Can't post the original)
- Dynamic query has to be there since I am using it for building the actual query with session, current application schema name etc.
/*
CREATE TABLE some_other_table
(word_id NUMBER(10), word_code VARCHAR2(30), word VARCHAR2(255));
INSERT INTO some_other_table VALUES (1, 'A', 'AB');
INSERT INTO some_other_table VALUES (2, 'B', 'BC');
INSERT INTO some_other_table VALUES (3, 'C', 'CD');
INSERT INTO some_other_table VALUES (4, 'D', 'DE');
COMMIT;
*/
DECLARE
l_word_count NUMBER(10) := 0;
l_counter NUMBER(10) := 0;
l_long_string VARCHAR2(30000) := NULL;
l_dyn_query VARCHAR2(30000) := NULL;
l_string_coll DBMS_SQL.VARCHAR2S;
BEGIN
-- l_string_coll of type DBMS_SQL.VARCHAR2S comes as Input to the procedure
FOR i IN 1 .. 4100
LOOP
l_counter := l_counter + 1;
l_string_coll(l_counter) := 'AB';
END LOOP;
-- Above input collection is concatenated into CSV string
FOR i IN l_string_coll.FIRST .. l_string_coll.LAST
LOOP
l_long_string := l_long_string || l_string_coll(i) || ', ';
END LOOP;
l_long_string := TRIM(',' FROM TRIM(l_long_string));
dbms_output.put_line('Length of l_long_string = ' || LENGTH(l_long_string));
/*
Some other tasks in PLSQL done successfully using the concatenated string l_long_string
*/
l_dyn_query := ' SELECT COUNT(*)
FROM some_other_table
WHERE word IN ( SELECT TRIM(REGEXP_SUBSTR(str, ''[^,]+'', 1, LEVEL)) word
FROM ( SELECT :string str FROM SYS.DUAL )
CONNECT BY TRIM(REGEXP_SUBSTR(str, ''[^,]+'', 1, LEVEL)) IS NOT NULL )';
--WHERE word IN ( SELECT column_value FROM TABLE(l_string_coll) )';
EXECUTE IMMEDIATE l_dyn_query INTO l_word_count USING l_long_string;
dbms_output.put_line('Word Count = ' || l_word_count);
EXCEPTION
WHEN OTHERS
THEN
dbms_output.put_line('SQLERRM = ' || SQLERRM);
dbms_output.put_line('FORMAT_ERROR_BAKCTRACE = ' || dbms_utility.format_error_backtrace);
END;
/
How can I modify the dynamic query?
First of all. Based on the code you've provided, there is absolutely no need to use dynamic, native or DBMS_SQL dynamic SQL at all.
Secondly, SQL cannot operate on "strings" that are greater than 4K bytes in length(Oracle versions prior to 12c), or 32K bytes(Oracle version 12cR1 and up, if MAX_STRING_SIZE initialization parameter is set to EXTENDED).
PL/SQL, on the other hand, allows you to work with varchar2() character strings that are greater than 4K bytes (up to 32Kb) in length. If you just need to count words in a comma separated sting, you can simply use regexp_count() regular expression function(Oracle 11gr1 and up) as follows:
set serveroutput on;
set feedback off;
clear screen;
declare
l_str varchar2(100) := 'aaa,bb,ccc,yyy';
l_numOfWords number;
begin
l_numOfWords := regexp_count(l_str, '[^,]+');
dbms_output.put('Number of words: ');
dbms_output.put_line(to_char(l_numOfWords));
end;
Result:
Number of words: 4

Resources