how to look up another field in Oracle Function - oracle

Table 1
ID
----------
1
2
3
4
5
Table 2
ID Desc
------------------------------
A1 Apple
A2 Pear
A3 Orange
I am trying to create a Function in Oracle, so that it add the prefix 'A' in Table 1, and after that I want to look up in Table 2 to get the DESC returned. It has to be a function.
Thank you!!!

You may use the following for creation of such a function :
Create or Replace Function Get_Fruit( i_id table2.description%type )
Return table2.description%type Is
o_desc table2.description%type;
Begin
for c in ( select description from table2 where id = 'A'||to_char(i_id) )
loop
o_desc := c.description;
end loop;
return o_desc;
End;
where
no need to include exception handling, because of using cursor
instead of select into clause.
using table_name.col_name%type for declaration of data types for
arguments or variables makes the related data type of the columns
dynamic. i.e. those would be able to depend on the data type of the
related columns.
the reserved keywords such as desc can not be used as column names
of tables, unless they're expressed in double quotes ("desc")
To call that function, the following might be preferred :
SQL> set serveroutput on
SQL> declare
2 i_id pls_integer := 1;
3 o_fruit varchar2(55);
4 begin
5 o_fruit := get_fruit( i_id );
6 dbms_output.put_line( o_fruit );
7 end;
8 /
Apple
PL/SQL procedure successfully completed

I am not sure with your question- Are you trying to achieve something like this:-
CREATE OR REPLACE FUNCTION Replace_Value
(
input_ID IN VARCHAR2
) RETURN VARCHAR2
AS
v_ID varchar(2);
BEGIN
begin
SELECT distinct a.ID into v_id from Table 2 a where a.ID in (select 'A'||b.id from table1 b where b.id=input_ID);
exception
when others then
dbms_output.put_line(sqlcode);
end;
RETURN v_id;
END Replace_Value;

Are you trying for something like this?
CREATE OR replace FUNCTION replace_value (table_name IN VARCHAR2,
input_id IN INTEGER)
RETURN VARCHAR2
AS
v_desc VARCHAR(20);
BEGIN
SELECT descr
INTO v_desc
FROM table2
WHERE id = 'A' || input_id
AND ROWNUM = 1; -- only needed if there are multiple rows for each id.
RETURN v_desc;
END replace_value;
You may also add an exception handling for NO_DATA_FOUND or INVALID_NUMBER

Related

Variables in PLSQL

