Return multiple rows from a stored procedure - sql

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.

Related

Oracle SQL user defined function

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

Oracle - Derived table, Inserting values into a new table

I have a function that will read through a list of comma-separated values within a field. The result of this function will create a derived table.
Example:
derived Table:
Now, I need to get this data inserted into a new/existing table. I created and object type/package that has all the columns from the derived table, however, I'm not sure how to add/reference this data to be able to insert into the new/existing table.
Functions that return a nested table can be converted into regular rows and columns by selecting from the table function using the TABLE function. (And in recent versions of Oracle, the TABLE operator is optional.)
For example:
SQL> create or replace function get_columns return sys.odcivarchar2list is
2 begin
3 return sys.odcivarchar2list('355352', 'Yes');
4 end;
5 /
Function created.
SQL> select column_value from table(get_columns);
COLUMN_VALUE
--------------------------------------------------------------------------------
355352
Yes
Now the function can be used anywhere, like an insert: insert into some_table select column_value from table(get_columns);
Your specific solution may be more complicated if the results contain multiple columns, or you have multiple result sets, etc. Edit your question with more details about the function and types if you still need help.
Edit: Below is a full example of storing CSV values in a column, creating a function to convert CSV to a VARRAY, calling that function to pivot and then pivot again back into multiple columns:
create table table_1
(
request_id number,
service varchar2(100),
method varchar2(100),
request_date date,
process_date date,
te_suid number,
status varchar2(100),
params varchar2(4000)
);
create or replace type params_varray is varray(48) of varchar2(4000);
--Example function that converts comma-separated lists into VARRAYS.
--(Not tested for nulls, consecutive commas, etc.)
create or replace function get_columns(p_params varchar2, p_delimiter varchar2) return params_varray is
v_array params_varray := params_varray();
begin
for i in 1 .. regexp_count(p_params, p_delimiter) + 1 loop
v_array.extend;
v_array(v_array.count) := regexp_substr(p_params, '[^'||p_delimiter||']+', 1, i);
end loop;
return v_array;
end;
/
insert into table_1 values(1,1,1,sysdate,sysdate,1,1,'1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48');
insert into table_1 values(2,2,2,sysdate,sysdate,2,2,'49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96');
--Convert CSV into multiple columns.
--
--Convert rows back into multiple columns.
select request_id, service, method, request_date, process_date, te_suid, status
,max(case when param_index = 1 then param_value else null end) value1
,max(case when param_index = 2 then param_value else null end) value2
,max(case when param_index = 3 then param_value else null end) value3
--Repeat for 4 through 48.
from
(
--Convert CSV to rows.
select request_id, service, method, request_date, process_date, te_suid, status,
row_number() over (partition by table_1.rowid order by rownum) param_index,
column_value param_value
from table_1
cross join get_columns(table_1.params, ',')
)
group by request_id, service, method, request_date, process_date, te_suid, status;
You can run the example on db<>fiddle.
But if you have any choice in the matter, I strongly suggest you denormalize your table and never store delimited values in a database.

How to create a function to add new record into a table? ORACLE

I am not sure if using the create or replace function is the right way to do this but i am trying to figure out how to ONLY add record to the table when the count of employee ID where month of read date = month of sysdate is not more than 10.
I have a employee_read table using primary key, reading_ID, foreign key emp_id and an attribute of read_date.
The information i found online shows return value. but how do i add into table instead of return? is it do-able?
Thank you for your help!
CREATE OR REPLACE FUNCTION AddNewReadRecord
(varEmpID IN NUMBER. varReadDate IN DATE, varWaterMeterID IN CHAR,
varCuReading IN NUMBER, varPrevReading IN Number)
RETURN
BEGIN
END
You can obviously add DML in the function but using PRAGMA AUTONOMOUS_TRANSACTION as follows:
CREATE OR REPLACE FUNCTION AddNewReadRecord
(varEmpID IN NUMBER. varReadDate IN DATE, varWaterMeterID IN CHAR, varCuReading IN NUMBER, varPrevReading IN Number)
RETURN number IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
Insert into your_table (...) -- column list
Select ... -- variable list
From your_table
Where emp_id = varEmpID
And read_date = varReadDate
Group by emp_id
Having count(1) < 10;
Commit;
Return 1; -- add logic for return using exception
END;
/

Oracle procedure, placement of multiple records in variables

