Cursor for loop in procedure with called function in Oracle - oracle-sqldeveloper

The table is:
F TIME1 END_TIME
- --------------------------------------------------------------------------- ----------------------
C 16-NOV-16 09.45.32.000000 AM 17-NOV-16 09.45.32.000000 AM
A 16-NOV-16 10.14.54.000000 AM 16-NOV-16 11.14.54.000000 AM
A 16-NOV-16 10.14.56.000000 AM 16-NOV-16 11.14.56.000000 AM
I have created a function..
CREATE OR REPLACE FUNCTION datediff
(
time1 TIMESTAMP
, time2 TIMESTAMP
)
RETURN number
as
tot number;
BEGIN
SELECT(extract(DAY FROM time2-time1)*24*60*60)+
(extract(HOUR FROM time2-time1)*60*60)
into tot from tt ;
RETURN tot;
END;
I am then calling the function in procedure...
CREATE OR REPLACE PROCEDURE P1
IS
CURSOR c1
IS
select count(*) as cnt,time1,end_time
from tt group by time1,end_time ;
a number;
BEGIN
FOR i IN c1
LOOP
declare
a number;
BEGIN
insert into y1 values(i.cnt,datediff(i.time1,i.end_time)) ;
--display(i.cnt||' '||a);
/* EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('Error updating record ' || SUBSTR (SQLERRM, 1, 250));*/
END;
END LOOP;
END P1;
The error I am getting is ...
SQL> exec p1
BEGIN p1; END;
*
ERROR at line 1:
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "ANU.DATEDIFF", line 11
ORA-06512: at "ANU.P1", line 14
ORA-06512: at line 1
This is working for single record in the table ,but not for multiple records..? please guide..

The problem is in your datediff function.
Whenever you have a SELECT ... INTO ... statement in PL/SQL, the SELECT query must return a single row. If it returns no rows at all, you get an ORA-01403 no data found error, and if it returns more than one row, you get the ORA-01422 exact fetch returns more than requested number of rows error you see above.
So, how can you get your query to return one row? Well, you can start by noticing that you aren't selecting any values from of the table tt. All your query is returning is the same value for each row in tt. If tt has one row, your query returns the value once. If tt has three rows, your query returns the same value three times. All this is unnecessary as you only want the one value, regardless of how many rows there are in tt.
So instead of SELECTing from tt, use the built-in table dual instead. The dual table only ever has one row in it:
SELECT(extract(DAY FROM time2-time1)*24*60*60)+
(extract(HOUR FROM time2-time1)*60*60)
into tot from dual;
However, in your case you don't even need a query: your datediff function could be rewritten to just perform the calculation and return the value:
CREATE OR REPLACE FUNCTION datediff
(
time1 TIMESTAMP
, time2 TIMESTAMP
)
RETURN number
as
BEGIN
RETURN (extract(DAY FROM time2-time1)*24*60*60)+
(extract(HOUR FROM time2-time1)*60*60);
END;
/

Related

Returning the nth largest value in postgresql

I want to return the nth largest salary in the table using for-loops, and I am still having trouble with the syntax. Please help.
create function get_nth_max(n integer)
returns real
as
$$
declare
nth_max real = max(salary) from company;
pay real;
begin
for pay in select salary from company
order by salary = desc limit n
loop
if pay.salary < nth_max then
nth_max = pay.salary and
end loop;
return nth_max
end;
$$
language plpgsql;
Error message:
ERROR: syntax error at or near "desc"
LINE 10: order by salary = desc limit n loop
^
SQL state: 42601
Character: 182
First you need to define "the nth largest salary in the table" clearly.
What about duplicates? If two people earn 1000 and one earns 800, is 800 then the 2nd or 3rd highest salary? Can salary be NULL? If so, ignore NULL values? What if there are not enough rows?
Assuming ...
Duplicate entries only count once - so 800 is considered 2nd in my example.
salary can be NULL. Ignore NULL values.
Return NULL or nothing (no row) if there are not enough rows.
Proper solution
SELECT salary
FROM (
SELECT salary, dense_rank() OVER (ORDER BY salary DESC NULLS LAST) AS drnk
FROM company
) sub
WHERE drnk = 8;
That's basically the same as Tim's answer, but NULLS LAST prevents NULL from coming first in descending order. Only needed if salary can be NULL, obviously, but never wrong. Best supported with an index using the same sort order DESC NULLS LAST. See:
Sort by column ASC, but NULL values first?
FOR loop as proof of concept
If you insist on using a FOR loop for training purposes, this would be the minimal correct form:
CREATE OR REPLACE FUNCTION get_nth_max(n integer, OUT nth_max numeric)
LANGUAGE plpgsql AS
$func$
BEGIN
FOR nth_max IN
SELECT DISTINCT salary
FROM company
ORDER BY salary DESC NULLS LAST
LIMIT n
LOOP
-- do nothing
END LOOP;
END
$func$;
Working with numeric instead of real, because we don't want to use a floating-point number for monetary values.
Your version does a lot of unnecessary work. You don't need the overall max, you don't need to check anything in the loop, just run through it, the last assignment will be the result. Still inefficient, but not as much.
Notably, SELECT get_nth_max(10) returns NULL if there are only 9 rows, while the above SQL query returns no row. A subtle difference that may be relevant. (You could devise a function with RETURNS SETOF numeric to return no row for no result ...)
Using an OUT parameter to shorten the syntax. See:
Returning from a function with OUT parameter
You don't need a UDF for this, just use DENSE_RANK:
WITH cte AS (
SELECT *, DENSE_RANK() OVER (ORDER BY salary DESC) drnk
FROM company
)
SELECT salary
FROM cte
WHERE drnk = <value of n here>
So I have missing semicolons, did not end my if-statement, and a 'dangling' AND. The following code is the working version:
create or replace function get_nth_max(n integer)
returns real
as
$$
declare
nth_max real = max(salary) from company;
pay record;
begin
for pay in select salary from company
order by salary desc limit n
loop
if pay.salary < nth_max then
nth_max = pay.salary;
end if;
end loop;
return nth_max;
end;
$$
language plpgsql;

