Get resultset from a declare block - oracle

I've managed to incorporate variables into my pl/sql script and I get the correct result but I cannot figure out how to get it back in the same format as if I executed a plain select. I only see examples that use dbms_output.put_line which outputs text. How do I get an ordinary table out of this:
declare
monday date;
sunday date;
c sys_refcursor;
type t is table of MY_TABLE%rowtype;
r t;
begin
monday := (sysdate - 7 - to_char(sysdate, 'd') + 2);
sunday := (sysdate - 7 + to_char(sysdate, 'd'));
DBMS_OUTPUT.PUT_LINE(monday);
DBMS_OUTPUT.PUT_LINE(sunday);
select
*
bulk collect into
r
from
MY_TABLE
where
"Created On" >= monday and
"Created On" <= sunday
fetch next 10 rows only;
DBMS_OUTPUT.PUT_LINE(r.COUNT); -- <-- = 10
-- this might work but doesn't yet...
open c for select * from r;
DBMS_SQL.RETURN_RESULT(c);
close c;
end;
I found a way that it might be possible to use a cursor with DBMS_SQL.RETURN_RESULT but I still have issues with the r, it says ORA-00942 that the table name is invalid.

Related

Oracle SQL to display results from dividing one variable with another

I need to calculate the YTD weekly average for the GROSS_AMOUNT
In the below script, the first part of the code gets the number of weeks. The second part of the code gets the YTD sum for that same time period
How do I write the SQL so I can retrieve the actual value of YtdTotal/TotalWeeks?
SELECT YtdTotal/TotalWeeks FROM DUAL; does not work
-- STORES NUMBER OF WEEKS SINCE BEGINING OF YEAR INTO THE VARIABLE, TotalWeeks
DECLARE
TotalWeeks NUMBER;
BEGIN
SELECT to_number(to_char(sysdate, 'WW')) - to_number(to_char(trunc(sysdate, 'year'),'WW'))
INTO TotalWeeks
FROM DUAL
-- (retrieves number of weeks since beginning of year)
END
-- STORES THE SUM OF GROSS)AMOUNT FOR THE WEEKS CALCULATED ABOVE - INTO THE
DECLARE
VARIABLE, YtdTotal
BEGIN
SELECT SUM(GROSS_AMOUNT)
INTO YtdTotal
FROM PARENTS
WHERE process_date BETWEEN
(next_day(TRUNC(sysdate, 'year'),'SUN'))
AND
(next_day(TRUNC(sysdate),'SAT')-7);
END;
Don't use two separate anonymous PL/SQL blocks - combine them into one!
If you want to return the result, then - instead of an anonymous PL/SQL block - create a function.
CREATE OR REPLACE FUNCTION f_test
return NUMBER
IS
totalweeks NUMBER;
ytdtotal NUMBER;
result NUMBER;
BEGIN
-- STORES NUMBER OF WEEKS SINCE BEGINING OF YEAR INTO THE VARIABLE, TotalWeeks
SELECT TO_NUMBER (TO_CHAR (SYSDATE, 'WW'))
- TO_NUMBER (TO_CHAR (TRUNC (SYSDATE, 'year'), 'WW'))
INTO totalweeks
FROM DUAL;
-- (retrieves number of weeks since beginning of year)
-- STORES THE SUM OF GROSS)AMOUNT FOR THE WEEKS CALCULATED ABOVE - INTO THE
SELECT SUM (gross_amount)
INTO ytdtotal
FROM parents
WHERE process_date BETWEEN (NEXT_DAY (TRUNC (SYSDATE, 'year'), 'SUN'))
AND (NEXT_DAY (TRUNC (SYSDATE), 'SAT') - 7);
-- the final result
result := ytdtogal / totalweeks;
RETURN result;
END;
/
Use it as
select f_test from dual;

Looping in Oracle