I want to get the value defined in my Procedure as mentioned below.
declare
city varchar2(50) := 'XYZ';
TYPE table_type is table of table_name%rowtype;
var table_type;
begin
select * bulk collect into var from table_name;
DBMS_OUTPUT.PUT_LINE(var(1).field); -- Output is City
I want the output of
DBMS_OUTPUT.PUT_LINE(var(1).field); -- Output is XYZ
How can I achieve this?
First of all, your code does not work. Second, if you are going to recover a variable, you don't need bulk collect
Example
create table mytest ( c1 varchar2(50) ;
insert into mytest values ( 'XYZ' );
commit;
Then
SQL> set serveroutput on
declare
vcity mytest.c1%type := 'XYZ';
var mytest.c1%type;
begin
select c1 into var from mytest where c1=vcity;
DBMS_OUTPUT.PUT_LINE('vcity is '||vcity||' ');
DBMS_OUTPUT.PUT_LINE('var is '||var||' ');
end;
/SQL> 2 3 4 5 6 7 8 9
vcity is XYZ
var is XYZ
PL/SQL procedure successfully completed.
SQL>
vcity is just a variable defined with the type of the column c1, but
assigned to a constant, in this case 'XYZ'
var is the variable which I use to recover the value of c1 in the
table, whatever that value is.
You don't need bulk collect here at all.
Assuming you have several cities to list, you need to define a collection to house them once selected, then a variable of that collection. So for example:
--setup
create table table_name ( id integer
, city varchar2(10)
);
insert into table_name(id, city)
select level, 'City #' || trunc(dbms_random.value( 10, 75 ))
from dual connect by level <= 10;
-- process
declare
type city_list is table of table_name%rowtype;
var city_list;
begin
select *
bulk collect
into var
from table_name;
for r in 1 .. var.count
loop
dbms_output.put_line(var(r).city); -- Output is City
end loop;
end;
Let's keep remain mystery about why you want like this,
If I understood you , to achieve what you want you have to specify columns instead of '*' and replace the variable city with column field.
Dbfiddle link for your reference (unable to provide via link through mobile device)
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=20f1a1fff11d4acda7acc701ad120d32
DECLARE
city varchar2(50) := 'XYZ';
TYPE table1_type is table of table1%rowtype;
var table1_type;
BEGIN
select city,field2,field3,field4
bulk collect into var
from table1;
DBMS_OUTPUT.PUT_LINE(var(1).field);
END;
/

Unable to create table function in oracle, type mismatch found between FETCH cursor and INTO variable

I am trying to create a table function to use in tableau's custom SQL, but I am getting an error, type mismatch found between FETCH cursor and INTO variable. Below is the code I am trying, I have created a type object and table of that type object. Function my_fct should return the table with a select statement output.
CREATE
OR replace type DATA_OBJ AS OBJECT (
id varchar2(10)
);
CREATE
OR replace type
DATA_OBJ_TAB AS TABLE OF DATA_OBJ;
CREATE OR REPLACE FUNCTION my_fct()
RETURN DATA_OBJ_TAB PIPELINED
AS
TYPE CurTyp IS REF CURSOR RETURN DATA_OBJ_TAB%ROWTYPE;
rc CurTyp;
CURSOR data IS SELECT ID from alumni_data;
BEGIN
FOR rc IN data LOOP
PIPE ROW (rc);
END LOOP;
END;
This can be implemented with a packaged PTF without using the SQL data types at all.
Something like this:
create table alumni_data (id, memo) as
select rownum id, 'memo '||rownum from dual connect by level<=3
/
create or replace package pack as
type arrT is table of alumni_data%rowtype;
function get (c varchar2) return arrT pipelined;
end;
/
create or replace package body pack as
function get (c varchar2) return arrT pipelined is
arr arrT;
begin
select * bulk collect into arr
from alumni_data
where memo like c||'%';
for i in 1..arr.count loop
pipe row (arr(i));
end loop;
return;
end;
end;
/
Result:
select * from pack.get ('mem');
ID MEMO
---------- ---------------------------------------------
1 memo 1
2 memo 2
3 memo 3
Have a look at the following example:
SQL> create or replace type data_obj as object
2 (id varchar2(10));
3 /
Type created.
SQL> create or replace type
2 data_obj_tab as table of data_obj;
3 /
Type created.
SQL> create or replace function my_fct
2 return data_obj_tab pipelined
3 as
4 l_vc data_obj := data_obj(null);
5 begin
6 for cur_r in (select id from alumni_data) loop
7 l_vc.id := cur_r.id;
8 pipe row (l_vc);
9 end loop;
10 return;
11 end;
12 /
Function created.
SQL> select * from table(my_fct);
ID
----------
CLARK
KING
MILLER
SQL>

collection of records to out sys_refcursor

Oracle 11g
This seems harder than it should be so I might be on the wrong path here.
I have an application that generates user defined forms, my data is a bit more complicated than this but the idea is -- I have a data table which contains all the data input from the user defined forms
create table formData(
id number
, fName varchar(100)
, lName varChar(100)
, mName varChar(100)
, formType varchar(100)
...
);
insert all
into formData(id,fName,lName,mName,formType)values(1,'Bob','Smith',NULL,'birthday')
into formData(id,fName,lName,mName,formType)values(2,'Jim','Jones','Wilber','birthday')
into formData(id,fName,lName,mName,formType)values(3,'Frank','Peterson',NULL,'general')
into formData(id,fName,lName,mName,formType)values(4,'Alex','Anderson',NULL,'general')
I have a table which contains the field options for the dynamic forms
create table fieldOptions(
id number
, fieldName varchar(100)
, fieldLabel varChar(100)
, formType varchar(10)
, fieldUsed number
, ...
);
insert all
into fieldOptions (fieldName,fieldLabel,formType,fieldUsed)values('fName','First Name','birthday',1)
into fieldOptions (fieldName,fieldLabel,formType,fieldUsed)values('lName','Last Name','birthday',1)
into fieldOptions (fieldName,fieldLabel,formType,fieldUsed)values('mName','Middle','birthday',1)
into fieldOptions (fieldName,fieldLabel,formType,fieldUsed)values('fName','First','general',1)
into fieldOptions (fieldName,fieldLabel,formType,fieldUsed)values('lName','Surname','general',1)
into fieldOptions (fieldName,fieldLabel,formType,fieldUsed)values('mName','Middle Initial','general',0)
I want to create a procedure in my package that will return a cursor to my .net page that contains data that looks like:
where ID=3 (general output)
Label | Value
--------+---------
First | Frank
Surname | Peterson
or where ID=1 (birthday output)
Label | Value
------------+---------
First Name | Bob
Last Name | Smith
Middle | NULL
I'm unsure if I can do this in a (pivot?) query. I started toying with a collection of records built by processing the data but how would I get a collection of records into an out sys_refcursor if that's the solution? Perhaps I'm over thinking this and it can be done with a few sub queries? A shove in the right direction would be perfect, thanks.
Assuming your formData table structure is fixed and known, you can just use a case expression to translate the formOption.fName to the matching column value:
select fo.fieldLabel as label,
case fo.fieldName
when 'fName' then fd.fName
when 'lName' then fd.lName
when 'nName' then fd.mName
end as value
from formData fd
join fieldOptions fo
on fo.formType = fd.formtype
where fd.id = 3;
LABEL VALUE
-------------------- --------------------
First Frank
Surname Peterson
Middle Initial
...
where fd.id = 3;
LABEL VALUE
-------------------- --------------------
First Name Bob
Last Name Smith
Middle
You can then have your procedure open a ref cursor for that query, using an argument value for the ID value.
If the formData structure isn't known, or isn't static, then you probably have bigger problems; but for this you'd need to fall back to dynamic SQL. As a starting point, you could do something like:
create procedure p42 (p_id number, p_refcursor out sys_refcursor) as
l_stmt varchar2(32767);
begin
l_stmt := 'select fo.fieldLabel as label, case lower(fo.fieldName) ';
for r in (
select column_name from user_tab_columns
where table_name = 'FORMDATA'
and data_type = 'VARCHAR2'
)
loop
l_stmt := l_stmt || ' when ''' || lower(r.column_name) || ''' then fd.' || r.column_name;
end loop;
l_stmt := l_stmt || ' end as value '
|| 'from formData fd '
|| 'join fieldOptions fo '
|| 'on fo.formType = fd.formtype '
|| 'where fd.id = :d1';
open p_refcursor for l_stmt using p_id;
end p42;
/
This uses all the columns actually defined in the table to create the case expression at run time; because the case of your fieldName may not match the data dictionary, I'm forcing everything to lowercase for comparison. I'm also restricting to string columns to make the case simpler, but if you need columns which are other data types then each when ... then clause of the case expressions would need to check that column's data type (which you can add to the r cursor) and convert the actual column value to a string appropriately. All of the values have to end up the same data type, so it has to be strings, really.
Anyway, testing this from SQL*Plus:
var rc refcursor
exec p42(1, :rc);
PL/SQL procedure successfully completed.
print rc
LABEL VALUE
-------------------- --------------------
First Name Bob
Last Name Smith
Middle
3 rows selected.
You could query fieldOptions to get the possible column names instead, but you still may have the data type conversion issue, wich would be harder to deal with; but if all the referenced formData fields are actually strings then that would be:
for r in (
select fo.fieldName
from formData fd
join fieldOptions fo
on fo.formType = fd.formtype
where fd.id = p_id
)
loop
l_stmt := l_stmt || ' when ''' || r.fieldName || ''' then fd.' || r.fieldName;
end loop;
If your logic is complex, you can consider generating your query rows programatically using Oracle table functions. Basically you generate a collection of records and "convert it to table" using the table() operator, as in select ... from table(your_table_function) ... , which can be made through a standard sys_refcursor. Taken from the linked example:
-- Create the types to support the table function.
DROP TYPE t_tf_tab;
DROP TYPE t_tf_row;
CREATE TYPE t_tf_row AS OBJECT (
id NUMBER,
description VARCHAR2(50)
);
/
CREATE TYPE t_tf_tab IS TABLE OF t_tf_row;
/
-- Build the table function itself.
CREATE OR REPLACE FUNCTION get_tab_tf (p_rows IN NUMBER) RETURN t_tf_tab AS
l_tab t_tf_tab := t_tf_tab();
BEGIN
FOR i IN 1 .. p_rows LOOP
l_tab.extend;
l_tab(l_tab.last) := t_tf_row(i, 'Description for ' || i);
END LOOP;
RETURN l_tab;
END;
/
-- Test it.
SELECT *
FROM TABLE(get_tab_tf(10))
ORDER BY id DESC;
ID DESCRIPTION
---------- --------------------------------------------------
10 Description for 10
9 Description for 9
8 Description for 8
7 Description for 7
6 Description for 6
5 Description for 5
4 Description for 4
3 Description for 3
2 Description for 2
1 Description for 1
10 rows selected.
If the collection can be large, it can be useful to stream it using a pipelined table function, which works a bit like yield in c#. Again, following the examples in the linked page:
-- Build a pipelined table function.
CREATE OR REPLACE FUNCTION get_tab_ptf (p_rows IN NUMBER) RETURN t_tf_tab PIPELINED AS
BEGIN
FOR i IN 1 .. p_rows LOOP
PIPE ROW(t_tf_row(i, 'Description for ' || i));
END LOOP;
RETURN;
END;
/
-- Test it.
SELECT *
FROM TABLE(get_tab_ptf(10))
ORDER BY id DESC;
ID DESCRIPTION
---------- --------------------------------------------------
10 Description for 10
9 Description for 9
8 Description for 8
7 Description for 7
6 Description for 6
5 Description for 5
4 Description for 4
3 Description for 3
2 Description for 2
1 Description for 1
10 rows selected.
SQL>

How to change below procedure to (update & insert) instead of (delete &insert) in Oracle

I want to update the record if already exists based on where condition or else insert. I have written query with delete and insert I am facing difficulty into converting into update and insert.
PS: I have tried using SQL%ROWCOUNT but I think i missed somewhere in syntax.
Any help would be appreciated.
Below is the proc i am using
Create Or Replace Procedure sal_proc(Empid Varchar2,Fmdt Date,bp Number)
As
Begin
--delete
delete from Emp_Sal
Where Empid = Empid
And Fmdt = Fmdt;
--insert
Insert Into Emp_Sal(empid,fmdt,Basicpay) Values (empid,fmdt,Bp);
End;
You do not need a procedure; you can use MERGE to update or insert a row at the same time:
merge into Emp_Sal e
using (
/* your values to insert/update */
select 2 as Empid, 'c' as Fmdt, 100 as Bp from dual
) x
on ( e.Empid = x.Empid And e.Fmdt = x.Fmdt)
when matched
then /* if a record exists, update */
update set Basicpay = Bp
when not matched
then /* it the record does not exist, insert */
insert values (x.empid, x.fmdt, x.Bp)
Of course you can use this inside a procedure to handle your input parameters or do whatever you may need to do in your procedure:
Create Or Replace Procedure sal_proc(Empid Varchar2,Fmdt Date,bp Number)
As
Begin
merge into Emp_Sal e
using (
/* your values to insert/update */
select Empid as Empid, Fmdt as Fmdt, bp as Bp from dual
) x
on ( e.Empid = x.Empid And e.Fmdt = x.Fmdt)
when matched
then /* if a record exists, update */
update set Basicpay = Bp
when not matched
then /* it the record does not exist, insert */
insert (empid,fmdt,Basicpay) values (x.empid, x.fmdt, x.Bp);
End;
An hint: use parameter names different fron column names to avoid confusion; a good practice could be use parameter names like p_XXX; just an example of how dangerous this can be:
SQL> create or replace procedure checkPar(n in number) is
2 c number;
3 begin
4 select count(1)
5 into c
6 from checkTab
7 where n = n;
8 --
9 dbms_output.put_line(c);
10 end;
11 /
Procedure created.
SQL> select * from checkTab;
N
----------
1
2
3
SQL> exec checkPar(1);
3
PL/SQL procedure successfully completed.
SQL> exec checkPar(999);
3
PL/SQL procedure successfully completed.
Not very efficient way but should work if huge data is not present in tables
CREATE OR REPLACE PROCEDURE sal_proc (E_Empid VARCHAR2, F_Fmdt DATE, b_bp NUMBER)
AS
var number;
BEGIN
Begin
--Checking if record exists
select 1
into var
from Emp_Sal
Where Empid = E_Empid
And Fmdt = F_Fmdt;
exception
when no_data_found then
var:= 0;
End;
if var <> 1 then
--insert
INSERT INTO Emp_Sal (empid, fmdt, Basicpay)
VALUES (e_empid, f_fmdt, b_Bp);
else
update Emp_Sal
set col ....<>;
end if;
END;

Selecting Values from Oracle Table Variable / Array?

Following on from my last question (Table Variables in Oracle PL/SQL?)...
Once you have values in an array/table, how do you get them back out again? Preferably using a select statement or something of the like?
Here's what I've got so far:
declare
type array is table of number index by binary_integer;
pidms array;
begin
for i in (
select distinct sgbstdn_pidm
from sgbstdn
where sgbstdn_majr_code_1 = 'HS04'
and sgbstdn_program_1 = 'HSCOMPH'
)
loop
pidms(pidms.count+1) := i.sgbstdn_pidm;
end loop;
select *
from pidms; --ORACLE DOESN'T LIKE THIS BIT!!!
end;
I know I can output them using dbms_output.putline(), but I'm hoping to get a result set like I would from selecting from any other table.
Thanks in advance,
Matt
You might need a GLOBAL TEMPORARY TABLE.
In Oracle these are created once and then when invoked the data is private to your session.
Oracle Documentation Link
Try something like this...
CREATE GLOBAL TEMPORARY TABLE temp_number
( number_column NUMBER( 10, 0 )
)
ON COMMIT DELETE ROWS;
BEGIN
INSERT INTO temp_number
( number_column )
( select distinct sgbstdn_pidm
from sgbstdn
where sgbstdn_majr_code_1 = 'HS04'
and sgbstdn_program_1 = 'HSCOMPH'
);
FOR pidms_rec IN ( SELECT number_column FROM temp_number )
LOOP
-- Do something here
NULL;
END LOOP;
END;
/
In Oracle, the PL/SQL and SQL engines maintain some separation. When you execute a SQL statement within PL/SQL, it is handed off to the SQL engine, which has no knowledge of PL/SQL-specific structures like INDEX BY tables.
So, instead of declaring the type in the PL/SQL block, you need to create an equivalent collection type within the database schema:
CREATE OR REPLACE TYPE array is table of number;
/
Then you can use it as in these two examples within PL/SQL:
SQL> l
1 declare
2 p array := array();
3 begin
4 for i in (select level from dual connect by level < 10) loop
5 p.extend;
6 p(p.count) := i.level;
7 end loop;
8 for x in (select column_value from table(cast(p as array))) loop
9 dbms_output.put_line(x.column_value);
10 end loop;
11* end;
SQL> /
1
2
3
4
5
6
7
8
9
PL/SQL procedure successfully completed.
SQL> l
1 declare
2 p array := array();
3 begin
4 select level bulk collect into p from dual connect by level < 10;
5 for x in (select column_value from table(cast(p as array))) loop
6 dbms_output.put_line(x.column_value);
7 end loop;
8* end;
SQL> /
1
2
3
4
5
6
7
8
9
PL/SQL procedure successfully completed.
Additional example based on comments
Based on your comment on my answer and on the question itself, I think this is how I would implement it. Use a package so the records can be fetched from the actual table once and stored in a private package global; and have a function that returns an open ref cursor.
CREATE OR REPLACE PACKAGE p_cache AS
FUNCTION get_p_cursor RETURN sys_refcursor;
END p_cache;
/
CREATE OR REPLACE PACKAGE BODY p_cache AS
cache_array array;
FUNCTION get_p_cursor RETURN sys_refcursor IS
pCursor sys_refcursor;
BEGIN
OPEN pCursor FOR SELECT * from TABLE(CAST(cache_array AS array));
RETURN pCursor;
END get_p_cursor;
-- Package initialization runs once in each session that references the package
BEGIN
SELECT level BULK COLLECT INTO cache_array FROM dual CONNECT BY LEVEL < 10;
END p_cache;
/
The sql array type is not neccessary. Not if the element type is a primitive one. (Varchar, number, date,...)
Very basic sample:
declare
type TPidmList is table of sgbstdn.sgbstdn_pidm%type;
pidms TPidmList;
begin
select distinct sgbstdn_pidm
bulk collect into pidms
from sgbstdn
where sgbstdn_majr_code_1 = 'HS04'
and sgbstdn_program_1 = 'HSCOMPH';
-- do something with pidms
open :someCursor for
select value(t) pidm
from table(pidms) t;
end;
When you want to reuse it, then it might be interesting to know how that would look like.
If you issue several commands than those could be grouped in a package.
The private package variable trick from above has its downsides.
When you add variables to a package, you give it state and now it doesn't act as a stateless bunch of functions but as some weird sort of singleton object instance instead.
e.g. When you recompile the body, it will raise exceptions in sessions that already used it before. (because the variable values got invalided)
However, you could declare the type in a package (or globally in sql), and use it as a paramter in methods that should use it.
create package Abc as
type TPidmList is table of sgbstdn.sgbstdn_pidm%type;
function CreateList(majorCode in Varchar,
program in Varchar) return TPidmList;
function Test1(list in TPidmList) return PLS_Integer;
-- "in" to make it immutable so that PL/SQL can pass a pointer instead of a copy
procedure Test2(list in TPidmList);
end;
create package body Abc as
function CreateList(majorCode in Varchar,
program in Varchar) return TPidmList is
result TPidmList;
begin
select distinct sgbstdn_pidm
bulk collect into result
from sgbstdn
where sgbstdn_majr_code_1 = majorCode
and sgbstdn_program_1 = program;
return result;
end;
function Test1(list in TPidmList) return PLS_Integer is
result PLS_Integer := 0;
begin
if list is null or list.Count = 0 then
return result;
end if;
for i in list.First .. list.Last loop
if ... then
result := result + list(i);
end if;
end loop;
end;
procedure Test2(list in TPidmList) as
begin
...
end;
return result;
end;
How to call it:
declare
pidms constant Abc.TPidmList := Abc.CreateList('HS04', 'HSCOMPH');
xyz PLS_Integer;
begin
Abc.Test2(pidms);
xyz := Abc.Test1(pidms);
...
open :someCursor for
select value(t) as Pidm,
xyz as SomeValue
from table(pidms) t;
end;

Resources