Elegant way to make a SUm - oracle

I need a query (in Oracle), that will be inside a stored procedure, where I can get a sum of the Amount value of Table REV.
The YEAR and one MONTH will be received as a parameter in the stored procedure, as YY and MM.
What I want is to sum the amount values since the 1st month of the year UP to the MM passed in the argument.
So
if MM in the argument is 02, I want to take the sum of amounts of months 01 + 02
if MM in the argument is 05, I want to take the sum of amounts of months 01+02+03+04+05
So MM is the last month to be summed.
How can I make this in the most efficient and elegant way?
CREATE OR REPLACE PROCEDURE "GET_YTD_AMOUNT" (YY in VARCHAR,
MM in VARCHAR)
select
ACT.LABEL ,
R.YEAR,
R.MONTH,
sum(R.AMOUNT)
from
ACTIVITY ACT,
REV R
where
R.YEAR=YEAR and
R.MONTH ??
R.ID_CODE = ACT.ID_CODE

I'd prefer using numeric variables rather than strings for such cases.
In your case ;
considering your year and month parameters are of string type, you need a to_number() conversion with less than or equal to operator
to_number(R.MONTH) <= to_number(i_month)
add an out parameter o_amount to return the result you get
of course, you need to convert your SQL format containing explicit
joins
better to define parameters ( or local variables ) by their type
within the tables in which they're contained. Btw, I didn't define o_amount by rev.amount%type against probability of sum() aggregation might exceed the precision of numeric value provided it's defined as so within the table.
So,use :
CREATE OR REPLACE PROCEDURE GET_YTD_AMOUNT(
i_year in rev.year%type,
i_month in rev.month%type,
o_amount out number
) IS
BEGIN
select sum(r.amount)
into o_amount
from activity a
join rev r
on r.id_code = a.id_code
where r.year = i_year
and to_number(r.month) <= to_number(i_month);
END;
/

You can use less than equal to:
select
ACT.LABEL ,
R.YEAR,
Max(R.MONTH) || '-' || Max(R.MONTH) as months_from_to
sum(R.AMOUNT)
from
ACTIVITY ACT,
REV R
where
R.YEAR= YY and -- it should be YY
R.MONTH <= MM -- less than equal to with MM
R.ID_CODE = ACT.ID_CODE
Group by ACT.LABEL ,
R.YEAR
Note: You must re-design your DB to store dates in date data type.
Cheers!!

Related

OUT parameter with multiples values

create or replace PROCEDURE Show_R(A IN VARCHAR2, B OUT VARCHAR2)
IS
BEGIN
select func_w(day),TO_CHAR(hour, 'HH24:MI')INTO B
from task t
inner join mat m
on t.id_p = m.id_a
where m.cod_mod = A;
END;
I have a issue with this code, this select gets two types of columns data that are not the same type of data, i don't know how to add into B two types of data in only one "out parameter"
You can't put 2 values into 1 OUT parameter. So, use 2 OUT parameters.
Firstly don't store day and hour in separate columns. Just use a single DATE column as, in Oracle, the DATE data type has year, month, day, hour, minute and second components and so can store both the date and time.
Secondly, don't use A, B, show_R or func_w identifiers; use meaningful names as it will be far easier to debug your code in 6-months if you can tell what it is intended to do.
Third, your SELECT ... INTO statement will fail as you have two columns but only one variable to select into; you need 2 variables in INTO clause and this means (unless you are going to concatenate the two values) that you need 2 OUT parameters.
CREATE PROCEDURE Show_w_day_and_hour(
i_cod_mod IN mat.cod_mod%TYPE,
o_w_day OUT VARCHAR2,
o_hour OUT VARCHAR2
)
IS
BEGIN
SELECT func_w(day),
TO_CHAR(hour, 'HH24:MI')
INTO o_w_day,
o_hour
FROM task t
INNER JOIN mat m
ON ( t.id_p = m.id_a )
WHERE m.cod_mod = i_cod_mod;
END;
/
db<>fiddle

Converting Integer into time