I need to loop in Oracle on monthly basis for 3 years. I am trying for daily basis first and below is my query but not sure about the error.
can you please help with this. I am fairly new to oracle.
DECLARE #StartDT DATE;
SET #StartDT = '20090101'
WHILE #StartDT <= '20090131'
BEGIN
PRINT CONVERT(VARCHAR,#StartDT) + '---' + convert(varchar,DATEADD(DAY,1,#StartDT))
END
What you posted is completely SQL Server syntax. In Oracle, you'd probably do this in SQL
select date '2009-01-01' + level
from dual
connect by level <= 31
If you want to use PL/SQL (and assuming you are using a tool that will display the output from dbms_output which you shouldn't assume will happen in prod), you could do something like this.
declare
l_dt date := date '2009-01-01';
begin
while( l_dt <= date '2009-01-31' )
loop
dbms_output.put_line( to_char( l_dt, 'yyyy-mm-dd' ) );
l_dt := l_dt + 1;
end loop;
end;
In oracle you can use for loop as following:
Begin
For d in (select date '2009-01-01' + level - 1 as dt
from dual
Connect by level <= add_months(date '2009-01-01', 3) - date '2009-01-01')
Loop
dbms_output.put_line(to_char(d.dt, 'yyyy-mm-dd'));
End loop;
End;
/
Cheers!!

function/procedure to calculate total based on total for each month in plqsl

I need advice on how to write a stored procedure or function to calculate total based on total of each month. Lets say:
target income for Jan = 20K, then for a month: 20K * 31 (total days of January)
target income for Feb = 19K, then for a month 19K * 28 (total days of February)
I need to get total up to the given date. For instance:
date_param = 25 February 2018. Then grand total = (20K * 31) + (19K * 25)
How to write this in pl/sql?
Thank you.
Your question remains rather light on specifics, so here are my assumptions.
The daily targets are stored in a table with a structure of
(month_no number, daily_tgt number).
The targets are the same for each year.
The calculation starts from the first month of the current year.
There is no need to handle leap years.
The presented question rules out the need to handle non-working days.
This solution is a function to return the calculated total.
create or replace function get_target_sum
(p_cutoff in date)
return number
as
rv number;
begin
select sum(daily_tgt * no_of_days)
into rv
from (
select daily_tgt
, case when month_no < to_number(to_char(p_cutoff, 'MM'))
then to_number(to_char(last_day(to_date(month_no,'MM')), 'DD'))
else to_number(to_char(p_cutoff, 'DD'))
end as no_of_days
from targets
where month_no <= to_number(to_char(p_cutoff, 'MM'))
);
return rv;
end;
/
Notes
Storing the daily targets by numeric month make it easier to filter the months before the cut-off date in the WHERE clause.
Casting the month number to a date allows us to derive the last day of the month from which we get the number of days to multiply for whole months.
For the final month we just need the day from the cut-off date.
Your question is not very clear as in stating how to get the Monthly rates. I assumed few things and providing my solution as below:
1) Created a table mnthly_tgt which would store monthly rates.
2) The function will take an input date and then return the sum from past months of the year.
Table Definition:
Create table mnthly_tgt(mnth number, tgt_amt number);
insert into mnthly_tgt values(1 , 15);
insert into mnthly_tgt values(2, 20);
insert into mnthly_tgt values(3 , 12);
insert into mnthly_tgt values(4 , 10);
insert into mnthly_tgt values(5 , 11);
insert into mnthly_tgt values(6, 15);
insert into mnthly_tgt values(7, 20);
Function:
Create or replace function Ret_Tot(ipt_dt date)
return number
is
v_curr_mnth number;
v_curr_days number;
v_tot_amt number;
v_sql varchar2(400);
v_date date;
v_num_of_days number;
cntr number:=0;
begin
-- Calculating total for current month.
Select to_char(ipt_dt,'MM') col1 -- Current Month
,to_char(ipt_dt,'dd') col3 -- Days in Current Month
INTO
v_curr_mnth ,
v_curr_days
from DUAL;
-- Current Month Total
v_tot_amt: = v_curr_days * (Select tgt_amt from mnthly_tgt where mnth = v_curr_mnth);
--Calculation for previous months
For i in (Select to_char(ipt_dt,'MM') - LEVEL col1
from Dual
CONNECT BY LEVEL < to_char(col,'MM')
Loop
cntr := cntr + 1;
v_sql:='Select ipt_dt - INTERVAL '|| cntr ||' MONTH from DUAL' ;
Execute Immediate v_sql INTO V_DATE;
--calculating no. of days of previous months
v_sql:='SELECT EXTRACT(DAY FROM LAST_DAY('|| V_DATE ||')) FROM dual';
Execute Immediate v_sql INTO v_num_of_days ;
--Summing up total
v_tot_amt: = v_tot_amt + ( v_num_of_days * (Select tgt_amt from mnthly_tgt where mnth = i.col1));
END LOOP;
return v_tot_amt;
end;
PS: Nt tested.

Increment Multiple Timestamp Values with PL/SQL

I don't know Oracle at all, but I need to write something like this:
MySQL:
SET #serial:=1;
UPDATE table1 SET t = t + INTERVAL (#serial:=#serial+1) SECOND;`
Update and increment a timestamp by one second for all records. How to do this in Oracle?
Question Update:
My wording was not explaining my problem well enough.
I want to have a variable of (TimeStamp).
Then go through all records incrementing this variable with one second every time for a record update.
It should be like this as per my understanding
DECLARE
serial number := 1;
BEGIN
update table1 set t= t + (( serial + rownum - 1 )/86400);
END;
This will do increment like below
1st row -> 1 sec
2nd row -> 2 sec
.
.
nth row -> nsec
though the serial starts from 1
Another way would be
update table1 set t= t + interval '1' second;
Read more about Interval literals
As per your update, it should be
DECLARE
t_update_time date := sysdate;
BEGIN
update table1 set t=t_update_time + interval '1' second;
END;
This snippet assigns current datetime to t_update_time variable, and updates the record with 1 second added to the datetime declared in t_update_time. Change t_update_time assignment accordingly.
Without a PL/SQL switch it should be as
update table1 set t=to_date('21.01.2015 09:00:00','dd.mm.rrrr hh:mi:ss') + interval '1' second;
It's pretty simple actually. Just do:
update table1 set t = t + 1/86400;
After question update, you can do:
DECLARE
t_serial number;
cursor c is select * from table1 for update of t;
cr table1%rowtype;
BEGIN
t_serial := 1;
for cr in c loop
UPDATE table1 SET t = t_serial/86400 WHERE CURRENT OF c;
t_serial := t_serial + 1;
end loop;
END;

oracle function to return list of dates as object

To whom it may respond to ,
I am trying to return list of dates and weekdays to be used in other functions. Code below is compiled without error. But it should give output of 15 days (via V_MAX_DAYS variable) and number of the day in that week.
I have tried to implement like this, but cannot get output using DBMS_OUTPUT. I want to test it but got ORA-06532 error at when running .
My aim is to return values to asp.net application as we have done using SYS_REFCURSOR.
How can I achieve that?
Thank you for your concern,
The script is as below :
CREATE OR REPLACE TYPE DATE_ROW AS OBJECT
(
WEEKDAY_VALUE DATE,
DATE_IN_LIST VARCHAR2(5)
)
/
CREATE OR REPLACE TYPE DATE_TABLE as table of DATE_ROW
/
CREATE OR REPLACE FUNCTION FN_LISTDATES
RETURN DATE_TABLE
IS
V_DATE_TABLE DATE_TABLE := DATE_TABLE ();
V_MAX_DAYS NUMBER := 15;
V_CALCULATED_DATE DATE;
V_WEEKDAY VARCHAR2 (5);
BEGIN
FOR X IN -2 .. V_MAX_DAYS
LOOP
SELECT TO_DATE (TO_CHAR (SYSDATE + X, 'DD.MM.YYYY'))
INTO V_CALCULATED_DATE
FROM DUAL;
V_DATE_TABLE.EXTEND;
V_DATE_TABLE(X) := DATE_ROW(V_CALCULATED_DATE, 'Test');
END LOOP;
RETURN V_DATE_TABLE;
END;
/
A few points.
If you want a DATE (V_CALCULATED_DATE) that is X days from SYSDATE with the time component set to midnight, which appears to be your intent here, you would want something like v_calculated_date := TRUNC(sysdate) + x;. A TO_DATE without an explicit format mask is going to create issues if a future session's NLS_DATE_FORMAT happens not to be DD.MM.YYYY
If you really want to return a collection like this, your collection indexes would need to start with 1, not -2. You could accomplish that by doing v_date_table(x+3) := DATE_ROW(v_calculated_date, 'Test');.
However, I would tend to suspect that you would be better served here with a pipelined table function.
The pipelined table function would look something like
SQL> ed
Wrote file afiedt.buf
1 CREATE OR REPLACE FUNCTION FN_LISTDATES
2 RETURN DATE_TABLE
3 PIPELINED
4 IS
5 V_MAX_DAYS NUMBER := 15;
6 V_CALCULATED_DATE DATE;
7 V_WEEKDAY VARCHAR2 (5);
8 BEGIN
9 FOR X IN -2 .. V_MAX_DAYS
10 LOOP
11 v_calculated_date := trunc(sysdate) + x;
12 PIPE ROW( DATE_ROW(v_calculated_date,'Test') );
13 END LOOP;
14 RETURN;
15* END;
SQL> /
Function created.
SQL> select * from table( fn_listDates );
WEEKDAY_V DATE_
--------- -----
30-NOV-10 Test
01-DEC-10 Test
02-DEC-10 Test
03-DEC-10 Test
04-DEC-10 Test
05-DEC-10 Test
06-DEC-10 Test
07-DEC-10 Test
08-DEC-10 Test
09-DEC-10 Test
10-DEC-10 Test
WEEKDAY_V DATE_
--------- -----
11-DEC-10 Test
12-DEC-10 Test
13-DEC-10 Test
14-DEC-10 Test
15-DEC-10 Test
16-DEC-10 Test
17-DEC-10 Test
18 rows selected.

Resources