I'm trying to create my first oracle procedure. The select will return multiple records; I need to be able to place each record in the variables and use the record in later actions in the procedure. Any help please?
key number;
keyCount number;
rub varchar2(50);
srub varchar2(100);
type varchar2(200);
date varchar2(14);
note varchar2(500);
BEGIN
SELECT KEY,COUNT(KEY),RUB,
SRUB,TYPE ,DATE,NOTE FROM Student
WHERE S_KEY = {key};
END;
In PL/SQL we need to select results into matching variables. One way is separate variables for each column (as shown). The alternative is to use a row variable which matches the project of the query; find out more.
You've got an aggregating function - COUNT() so you need a GROUP BY clause which defines the non-aggregating columns. You say you have more than one record so you need to populate a collection not scalar variables. Find out more.
Your procedure should look something like this
create or replace procedure my_first_proc
( p_key in student.s_key%type )
as
type my_rec is record (
key number ,
keyCount number ,
rub varchar2(50); ,
srub varchar2(100) ,
type varchar2(200) ,
date varchar2(14),
note varchar2(500)
);
type my_rec_coll is table of my_rec;
l_student_recs my_rec_coll;
BEGIN
SELECT KEY,COUNT(KEY),RUB,SRUB,TYPE ,DATE,NOTE
bulk collect into l_student_recs
FROM Student
WHERE S_KEY = p_key
group by KEY,RUB,SRUB,TYPE ,DATE,NOTE
;
for idx in l_student_recs.first() .. l_student_recs.last()
loop
-- do some processing here
dbms_output.put_line('RUB = '||l_student_recs(idx).rub);
end loop;
EXCEPTION
when no_data_found then
raise_application_error(-01403, 'no student records for key='||p_key);
END;
Get into good habits:
use sensible variable names
distinguish parameter names from local variables
handle predictable exceptions

How to exceute a query stored in dictionary table into a CURSOR?

I’ve a table named QUERIES_DICTIONNARY with 2 columns (ID, SQL_QUERY) the first column is NUMBER and the second column is a Varchar represent an SQL query.
CREATE TABLE "QUERIES_DICTIONNARY"
(
"ID" NUMBER(10,0) NOT NULL ENABLE,
"SQL_QUERY" NVARCHAR2(2000) NOT NULL ENABLE
)
I want to create a stored procedure with 2 parameters (P_KEY, P_RESULTS_CURSOR) where the P_KEY is the ID of my table, and the P_RESULT_CURSOR where i execute my SQL query of the record having ID = P_KEY.
CREATE OR REPLACE PROCEDURE GET_STATISTICS_RESULTS
(
P_KEY IN NUMBER,
P_RESULTS_CURSOR OUT SYS_REFCURSOR
)
In my stored procedure I get the record where the ID = P_KEY
Once I’ve the record I want to execute the the associated query using EXECUTE IMMEDIATE into the P_RESULTS_CURSOR.
CREATE OR REPLACE PROCEDURE GET_STATISTICS_RESULTS
(
P_KEY IN NUMBER
P_RESULTS_CURSOR OUT SYS_REFCURSOR
) AS
BEGIN
---- Get the record
-- SELECT SQL_QUERY FROM QUERIES_DICTIONNARY WHERE ID = P_KEY;
---- Then Execute the query
-- OPEN P_RESULTS_CURSOR FOR ?
END GET_STATISTICS_RESULTS;
How can I do this approach in one Stored procedure please?
There is two ways: simple and not so simple. Simple way is:
CREATE OR REPLACE PROCEDURE GET_STATISTICS_RESULTS
(
P_KEY IN NUMBER;
P_RESULTS_CURSOR OUT SYS_REFCURSOR;
) AS
cursor_text QUERIES_DICTIONNARY.SQL_QUERY%TYPE;
BEGIN
---- Get the record
SELECT SQL_QUERY
INTO cursor_text
FROM QUERIES_DICTIONNARY WHERE ID = P_KEY;
---- Then Execute the query
OPEN P_RESULTS_CURSOR FOR cursor_text;
END GET_STATISTICS_RESULTS;
Not so simple way - using DBMS_SQL package. You can see example in documentation. It will be quite complicated decision.
CREATE OR REPLACE PROCEDURE GET_STATISTICS_RESULTS
(
P_KEY IN NUMBER
P_RESULTS_CURSOR OUT SYS_REFCURSOR
) AS
l_s "QUERIES_DICTIONNARY"."SQL_QUERY"%TYPE;
BEGIN
---- Get the record
SELECT SQL_QUERY into l_s FROM QUERIES_DICTIONNARY WHERE ID = P_KEY;
---- Then Execute the query
OPEN P_RESULTS_CURSOR FOR l_s;
END GET_STATISTICS_RESULTS;
PLS-00382 appears because of NVARCHAR2 column. You may try to cast nvarchar2 to varchar2:
l_s VARCHAR2(4000);
...
SELECT CAST(SQL_QUERY AS VARCHAR2(4000)) into l_s FROM QUERIES_DICTIONNARY WHERE ID = P_KEY;
...
But you may lose some data.