Recursive with cursor on psql, nothing data found

How to use a recursive query and then using cursor to update multiple rows in postgresql. I try to return data but no data is found. Any alternative to using recursive query and cursor, or maybe better code please help me.
drop function proses_stock_invoice(varchar, varchar, character varying);
create or replace function proses_stock_invoice
(p_medical_cd varchar,p_post_cd varchar, p_pstruserid character varying)
returns void
language plpgsql
as $function$
declare
cursor_data refcursor;
cursor_proses refcursor;
v_medicalCd varchar(20);
v_itemCd varchar(20);
v_quantity numeric(10);
begin
open cursor_data for
with recursive hasil(idnya, level, pasien_cd, id_root) as (
select medical_cd, 1, pasien_cd, medical_root_cd
from trx_medical
where medical_cd = p_pstruserid
union all
select A.medical_cd, level + 1, A.pasien_cd, A.medical_root_cd
from trx_medical A, hasil B
where A.medical_root_cd = B.idnya
)
select idnya from hasil where level >=1;
fetch next from cursor_data into v_medicalCd;
return v_medicalCd;
while (found)
loop
open cursor_proses for
select B.item_cd, B.quantity from trx_medical_resep A
join trx_resep_data B on A.medical_resep_seqno = B.medical_resep_seqno
where A.medical_cd = v_medicalCd and B.resep_tp = 'RESEP_TP_1';
fetch next from cursor_proses into v_itemCd, v_quantity;
while (found)
loop
update inv_pos_item
set quantity = quantity - v_quantity, modi_id = p_pstruserid, modi_id = now()
where item_cd = v_itemCd and pos_cd = p_post_cd;
end loop;
close cursor_proses;
end loop;
close cursor_data;
end
$function$;
but nothing data found?
You have a function with return void so it will never return any data to you. Still you have the statement return v_medicalCd after fetching the first record from the first cursor, so the function will return from that point and never reach the lines below.
When analyzing your function you have (1) a cursor that yields a number of idnya values from table trx_medical, which is input for (2) a cursor that yields a number of v_itemCd, v_quantity from tables trx_medical_resep, trx_resep_data for each idnya, which is then used to (3) update some rows in table inv_pos_item. You do not need cursors to do that and it is, in fact, extremely inefficient. Instead, turn the whole thing into a single update statement.
I am assuming here that you want to update an inventory of medicines by subtracting the medicines prescribed to patients from the stock in the inventory. This means that you will have to sum up prescribed amounts by type of medicine. That should look like this (note the comments):
CREATE FUNCTION proses_stock_invoice
-- VVV parameter not used
(p_medical_cd varchar, p_post_cd varchar, p_pstruserid varchar)
RETURNS void AS $function$
UPDATE inv_pos_item -- VVV column repeated VVV
SET quantity = quantity - prescribed.quantity, modi_id = p_pstruserid, modi_id = now()
FROM (
WITH RECURSIVE hasil(idnya, level, pasien_cd, id_root) AS (
SELECT medical_cd, 1, pasien_cd, medical_root_cd
FROM trx_medical
WHERE medical_cd = p_pstruserid
UNION ALL
SELECT A.medical_cd, level + 1, A.pasien_cd, A.medical_root_cd
FROM trx_medical A, hasil B
WHERE A.medical_root_cd = B.idnya
)
SELECT B.item_cd, sum(B.quantity) AS quantity
FROM trx_medical_resep A
JOIN trx_resep_data B USING (medical_resep_seqno)
JOIN hasil ON A.medical_cd = hasil.idnya
WHERE B.resep_tp = 'RESEP_TP_1'
--AND hacil.level >= 1 Useless because level is always >= 1
GROUP BY 1
) prescribed
WHERE item_cd = prescribed.item_cd
AND pos_cd = p_post_cd;
$function$ LANGUAGE sql STRICT;
Important
As with all UPDATE statements, test this code before you run the function. You can do that by running the prescribed sub-query separately as a stand-alone query to ensure that it does the right thing.

