Currently working on a .Net application for viewing the contents of a table, I faced difficulties with the function that returns the contents of the table.
I use the Oracle WebLogic Server to interface comics and application, I have a function that returns a desired record of the table but I unable to adapt to it returns all records of table.
I used tables, objects I have not been able to operate the pipeline.
Here is the function that returns a record.
CREATE OR REPLACE TYPE DMD_REC AS OBJECT
(
matricule VARCHAR2(10),
nom VARCHAR2(15),
prenom VARCHAR2(15),
adresse VARCHAR2(10),
profile VARCHAR2(15),
service VARCHAR2(15),
date_dmd DATE
);
CREATE OR REPLACE FUNCTION aff_dmd
(dmd_mat IN VARCHAR2)
RETURN DMD_REC IS
dmd_found demande%rowtype;
dmd_rtn DMD_REC;
BEGIN
SELECT *
INTO dmd_found
FROM demande
WHERE demande.matricule=dmd_mat;
dmd_rtn := DMD_REC
(
dmd_found.matricule,
dmd_found.nom,
dmd_found.prenom,
dmd_found.adresse,
dmd_found.profile,
dmd_found.service,
dmd_found.date_dmd
);
RETURN dmd_rtn;
END aff_dmd;
Sorry for my english
Your function returns an object which fits one row. You need it to return a table of such objects.
So first you need a table type:
CREATE OR REPLACE TYPE DMD_NT AS TABLE OF DMD_REC;
Then you have to convert your function to use this new type. In the absence of any other requirements this revision will select all records in the demande table when a NULL is passed:
CREATE OR REPLACE FUNCTION aff_dmd
(dmd_mat IN VARCHAR2 := null)
RETURN DMD_NT
IS
dmd_rtn DMD_NT;
BEGIN
SELECT DMD_REC
(
d.matricule,
d.nom,
d.prenom,
d.adresse,
d.profile,
d.service,
d.date_dmd
)
BULK COLLECT INTO dmd_rtn
FROM demande
WHERE
( dmd_mat is null
or d.matricule = dmd_mat
);
RETURN dmd_rtn;
END aff_dmd;
/
The BULK COLLECT populates a collection which is stored in session memory. If your table is large (say more than 5000) this may create problems with memory resources. In which case you should consider creating a pipelined function:
CREATE OR REPLACE FUNCTION aff_dmd
(dmd_mat IN VARCHAR2 := null)
RETURN DMD_NT PIPELINED
IS
dmd_rtn DMD_NT;
BEGIN
loop
SELECT DMD_REC
(
d.matricule,
d.nom,
d.prenom,
d.adresse,
d.profile,
d.service,
d.date_dmd
)
BULK COLLECT INTO dmd_rtn limit 1000
FROM demande
WHERE
( dmd_mat is null
or d.matricule = dmd_mat
);
exit when dmd_rtn.count() = 0;
for idx in 1..dmd_rtn.count() loop
pipe row (dmd_rtn(idx));
end loop;
end loop
RETURN;
END aff_dmd;
Pipelined functions have an overhead. You should consider whether there is a way you can make it work with SYS_REFCURSOR as #BobJarvis suggested.
Related
I am trying to write Oracle SQL function. The should take country code, min year and max year as inputs and should return table which contains information for that country in the specified years. This is what I tried to write, but I am new to SQL functions. This is how the data looks and I will be glad for any help.
create or replace type african_crisis_row as object(
country_abv varchar(4),
year number(5),
banking_crisis varchar(10)
);
create or replace type t_african_crisis_table as table of african_crisis_row;
create or replace function african_crisis (
country_abv in varchar,
year_min in number,
year_max in number
)
return t_african_crisis_table as v_ret table t_african_crisis_table;
begin
select
african_crisis_row(country_abv, year)
bulk collect into
v_ret
from
africancrisisdata
where
country_abv = country_abv and year between year_min and year_max;
return v_ret
end african_crisis
You need to:
remove table after the v_ret declaration.
Include the 3rd banking_crisis value in the call to the african_crisis_row object constructor.
Include ; statement terminators after the return and final end statements.
Don't name the function parameters with the same name as the column values.
(Oracle uses VARCHAR2 and VARCHAR is an alias to VARCHAR2.)
Something like this:
create or replace function african_crisis (
i_country_abv in varchar2,
i_year_min in number,
i_year_max in number
) return t_african_crisis_table
as
v_ret t_african_crisis_table;
begin
select african_crisis_row(country_abv, year, banking_crisis)
bulk collect into v_ret
from africancrisisdata
where country_abv = i_country_abv
and year between i_year_min and i_year_max;
return v_ret;
end african_crisis;
/
db<>fiddle here
So I have a table called Mroom which looks something like this:
CREATE Mroom(ID_ROOM NUMBER(3), CHAR_NAME NVARCHAR2(30), CHAR_VALUE NUMBER(20));
The contents of the table are something like:
1; air-conditioner; 1
1; pool; 2
2; pool; 1
Which would mean that the room with the ID 1 has 1 air-conditioner and 2 pools. And the room with the ID 2 has one pool.
I've also created a table of a datatype, that I've called c_chars:
CREATE OR REPLACE TYPE c_chars FORCE IS OBJECT (CHAR VARCHAR2(20), VALUE NUMBER(30));
/
CREATE OR REPLACE TYPE tab_chars FORCE IS TABLE OF c_chars;
/
And I need to know if for example if there are any rooms that have at least 1 air-conditioner and a pool. My current code:
CREATE OR REPLACE PROCEDURE thisRoomOK(v_carateristicas IN tab_chars, lista OUT SYS_REFCURSOR)
IS BEGIN
OPEN lista FOR SELECT MRoom.ID_ROOM
FROM Mroom, TABLE(v_carateristicas) v_carateristicas
WHERE Mroom.CHAR_NAME = v_carateristicas.CHAR
AND Mroom.VALUE >= v_carateristicas.VALUE
GROUP BY Mroom.ID_ROOM
ORDER BY getSalas.Mroom.ID_ROOM;
END;
/
The problem is if I ask for the rooms that have at least one air-conditioner and a pool. It will return me both rooms, because the rrom 2 has a pool (it works like an OR, but I'd like it to work like and AND).
Edit 1:
If possible, I'd like to receive answers that changes the procedure, but not the tables, altough all answers are welcome!
Edit 2:
The code above is a simplification, this is the full code:
Object definition:
CREATE OR REPLACE TYPE c_valor FORCE IS OBJECT (CARATERISTICA VARCHAR2(20), VALOR NUMBER(30));
/
CREATE OR REPLACE TYPE tabc_valor FORCE IS TABLE OF c_valor;
/
Procedure:
CREATE OR REPLACE PROCEDURE getSalas_Carateristicas(v_carateristicas IN tabc_valor, lista OUT SYS_REFCURSOR)
IS BEGIN
OPEN lista FOR SELECT getSalas.ID_SALA
FROM getSalas, TABLE(v_carateristicas) v_carateristicas
WHERE getSalas.NOME_CARATERISTICA = v_carateristicas.CARATERISTICA
AND getSalas.VALOR >= v_carateristicas.VALOR
GROUP BY getSalas.ID_SALA
ORDER BY getSalas.ID_SALA;
END;
/
Procedure altered according to #XING's sugestion:
CREATE OR REPLACE PROCEDURE getSalas_Carateristicas(v_carateristicas IN tabc_valor, lista OUT SYS_REFCURSOR)
IS BEGIN
OPEN lista FOR SELECT getSalas.ID_SALA
FROM getSalas
INNER JOIN TABLE(v_carateristicas) v_carateristicas ON getSalas.NOME_CARATERISTICA = v_carateristicas.CARATERISTICA
AND getSalas.VALOR >= v_carateristicas.VALOR
GROUP BY getSalas.ID_SALA
ORDER BY getSalas.ID_SALA;
END;
/
I guess the issue is with the way you ask for a room. See below demo and explaination inline.
Proc:
CREATE OR REPLACE TYPE T541682.c_chars FORCE IS OBJECT (v_CHAR VARCHAR2(20), v_VALUE NUMBER(30));
/
CREATE OR REPLACE TYPE T541682.tab_chars FORCE IS TABLE OF c_chars;
/
CREATE OR REPLACE PROCEDURE T541682.thisRoomOK (
v_carateristicas IN tab_chars,
lista OUT SYS_REFCURSOR)
IS
BEGIN
OPEN lista FOR
SELECT MRoom.ID_ROOM
FROM Mroom
inner join TABLE (v_carateristicas) v_carateristicas
ON Mroom.CHAR_NAME = v_carateristicas.v_CHAR
AND Mroom.CHAR_VALUE = v_carateristicas.v_VALUE
GROUP BY Mroom.ID_ROOM
ORDER BY Mroom.ID_ROOM;
END;
/
declare
var tab_chars :=tab_chars();
type var1 is table of MRoom.ID_ROOM%type index by pls_integer;
var_out var1;
x sys_refcursor;
begin
var.extend(3);
-- This is the way i populate my object for which i want to match with the table. Am sure you are passing all rows same as your table hence its getting the all the rows.
var(1) := c_chars('air-conditioner',1);
var(2) := c_chars('pool',2);
-- Must be populating the third row as well. And as per your query both rows wil be picked up if you populate the object .
--var(3) := c_chars('pool',1);
-- Calling your procedure
thisRoomOK(v_carateristicas => var,lista => x);
fetch x bulk collect into var_out ;
--- displaying number of room
for i in 1..var_out.count
loop
dbms_output.put_line('Room Number - '||var_out(i));
end loop;
---passing the sys_refcursor result to another procedure
proc1(var_out);
end;
Output:
SQL> /
Room Number - 1
PL/SQL procedure successfully completed.
How do I create a stored procedure which can return multiple rows using SQL Developer BTW.?
Right now stored procedure returns the value for 1 row in 4 diff variables (there are 4 cols)
How I would go about making it so that it could return more than 1 row, for example if i were to query in my date it could return all the relevant data for that date instead of only 1.
create or replace PROCEDURE P2
(
ts IN TIMESTAMP,
u_id OUT VARCHAR2,
u_email OUT VARCHAR2,
cmnt OUT VARCHAR2
)
AS
BEGIN
SELECT U_ID , U_EML, C_TX INTO u_id, u_email, cmnt
FROM U_CM
WHERE U_CM_TS = ts;
END;
ts is the input timestamp
if i put in more a timestamp that has multiple rows associated with it i get an error?
How do i change the design so I can be successful in doing what i want? I am new to this so I dont know where to start
Use ref cursor:
create or replace PROCEDURE P2
(
ts IN TIMESTAMP,
p_result OUT sys_refcursor
)
AS
BEGIN
open p_result for
SELECT U_ID , U_EML, C_TX
FROM U_CM
WHERE U_CM_TS = ts;
END;
Also, try to give a more detailed names to columns and tables for more maintainable code. For example, user_id, user_email instead of u_id, u_eml. What is c_tx? I have no idea. Read about table and column naming conventions.
When calling a function via an inline select statement, when the function is returning a custom type, Oracle seems to execute the function equal to the number of arguments +1. This seems to happen when the select is included as a CTAS or an insert/select.
Has anyone seen this before? Is this an Oracle bug? I would expect the function to be called once per row in the table.
--Inline function gets called for the number of arguments +1
--drop table t
create table t(
id number,
l_blob blob
);
insert into t values(1, utl_raw.cast_to_raw('SampleString'));
COMMIT;
create table tmp_ts (c1 timestamp);
create or replace type test_type as object(
c1 varchar2(32)
,c2 varchar2(32)
);
/
create or replace FUNCTION test_function (p_blob blob, p_date date)
RETURN test_type
IS
BEGIN
--This could also be a DBMS_OUTPUT.PUT_LINE statement
insert into tmp_ts VALUES (systimestamp);
return test_type(null,null);
END test_function;
/
--0
select count(*) from tmp_ts;
--Call function on 1 row table - function should just insert 1 row into tmp_ts
create table tst_table as
select test_function(l_blob, '25-JAN-09') as c1
from t;
--it actually inserts 3
select count(*) from tmp_ts;
Example where increasing the argument call for the type increases the number of time the function is executed
--Same example with more arguements - 6 arguements here
--Inline function gets called for the number of arguments +1
--drop table t
create table t2(
id number,
l_blob blob
);
insert into t2 values(1, utl_raw.cast_to_raw('SampleString'));
COMMIT;
create table tmp_ts2 (c1 timestamp);
create or replace type test_type2 as object(
c1 varchar2(32)
,c2 varchar2(32)
,c3 varchar2(32)
,c4 varchar2(32)
,c5 varchar2(32)
,c6 varchar2(32)
);
/
create or replace FUNCTION test_function2 (p_blob blob, p_date date)
RETURN test_type2
IS
BEGIN
insert into tmp_ts2 VALUES (systimestamp);
return test_type2(null,null,null,null,null,null);
END test_function2;
/
--0
select count(*) from tmp_ts2;
--Call function on 1 row table - function should just insert 1 row into tmp_ts
create table tst_table2 as
select test_function2(l_blob, '25-JAN-09') as c1
from t;
--it actually inserts 7
select count(*) from tmp_ts2;
Any help/feedback is greatly appreciated.
First: It is a bug that you can even perform a DML inside a function which is called in a SELECT Statement. This should raise an exception.
Otherwise Oracle makes absolutely no guarantee how often Functions in a SQL-Select are executed, it could be once per row, ten times per row or just once for the whole query (with caching) - so however often it is called, this conforms to the specifications.
In this special case it will call the function for each attribute of the returning type, since oracle will not insert the object type as one memory-structure, but use the function like a table with multiple columns and read each column individually like this:
INSERT VALUES ( myFunc(x).attribute1, myFunc(x).attribute2 );
The important part: Never make any assumptions about how often a FUNCTION is called when you use it in an SQL Statement!!! At any time the function could be called again by the optimizer, maybe for sampling or caching...
Preferred solution: Pipelined Functions - a pipelined Function can be called like a Table and will only be called once. You can pass in a cursor which the function uses for input processing and do the whole data-transformation and logging and everything in the function.
I have the following columns: user_address, user_city, user_state, and user_zip.
I have created a new column that has a custom object datatype:
CREATE TYPE ADDRESS_ADT AS OBJECT (
address VARCHAR2(255),
city VARCHAR2(20),
state CHAR(2),
zip VARCHAR2(20)
);
ALTER TABLE
USERS
ADD (
ADDRESS ADDRESS_ADT
);
I want to write a PL/SQL Unnamed procedure to migrate addresses to this new single column but am not sure how to approach it. Any suggestions?
Create a cursor, loop through the table
Read into the cursor
Build your ADDRESS_ADT object
Update the table
Something like this:
declare
cursor users_cur is
select rowid, street_address as address, city, state, zip
from users;
type users_aat is table of users_cur%ROWTYPE index by pls_integer;
l_users users_aat;
l_address_adt address_adt;
begin
open users_cur;
loop
fetch users_cur bulk collect into l_users limit 100;
for i in 1..l_users.count
loop
l_address_adt := address_adt(
l_users(i).address,
l_users(i).city,
l_users(i).state,
l_users(i).zip
);
update users set address = l_address_adt
where rowid = l_users(i).rowid;
end loop;
exit when l_users.count < 100;
end loop;
commit;
close users_cur;
end;
/