Create View with 365 days - oracle

How to Create a View with all days in year. view should fill with dates from JAN-01 to Dec-31. How can I do this in Oracle ?
If current year have 365 days,view should have 365 rows with dates. if current year have 366 days,view should have 366 rows with dates. I want the view to have a single column of type DATE.

This simple view will do it:
create or replace view year_days as
select trunc(sysdate, 'YYYY') + (level-1) as the_day
from dual
connect by level <= to_number(to_char(last_day(add_months(trunc(sysdate, 'YYYY'),11)), 'DDD'))
/
Like this:
SQL> select * from year_days;
THE_DAY
---------
01-JAN-11
02-JAN-11
03-JAN-11
04-JAN-11
05-JAN-11
06-JAN-11
07-JAN-11
08-JAN-11
09-JAN-11
10-JAN-11
11-JAN-11
...
20-DEC-11
21-DEC-11
22-DEC-11
23-DEC-11
24-DEC-11
25-DEC-11
26-DEC-11
27-DEC-11
28-DEC-11
29-DEC-11
30-DEC-11
31-DEC-11
365 rows selected.
SQL>
The date is generated by applying several Oracle date functions:
trunc(sysdate, 'yyyy') gives us the first of January for the current year
add_months(x, 11) gives us the first of December
last_day(x) gives us the thirty-first of December
to_char(x, 'DDD') gives us the number of the thirty-first of December, 365 this year and 366 next.
This last figure provides the upper bound for the row generator CONNECT BY LEVEL <= X

you can use piplined table, it should be something like this:
create or replace type year_date_typ as object (v_day date);
create or replace type year_date_tab as table of year_date_typ;
CREATE OR REPLACE FUNCTION get_dates(year IN VARCHAR2) RETURN year_date_tab PIPELINED IS
v_start_date date := to_date('0101' || year, 'ddmmyyyy');
res year_date_typ := year_date_typ(null);
v_days_in_year integer := 365;
BEGIN
if to_char(last_day(to_date('0102'||year, 'ddmmyyyy')), 'dd') = '29' then
v_days_in_year := 366;
end if;
FOR i in 0 .. v_days_in_year integer-1 LOOP
res.v_day := v_start_date + i;
pipe row(res);
END LOOP;
return;
END get_dates;
and you can use it:
select * from table(get_dates('2011'));

This works well in MS SQL
SELECT TOP (DATEDIFF(day, DATEADD(yy, DATEDIFF(yy,0,getdate()), 0), DATEADD(yy, DATEDIFF(yy,0,getdate()) + 1, -1))) n = ROW_NUMBER() OVER (ORDER BY [object_id]),
dateadd(day, ROW_NUMBER() OVER (ORDER BY [object_id]) - 1, DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)) AS AsOfDate FROM sys.all_objects

Related

create pl sql trigger