Unexpected difference between two pieces of sql

I have two pieces of sql which I think should give identical results and do not. They both involve a function
create or replace function viewFromList( lid integer, offs integer, lim integer ) returns setof resultsView as $xxx$
BEGIN
return query select resultsView.* from resultsView, list, files
where list_id=lid and
resultsView.basename = files.basename and
idx(imgCmnt,file_id) > 0
order by idx(imgCmnt,file_id)
limit lim offset offs ;
return;
END;
$xxx$
The first is:
drop table if exists t1;
create temp table t1 as select * from viewFromList( lid, frst, nmbr::integer );
select count(*) into rv.height from t1;
The second is
DECLARE
t1 resultsView;
....
select viewFromList( lid, frst, nmbr::integer ) into t1;
select count(*) into rv.height from t1;
Seems to me, rv.height should get the same value in both cases. It doesn't. If it matters, the correct answer in my case is 7, the second code produces 12. I have, of course, looked at the result of the call to viewFromList with the appropriate values. When run in psql, it returns the expected 7 rows.
Can someone tell me what's going on?
Thanks.

Calculate daily sums in PostgreSQL

I am fairly new in postgres and what am trying to do is calculate sum values for each day for every month (i.e daily sum values). Based on scattering information I came up with something like this:
CREATE OR REPLACE FUNCTION sumvalues() RETURNS double precision AS
$BODY$
BEGIN
FOR i IN 0..31 LOOP
SELECT SUM("Energy")
FROM "public"."EnergyWh" e
WHERE e."DateTime" = day('01-01-2005 00:00:00'+ INTERVAL 'i' DAY);
END LOOP;
END
$BODY$
LANGUAGE plpgsql VOLATILE NOT LEAKPROOF;
ALTER FUNCTION public.sumvalues()
OWNER TO postgres;
The query returned successfully, so I thought I had made it. However when am trying to insert the values of the function to a table (which maybe wrong):
INSERT INTO "SumValues"
("EnergyDC")
(
SELECT sumvalues()
);
I get this:
ERROR: invalid input syntax for type interval: "01-01-2005 00:00:00"
LINE 3: WHERE e."DateTime" = day('01-01-2005 00:00:00'+ INTERVAL...
I tried to debug it myself but yet am not sure, which of the two I am doing wrong (or both) and why.
Here is an example of EnergyWh
(am using systemid and datetime as composite PK, but that should not matter)
see GROUP BY clause http://www.postgresql.org/docs/9.2/static/tutorial-agg.html
SELECT EXTRACT(day FROM e."DateTime"), EXTRACT(month FROM e."DateTime"),
EXTRACT(year FROM e."DateTime"), sum("Energy")
FROM "public"."EnergyWh" e
GROUP BY 1,2,3
but following query should to work too:
SELECT e."DateTime"::date, sum("Energy")
FROM "public"."EnergyWh" e
GROUP BY 1
I am using a short syntax for GROUP BY ~ GROUP BY 1 .. group by first column.
Here is simple Example that can help you:
Table :
create table demo (value double precision);
Function
CREATE OR REPLACE FUNCTION sumvalues() RETURNS void AS
$BODY$
DECLARE
inte text;
BEGIN
FOR i IN 0..31 LOOP
inte := 'INSERT INTO demo SELECT EXTRACT (DAY FROM TIMESTAMP ''01-01-2005 00:00:00''+ INTERVAL '''||i||' Days'')';
EXECUTE inte;
END LOOP;
END
$BODY$
LANGUAGE plpgsql VOLATILE NOT LEAKPROOF;
ALTER FUNCTION public.sumvalues()
OWNER TO postgres;
Function Call
SELECT sumvalues();
Output
SELECT * FROM demo;
Here if you want to use some variable value into SQL query than you must have to use some DYNAMIC QUERY for that.
Reference : Dynamic query in pgsql

Stored function with temporary table in postgresql

Im new to writing stored functions in postgresql and in general . I'm trying to write onw with an input parameter and return a set of results stored in a temporary table.
I do the following in my function .
1) Get a list of all the consumers and store their id's stored in a temp table.
2) Iterate over a particular table and retrieve values corresponding to each value from the above list and store in a temp table.
3)Return the temp table.
Here's the function that I've tried to write by myself ,
create or replace function getPumps(status varchar) returns setof record as $$ (setof record?)
DECLARE
cons_id integer[];
i integer;
temp table tmp_table;--Point B
BEGIN
select consumer_id into cons_id from db_consumer_pump_details;
FOR i in select * from cons_id LOOP
select objectid,pump_id,pump_serial_id,repdate,pumpmake,db_consumer_pump_details.status,db_consumer.consumer_name,db_consumer.wenexa_id,db_consumer.rr_no into tmp_table from db_consumer_pump_details inner join db_consumer on db_consumer.consumer_id=db_consumer_pump_details.consumer_id
where db_consumer_pump_details.consumer_id=i and db_consumer_pump_details.status=$1--Point A
order by db_consumer_pump_details.consumer_id,pump_id,createddate desc limit 2
END LOOP;
return tmp_table
END;
$$
LANGUAGE plpgsql;
However Im not sure about my approach and whether im right at the points A and B as I've marked in the code above.And getting a load of errors while trying to create the temporary table.
EDIT: got the function to work ,but I get the following error when I try to run the function.
ERROR: array value must start with "{" or dimension information
Here's my revised function.
create temp table tmp_table(objectid integer,pump_id integer,pump_serial_id varchar(50),repdate timestamp with time zone,pumpmake varchar(50),status varchar(2),consumer_name varchar(50),wenexa_id varchar(50),rr_no varchar(25));
select consumer_id into cons_id from db_consumer_pump_details;
FOR i in select * from cons_id LOOP
insert into tmp_table
select objectid,pump_id,pump_serial_id,repdate,pumpmake,db_consumer_pump_details.status,db_consumer.consumer_name,db_consumer.wenexa_id,db_consumer.rr_no from db_consumer_pump_details inner join db_consumer on db_consumer.consumer_id=db_consumer_pump_details.consumer_id where db_consumer_pump_details.consumer_id=i and db_consumer_pump_details.status=$1
order by db_consumer_pump_details.consumer_id,pump_id,createddate desc limit 2;
END LOOP;
return query (select * from tmp_table);
drop table tmp_table;
END;
$$
LANGUAGE plpgsql;
AFAIK one can't declare tables as variables in postgres. What you can do is create one in your funcion body and use it thourough (or even outside of function). Beware though as temporary tables aren't dropped until the end of the session or commit.
The way to go is to use RETURN NEXT or RETURN QUERY
As for the function result type I always found RETURNS TABLE to be more readable.
edit:
Your cons_id array is innecessary, just iterate the values returned by select.
Also you can have multiple return query statements in a single function to append result of the query to the result returned by function.
In your case:
CREATE OR REPLACE FUNCTION getPumps(status varchar)
RETURNS TABLE (objectid INTEGER,pump_id INTEGER,pump_serial_id INTEGER....)
AS
$$
BEGIN
FOR i in SELECT consumer_id FROM db_consumer_pump_details LOOP
RETURN QUERY(
SELECT objectid,pump_id,pump_serial_id,repdate,pumpmake,db_consumer_pump_details.status,db_consumer.consumer_name,db_consumer.wenexa_id,db_consumer.rr_no FROM db_consumer_pump_details INNER JOIN db_consumer ON db_consumer.consumer_id=db_consumer_pump_details.consumer_id
WHERE db_consumer_pump_details.consumer_id=i AND db_consumer_pump_details.status=$1
ORDER BY db_consumer_pump_details.consumer_id,pump_id,createddate DESC LIMIT 2
);
END LOOP;
END;
$$
edit2:
You probably want to take a look at this solution for groupwise-k-maximum problem as that's exactly what you're dealing with here.
it might be easier to just return a table (or query)
CREATE FUNCTION extended_sales(p_itemno int)
RETURNS TABLE(quantity int, total numeric) AS $$
BEGIN
RETURN QUERY SELECT quantity, quantity * price FROM sales
WHERE itemno = p_itemno;
END;
$$ LANGUAGE plpgsql;
(copied from postgresql docs)