I am using Oracle 12c version.
I have a column which is integer data type and it represents total minutes.
I need to convert it into HOURS:MINUTES format.
Could someone suggest how to do that?
Thanks,
Venu
This is a fairly easy thing to accomplish using a series of built in functions.
to_char(floor(field / 60) || ':' || to_char(mod(field, 60), 'FM00')
Breaking it down:
Get the number of hours: to_char(floor(field / 60)
field / 60 gets us the number of hours
floor() ensures that we don't have to deal with decimals
to_char() converts it to a string so we can concatenate it.
Add the ':' || ':' ||
Get the number of minutes
mod(field, 60) gets the remainder of field / 60
to_char() again converts to a string for the concat step
'FM00' ensures that we keep our leading zeros.
Obviously, you'll need to replace field with whatever the field in your database is called.
You can use the to_date built-in function to translate the integer into a valid time.
Example
SQL> select to_char ( to_date ( '2300', 'HH24MI'), 'HH:MI AM') integer_time from dual;

convert minutes to hh/mi/ss format in oracle query

I want to know the query which converts minutes to the format of hh/mi/ss in Oracle.I 've already seen lot of same questions from many forums but nothing helped me to get the exact result.
The query I used -Select to_char(to_date(mod(100,60),'mi'),'hh/mi/ss') from dual;
But I don't know how to get the hour value.Because mod function returns only the remainder I don't know how to take the quotient part and substitute into the hour field.
I suppose there are two ways of storing "minutes" in an Oracle database - you can either store them in a field whose datatype is INTERVAL DAY TO SECOND, or you can store them in a NUMBER. The simplest case to handle is the INTERVAL - in this case, the TO_CHAR function converts the value to a string of the form SDD HH:MM:SS.FFFFFF, where 'S' is sign ('+' or '-' as intervals can be positive or negative), DD = days, HH= hours, 'MM' = minutes, 'SS' = seconds, and 'FFFFFF' = fractions; thus, to get the HH:MI:SS all we need to do is use the SUBSTR function, as in
SUBSTR(TO_CHAR(I_VAL), 5, 8)
where I_VAL is an INTERVAL DAY TO SECOND value.
If the value to be converted is in a numeric field it gets a bit messy as we have to compute the individual field values, then subtract the previous fields as part of getting the next field. However, since the value stored is in minutes instead of seconds it's not particularly difficult:
create table TST (N_VAL NUMBER,
I_VAL INTERVAL DAY TO SECOND);
INSERT INTO TST(N_VAL, I_VAL) VALUES (666, INTERVAL '666' MINUTE);
SELECT N_VAL,
TRUNC(N_VAL/60) AS HOURS,
N_VAL-(TRUNC(N_VAL/60) * 60) AS MINUTES,
0 AS SECONDS,
TO_CHAR(I_VAL),
SUBSTR(TO_CHAR(I_VAL), 5, 8) AS HMS_FROM_INTERVAL
FROM TST;
SQLFiddle here
Best of luck.

Sum of INTERVAL DAY in Oracle [duplicate]

I am trying to sum INTERVAL. E.g.
SELECT SUM(TIMESTAMP1 - TIMESTAMP2) FROM DUAL
Is it possible to write a query that would work both on Oracle and SQL Server? If so, how?
Edit: changed DATE to INTERVAL
I'm afraid you're going to be out of luck with a solution which works in both Oracle and MSSQL. Date arithmetic is something which is very different on the various flavours of DBMS.
Anyway, in Oracle we can use dates in straightforward arithmetic. And we have a function NUMTODSINTERVAL which turns a number into a DAY TO SECOND INTERVAL. So let's put them together.
Simple test data, two rows with pairs of dates rough twelve hours apart:
SQL> alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss'
2 /
Session altered.
SQL> select * from t42
2 /
D1 D2
-------------------- --------------------
27-jul-2010 12:10:26 27-jul-2010 00:00:00
28-jul-2010 12:10:39 28-jul-2010 00:00:00
SQL>
Simple SQL query to find the sum of elapsed time:
SQL> select numtodsinterval(sum(d1-d2), 'DAY')
2 from t42
3 /
NUMTODSINTERVAL(SUM(D1-D2),'DAY')
-----------------------------------------------------
+000000001 00:21:04.999999999
SQL>
Just over a day, which is what we would expect.
"Edit: changed DATE to INTERVAL"
Working with TIMESTAMP columns is a little more labourious, but we can still work the same trick.
In the following sample. T42T is the same as T42 only the columns have TIMESTAMP rather than DATE for their datatype. The query extracts the various components of the DS INTERVAL and converts them into seconds, which are then summed and converted back into an INTERVAL:
SQL> select numtodsinterval(
2 sum(
3 extract (day from (t1-t2)) * 86400
4 + extract (hour from (t1-t2)) * 3600
5 + extract (minute from (t1-t2)) * 600
6 + extract (second from (t1-t2))
7 ), 'SECOND')
8 from t42t
9 /
NUMTODSINTERVAL(SUM(EXTRACT(DAYFROM(T1-T2))*86400+EXTRACT(HOURFROM(T1-T2))*
---------------------------------------------------------------------------
+000000001 03:21:05.000000000
SQL>
At least this result is in round seconds!
Ok, after a bit of hell, with the help of the stackoverflowers' answers I've found the solution that fits my needs.
SELECT
SUM(CAST((DATE1 + 0) - (DATE2 + 0) AS FLOAT) AS SUM_TURNAROUND
FROM MY_BEAUTIFUL_TABLE
GROUP BY YOUR_CHOSEN_COLUMN
This returns a float (which is totally fine for me) that represents days both on Oracle ant SQL Server.
The reason I added zero to both DATEs is because in my case date columns on Oracle DB are of TIMESTAMP type and on SQL Server are of DATETIME type (which is obviously weird). So adding zero to TIMESTAMP on Oracle works just like casting to date and it does not have any effect on SQL Server DATETIME type.
Thank you guys! You were really helpful.
You can't sum two datetimes. It wouldn't make sense - i.e. what does 15:00:00 plus 23:59:00 equal? Some time the next day? etc
But you can add a time increment by using a function like Dateadd() in SQL Server.
In SQL Server as long as your individual timespans are all less than 24 hours you can do something like
WITH TIMES AS
(
SELECT CAST('01:01:00' AS DATETIME) AS TimeSpan
UNION ALL
SELECT '00:02:00'
UNION ALL
SELECT '23:02:00'
UNION ALL
SELECT '17:02:00'
--UNION ALL SELECT '24:02:00' /*This line would fail!*/
),
SummedTimes As
(
SELECT cast(SUM(CAST(TimeSpan AS FLOAT)) as datetime) AS [Summed] FROM TIMES
)
SELECT
FLOOR(CAST(Summed AS FLOAT)) AS D,
DATEPART(HOUR,[Summed]) AS H,
DATEPART(MINUTE,[Summed]) AS M,
DATEPART(SECOND,[Summed]) AS S
FROM SummedTimes
Gives
D H M S
----------- ----------- ----------- -----------
1 17 7 0
If you wanted to handle timespans greater than 24 hours I think you'd need to look at CLR integration and the TimeSpan structure. Definitely not portable!
Edit: SQL Server 2008 has a DateTimeOffset datatype that might help but that doesn't allow either SUMming or being cast to float
I also do not think this is possible. Go with custom solutions that calculates the date value according to your preferences.
You can also use this:
select
EXTRACT (DAY FROM call_end_Date - call_start_Date)*86400 +
EXTRACT (HOUR FROM call_end_Date - call_start_Date)*3600 +
EXTRACT (MINUTE FROM call_end_Date - call_start_Date)*60 +
extract (second FROM call_end_Date - call_start_Date) as interval
from table;
You Can write you own aggregate function :-). Please read carefully http://docs.oracle.com/cd/B19306_01/appdev.102/b14289/dciaggfns.htm
You must create object type and its body by template, and next aggregate function what using this object:
create or replace type Sum_Interval_Obj as object
(
-- Object for creating and support custom aggregate function
duration interval day to second, -- In this property You sum all interval
-- Object Init
static function ODCIAggregateInitialize(
actx IN OUT Sum_Interval_Obj
) return number,
-- Iterate getting values from dataset
member function ODCIAggregateIterate(
self IN OUT Sum_Interval_Obj,
ad_interval IN interval day to second
) return number,
-- Merge parallel summed data
member function ODCIAggregateMerge(
self IN OUT Sum_Interval_Obj,
ctx2 IN Sum_Interval_Obj
) return number,
-- End of query, returning summary result
member function ODCIAggregateTerminate
(
self IN Sum_Interval_Obj,
returnValue OUT interval day to second,
flags IN number
) return number
)
/
create or replace type body Sum_Interval_Obj is
-- Object Init
static function ODCIAggregateInitialize(
actx IN OUT Sum_Interval_Obj
) return number
is
begin
actx := Sum_Interval_Obj(numtodsinterval(0,'SECOND'));
return ODCIConst.Success;
end ODCIAggregateInitialize;
-- Iterate getting values from dataset
member function ODCIAggregateIterate(
self IN OUT Sum_Interval_Obj,
ad_interval IN interval day to second
) return number
is
begin
self.duration := self.duration + ad_interval;
return ODCIConst.Success;
exception
when others then
return ODCIConst.Error;
end ODCIAggregateIterate;
-- Merge parallel calculated intervals
member function ODCIAggregateMerge(
self IN OUT Sum_Interval_Obj,
ctx2 IN Sum_Interval_Obj
) return number
is
begin
self.duration := self.duration + ctx2.duration; -- Add two intervals
-- return = All Ok!
return ODCIConst.Success;
exception
when others then
return ODCIConst.Error;
end ODCIAggregateMerge;
-- End of query, returning summary result
member function ODCIAggregateTerminate(
self IN Sum_Interval_Obj,
returnValue OUT interval day to second,
flags IN number
) return number
is
begin
-- return = All Ok, too!
returnValue := self.duration;
return ODCIConst.Success;
end ODCIAggregateTerminate;
end;
/
-- You own new aggregate function:
CREATE OR REPLACE FUNCTION Sum_Interval(
a_Interval interval day to second
) RETURN interval day to second
PARALLEL_ENABLE AGGREGATE USING Sum_Interval_Obj;
/
Last, check your function:
select sum_interval(duration)
from (select numtodsinterval(1,'SECOND') as duration from dual union all
select numtodsinterval(1,'MINUTE') as duration from dual union all
select numtodsinterval(1,'HOUR') as duration from dual union all
select numtodsinterval(1,'DAY') as duration from dual);
Finally You can create SUM function, if you want.

convert from time in format h.mm to minutes

I have time stored as number in an oracle database in the format hh.mm, I want to calculate the sum and then write it back in another column in the same format hh.mm using a store procedure.
Is there any function in oracle for this type of summation or I have to do it from scratch?
Thanks in advance.
You are storing values in a custom format. So obviously Oracle won't a built-in function to handle it.
Doing this requires a series of steps, which could be wrapped into a user-defined function.
Convert the numeric column into a string. It is important to specify the format mask, otherwise the next step will produce the wrong result
Use regular expressions to extract the hour and minute values from the column
Derive the total number of minutes with simple arithmetic
Derive the new total number of hours and remainder of minutes with simple arithmetic
Derive the final number as a pseudo-decimal value.
Here is the SQL ...
select hh + (mi/100) as final_result
from (
select trunc(step3.tot_mins/60) as hh
, step3.tot_mins - (trunc(step3.tot_mins/60)*60) as mi
from (
select sum((step2.hh*60)+step2.mi) as tot_mins
from (
with step1 as (select to_char(ctime, '00000000000.99') ctime
from your_table)
select to_number(regexp_substr(ctime, '([0-9]+)', 1,1)) as hh
, to_number(regexp_substr(ctime, '([0-9]+)', 1, 2)) as mi
from step1
) step2
) step3
) step4
/
... and here is the obligatory SQL Fiddle.

Resources