I am trying to write a Trigger for before insert to validate a case date.
The case date should be between 5 years before and 7 years after current date.
For example, in 2018 the case date should be from year 2013 to 2025. If the date is out of range the trigger should stop inserting data.
CREATE OR REPLACE TRIGGER ch
BEFORE INSERT
on CASE
FOR EACH ROW
DECLARE
CASN number;
BEGIN
SELECT COUNT(*)
INTO CASN
FROM CASE
WHERE :new.STARTDATE > SYSDATE;
IF (CASN > 0) THEN
RAISE_APPLICATION_ERROR(-20000,'Start DATE CANNOT Be GREATER than today's
date');
END IF;
END;
Here STARTDATE is column of CASE table
This trigger starts when start date is greater than today's date but I need it to run when it's out range as given above.
How do I add an specified interval to the sysdate, so that it could work for the above condition?
The logic you are using in your Trigger is completely wrong. You don't need to get the count from the table using :NEW.STARTDATE. This is something what you are looking for.
CREATE OR replace TRIGGER ch
BEFORE INSERT ON cases
FOR EACH ROW
BEGIN
IF ( :NEW.casedate < SYSDATE - INTERVAL '5' year
OR :NEW.casedate > SYSDATE + INTERVAL '7' year ) THEN
RAISE_APPLICATION_ERROR(-20000,
'CASE DATE should be in range: current date - 5 years and current date + 7 years')
;
END IF;
END;
/
EDIT : I have not added TRUNC on the dates because I'm not sure if you want to consider time component as well while considering date range.If you are ok with just considering days, you may use TRUNC(SYSDATE) in place of just SYSDATE. Modify it accordingly as per your business needs.
Another option is to use CHECK constraint. Although Oracle does not allow you to have
use SYSDATE in a check constraint definition, you may create another column( or reuse existing) that defaults to SYSDATE and apply check constraint on that.
ALTER TABLE CASES ADD ( CURR_DATE DATE DEFAULT SYSDATE );
ALTER TABLE CASES ADD CONSTRAINT
RANGE_CHECK CHECK( casedate > CURR_DATE - INTERVAL '5' YEAR
AND casedate < CURR_DATE + INTERVAL '7' YEAR) ENABLE;
Date arithmetic. Oracle Database enables you to perform arithmetic operations on dates and time stamps in several ways:
Add a numeric value to or subtract it from a date, as in SYSDATE + 7; Oracle Database treats the number as the number of days.
Add one date to or subtract it from another, as in l_hiredate - SYSDATE.
Use a built-in function to “move” a date by a specified number of months or to another date in a week.
Here are some examples of date arithmetic with a date and a number (assume in all cases that the l_date variable has been declared as DATE):
Set a local variable to tomorrow’s date:
l_date := SYSDATE + 1;
Move back one hour:
l_date := SYSDATE - 1/24;
Move ahead 10 seconds:
l_date := SYSDATE + 10 / (60 * 60 * 24);
When you add one date to or subtract it from another, the result is the number of days between the two. As a result, executing this block:
DECLARE
l_date1 DATE := SYSDATE;
l_date2 DATE := SYSDATE + 10;
BEGIN
DBMS_OUTPUT.put_line (
l_date2 - l_date1);
DBMS_OUTPUT.put_line (
l_date1 - l_date2);
END;
returns the following output:
10
-10

How to get monthly periods between a date range in Oracle PL/SQL

I have say, start and end date as "6/11/1996" and "3/1/2002" in "mm/dd/yyyy" format respectively.
I need to get all the monthly periods as given below.
Start Date End Date
6/11/1996 - 6/30/1996
7/01/1996 - 7/31/1996
8/01/1996 - 8/31/1996
.
.
.
Till
2/01/2002 - 2/28/2002
Any help would be highly appreciated.
Assuming the interval is given by way of two bind variables, :from_dt and :to_dt (strings in the indicated format):
with
inputs ( f_dt, t_dt ) as (
select to_date(:from_dt, 'mm/dd/yyyy'), to_date(:to_dt, 'mm/dd/yyyy') from dual
),
ld ( l_day, lvl ) as (
select add_months(last_day(f_dt), level - 1), level
from inputs
connect by level <= months_between(last_day(t_dt), last_day(f_dt)) + 1
)
select case when ld.lvl = 1 then i.f_dt else add_months(ld.l_day, -1) + 1 end
as start_date,
least(i.t_dt, ld.l_day) as end_date
from inputs i cross join ld
;
This assumes that in the original post you did, in fact, mean to have one more interval, from 3/1/2002 to 3/1/2002; and the query deals correctly with the case when the from-date and the to-date are in the same month: if the inputs are 6/11/1996 to 6/21/1996, then the output is exactly that interval.
Added: creating column aliases in the declaration of factored subqueries (in the WITH clause), as I have done, requires Oracle 11.2 or above. For earlier versions, it is necessary to write it a little differently, like so:
with
inputs as (
select to_date(:from_dt, 'mm/dd/yyyy') as f_dt,
to_date(:to_dt , 'mm/dd/yyyy') as t_dt
from dual
),
ld as (
select add_months(last_day(f_dt), level - 1) as l_day, level as lvl
from inputs ...............
You could use connect by query with functions add_months and months_between:
with p as ( select date '1996-06-11' d1, date '2002-03-01' d2 from dual )
select greatest(trunc(add_months(d1, level - 1), 'month'), d1) as d1,
trunc(add_months(d1, level), 'month') - 1 as d2
from p connect by level <= months_between(trunc(d2, 'month'), trunc(d1, 'month'))
The output is exactly as requested.
DECLARE
L_min_date DATE := '11-Jun-1996';
L_max_date DATE := '28-Feb-2002';
L_number_of_months NUMBER := 0;
L_new_date DATE := NULL;
BEGIN
L_number_of_months := MONTHS_BETWEEN(L_max_date, L_min_date);
DBMS_OUTPUT.PUT_LINE('Start_Date End_Date');
FOR i IN 1..L_number_of_months
LOOP
SELECT (ADD_MONTHS(L_min_date, 1) - 1) INTO L_new_date FROM dual;
DBMS_OUTPUT.PUT_LINE(L_min_date || ' - ' || L_new_date);
L_min_date := L_new_date + 1;
END LOOP;
END;
/
Something like above can help.

Showing Different aggregate for different products

I want a code which is used to generate aggregate product by product. The product aggregate can be any like from Year to Date(YTD), Months to Date(MTD) and Quarter to Date(QTD). The user will pass the parameter on that basis the code should decide what kind of output the user wants.
If the Year is passing in the parameter than the code should generate the aggregate from the starting of the year to the sysdate.
If the Quarter No is passing in the parameter than the code should generate the aggregate from the starting of the quarter to the sysdate.
If the Month is passing in the parameter than the code should generate the aggregate from the starting of the month to the sysdate.
It means that on the basis of the parameter it should be able to decide which kind of user want from those 3. My input data is like this-
Product Table
Product_ID Product_name Price
1 Mobile 200
2 T.V. 400
3 Mixer 300
and
Sales Table-
Product_ID Sales_Date Quantity
1 01-01-2015 30
2 03-01-2015 40
3 06-02-2015 10
1 22-03-2015 30
2 09-04-2015 10
3 21-05-2015 40
1 04-06-2015 40
2 29-07-2015 30
1 31-08-2015 30
3 14-09-2015 30
And my ouput column contains 3 columns that are- Product_id, Product_Name and Total. The column Total_Amount(quantity*price) have to calculate sale on the basis of input given by user and is be something like this-
For example ,
If pro_test is the procedure then
call pro_test('YTD') -- Should Return the ProductWise YTD,
call pro_test('QTD') -- Should Return the ProductWise QTD and so on..
You are looking for a WHERE clause :-) List your conditions with OR and you are done.
select
p.product_id,
p.product_name,
coalesce(sum(s.quantity * p.price), 0) as total
from product p
left join sales s on s.product_id = p.product_id
where
(:aggregate = 'YTD' and to_char(s.sales_date, 'yyyy') = to_char(sysdate, 'yyyy'))
or
(:aggregate = 'MTD' and to_char(s.sales_date, 'yyyymm') = to_char(sysdate, 'yyyymm'))
or
(:aggregate = 'QTD' and to_char(s.sales_date, 'yyyyq') = to_char(sysdate, 'yyyyq'))
group by p.product_id, p.product_name;
EDIT: Here is how the corresponding PL/SQL function would look like:
create or replace function matches_date_aggregate(in_sales_date date, in_aggregate char)
return integer as
begin
if (in_aggregate = 'YTD' and to_char(in_sales_date, 'yyyy') = to_char(sysdate, 'yyyy'))
or (in_aggregate = 'MTD' and to_char(in_sales_date, 'yyyymm') = to_char(sysdate, 'yyyymm'))
or (in_aggregate = 'QTD' and to_char(in_sales_date, 'yyyyq') = to_char(sysdate, 'yyyyq')) then
return 1;
else
return 0;
end if;
end matches_date_aggregate;
Your query's WHERE clause would become:
where matches_date_aggregate(s.sales_date, :aggregate) = 1
The function cannot return BOOLEAN unfortunately, for even though Oracle's PL/SQL knows the BOOLEAN data type, Oracle SQL doesn't.

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.

date difference between two date datetype variable in HH:MM:SI in Oracle [duplicate]

This question already has answers here:
In Oracle, is there a function that calculates the difference between two Dates?
(5 answers)
Closed 8 years ago.
I am facing an issue where I have to take the difference between two date datetype variables in HH:MM:SS AM. For example if date1 stores 23-DEC-2014 02:00:00 PM and date2 stores 24-DEC-2014 02:00:00 PM then date2 - date1 should return 24:00:00.
I tried different to_char and likewise methods.
Can you please suggest what I should do to resolve this issue.
As you have plain DATE, the difference between two dates is expressed in fractional days. Some little arythmetics as explained in the related questions might help.
One other approach would be to cast the difference to an INTERVAL using NUMTODSINTERVAL. However, this does not work out-of-the-box, as (of 11g at least), the TO_CHAR function does not supports correctly INTERVAL.
However, as a workaround that is not provided in the related answers (or do I missed it?), you can cast to INTERVAL DAY TO SECOND using the right precision to achieve (more or less) what you are looking for:
Here is an example
with testdata as (select to_date('12/12/2014 09:00:00','DD/MM/YYYY HH:MI:SS') a,
to_date('10/11/2014 11:30:14','DD/MM/YYYY HH:MI:SS') b from dual)
select a-b "days",
numtodsinterval(a-b, 'DAY') "ds interval",
CAST(numtodsinterval(a-b, 'DAY') AS INTERVAL DAY(3) TO SECOND(0))
-- ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-- cast to 3 digit days interval -- no fractional seconds
from testdata
Producing (formatted as rows for display purpose):
days
31.8956712962962962962962962962962962963
ds interval
+000000031 21:29:46.000000000
CAST(NUMTODSINTERVAL(A-B,'DAY') AS INTERVAL DAY(3) TO SECOND(0))
+031 21:29:46
I don't know if/how you can get rid of the leading sign though
Maybe this helps:
CREATE OR REPLACE FUNCTION get_date_diff(p_date1 IN DATE,
p_date2 IN DATE) RETURN VARCHAR2
IS
v_seconds NUMBER;
v_minutes NUMBER;
v_hours NUMBER;
v_time VARCHAR2(20);
BEGIN
v_seconds := (p_date2 - p_date1) * 24 * 60 * 60;
v_hours := FLOOR(v_seconds / 3600);
v_minutes := FLOOR((v_seconds - (v_hours * 3600)) / 60);
v_seconds := FLOOR(v_seconds - (v_hours * 3600) - (v_minutes * 60));
v_time := CASE WHEN v_hours < 100
THEN LPAD(TO_CHAR(v_hours), 2, '0')
ELSE TO_CHAR(v_hours)
END || ':' ||
LPAD(TO_CHAR(v_minutes), 2, '0') || ':' ||
LPAD(TO_CHAR(v_seconds), 2, '0');
RETURN v_time;
END;
/
SAMPLE INPUT
p_date1:=to_date('20/11/2014 11:30:45','DD/MM/YYYY HH:MI:SS')
p_date2 :=to_date('15/12/2014 09:00:00','DD/MM/YYYY HH:MI:SS')
SAMPLE OUTPUT
597:29:15

Resources