Number of days in a month - sql

I have a monthly amount that I need to spread equally over the number of days in the month. The data looks like this:
Month Value
----------- ---------------
01-Jan-2012 100000
01-Feb-2012 121002
01-Mar-2012 123123
01-Apr-2012 118239
I have to spread the Jan amount over 31 days, the Feb amount over 29 days and the March amount over 31 days.
How can I use PL/SQL to find out how many days there are in the month given in the month column?

SELECT CAST(to_char(LAST_DAY(date_column),'dd') AS INT)
FROM table1

Don't use to_char() and stuff when doing arithmetics with dates.
Strings are strings and dates are dates. Please respect the data types and use this instead:
1+trunc(last_day(date_column))-trunc(date_column,'MM')
Indeed, this is correct. It computes the difference between the value of the last day of the month and the value of the first (which is obviously always 1 and therefore we need to add this 1 again).
You must not forget to use the trunc() function if your date columns contains time, because last_day() preserves the time component.

SELECT EXTRACT(DAY FROM LAST_DAY(SYSDATE)) num_of_days FROM dual;
/
SELECT SYSDATE, TO_CHAR(LAST_DAY(SYSDATE), 'DD') num_of_days FROM dual
/
-- Days left in a month --
SELECT SYSDATE, LAST_DAY(SYSDATE) "Last", LAST_DAY(SYSDATE) - SYSDATE "Days left"
FROM DUAL
/

You can add a month and subtract the two dates
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select date '2012-01-01' dt from dual union all
3 select date '2012-02-01' from dual union all
4 select date '2012-03-01' from dual union all
5 select date '2012-01-31' from dual
6 )
7 select dt, add_months(trunc(dt,'MM'),1) - trunc(dt,'MM')
8* from x
SQL> /
DT ADD_MONTHS(TRUNC(DT,'MM'),1)-TRUNC(DT,'MM')
--------- -------------------------------------------
01-JAN-12 31
01-FEB-12 29
01-MAR-12 31
31-JAN-12 31

select add_months(my_date, 1)-my_date from dual;

SELECT TO_CHAR(LAST_DAY(SYSDATE), 'fmday-mon-rr dd') as LastDayOfMonth
FROM dual;

Use the following Oracle query:
select to_number(to_char(last_day(sysdate),'dd')) TotalDays from dual
Date_Parameter='01-Oct-2017'
select to_number(to_char(last_day('Date_Parameter'),'dd')) TotalDays from dual

Related

How do I subtract multiple dates?

An image of the Discharge date and admission date in which i am supposed to subtract as (discharge date – admitted
date) +1 .
As you said - subtract and sum. Sample data in lines #1 - 5, query begins at line #6.
SQL> with test (who, admission_date, discharge_date) as
2 (select 'Leslie', date '2010-09-13', date '2010-09-25' from dual union all
3 select 'Leslie', date '2014-09-03', date '2014-09-21' from dual union all
4 select 'Leslie', date '2015-12-03', date '2015-12-14' from dual
5 )
6 select who, sum(discharge_date - admission_date + 1) total_days
7 from test
8 group by who;
WHO TOTAL_DAYS
------ ----------
Leslie 44
SQL>

Calculate calendar days by month for date ranges

I have a table with date ranges:
CREATE TABLE REQUEST (
REQUEST_ID NUMBER(*,0) NOT NULL ENABLE,
EMPLOYEE_ID NUMBER(*,0) NOT NULL ENABLE,
START_DATE DATE NOT NULL ENABLE,
END_DATE DATE NOT NULL ENABLE,
CONSTRAINT REQUEST_PK PRIMARY KEY (REQUEST_ID)
);
The're also a couple of constraints (omitted for brevity) that ensure they're valid (end date cannot be less then start date) and force time to be 00:00:00. (Runnable fiddle with sample data).
Is there a way to split my data set by year/month into a result set based on the date ranges? The rules are:
A request splits in as many rows as months its range covers.
Each month has a column stating how many calendar days the range has in such month.
For example, a request with [2020-12-28, 2021-02-10] would produce three rows:
request_id year month days
========== ==== ===== ====
1 2020 12 4
1 2021 1 31
1 2021 2 10
I've been playing with CONNECT BY but I've been unable to adapt it to my use case. Is that the right tool?
A more direct way to do the computation is to use connect by to generate just the needed months (not every day in every interval) - and then to do the day computation directly, rather than by counting. Something like this:
Adding data for testing:
insert into request (request_id, employee_id, start_date, end_date)
select 1, 1001, date '2020-12-28', date '2021-02-10' from dual union all
select 2, 4002, date '2021-02-10', date '2021-02-20' from dual union all
select 3, 6004, date '2020-12-15', date '2021-03-31' from dual
;
commit;
Query and output:
with
prep (request_id, start_date, end_date, mth) as (
select request_id, start_date, end_date,
add_months(trunc(start_date, 'mm'), level - 1)
from request
connect by level <= months_between(trunc(end_date, 'mm'),
trunc(start_date, 'mm')) + 1
and prior request_id = request_id
and prior sys_guid() is not null
)
select request_id, extract(year from mth) as year_,
extract(month from mth) as month_,
least(last_day(mth), end_date) - greatest(mth, start_date) + 1 as days
from prep
order by request_id, mth -- if needed
;
REQUEST_ID YEAR_ MONTH_ DAYS
---------- ---------- ---------- ----------
1 2020 12 4
1 2021 1 31
1 2021 2 10
2 2021 2 11
3 2020 12 17
3 2021 1 31
3 2021 2 28
3 2021 3 31
For example this one:
WITH t AS (
SELECT DATE '2020-12-28' +LEVEL-1 AS ts
FROM dual
CONNECT BY DATE '2020-12-28' +LEVEL-1 <= DATE '2021-02-10')
SELECT
EXTRACT(YEAR FROM TRUNC(ts, 'Month')) AS YEAR,
EXTRACT(MONTH FROM TRUNC(ts, 'Month')) AS MONTH,
COUNT(ts) AS DAYS
FROM t
GROUP BY TRUNC(ts, 'Month')
ORDER BY YEAR, MONTH;

How to loop in Oracle SQL

Assume we have exampleDB and select contractdate like
SELECT DB.contractdate
FROM exampleDB DB
contractdate
2014/12/1
2015/12/1
2016/12/1
2017/12/1
2018/12/1
2019/12/1
I would like to count the policy number at each time like
each time policy count
2014/1/1 0
2015/1/1 1
2016/1/1 2
2017/1/1 3
2018/1/1 4
2019/1/1 5
I tried
WHERE DB.contractdate <='2014/1/1';
But I must loop such code manually.
How can I loop?
If the binning is every month,it is very stressful process.
can they be combined into one?
Best regards
select contractdate as "each time",
count(*) as "policy count"
from exampleDB
where contractdate in (mention all dates you want)
group by contractdate
Hope this will help you.
you can use row_num() and trunc() to get 1st day of the month
SELECT TRUNC(DB.contractdate, 'MONTH'), row_number() over (order by DB.contractdate) -1 as policy_count
FROM exampleDB DB
You can use COUNT analytical function with RANGE operator as follows:
SQL> with dataa(contractdate) as
2 (
3 select date '2014-12-01' from dual union all
4 select date '2015-12-01' from dual union all
5 select date '2016-12-01' from dual union all
6 select date '2017-12-01' from dual union all
7 select date '2018-12-01' from dual union all
8 select date '2019-12-01' from dual
9 )
10 SELECT
11 TRUNC(CONTRACTDATE, 'year') as "each time",
12 COUNT(1) OVER(
13 ORDER BY
14 CONTRACTDATE DESC
15 RANGE BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING
16 ) as "policy count"
17 FROM
18 DATAA order by 1;
each time policy count
--------- ------------
01-JAN-14 0
01-JAN-15 1
01-JAN-16 2
01-JAN-17 3
01-JAN-18 4
01-JAN-19 5
6 rows selected.
SQL>
Cheers!!

ORA-01848: day of year must be between 1 and 365 (366 for leap year) error

I have existing data with day of the year (1-366) stored in DDD format.
Now, when I am trying to query the data and pull out report in MM/DD/YYYY format, I am getting
ORA-01848: day of year must be between 1 and 365 (366 for leap year) for the below query
select to_CHAR(TO_DATE(MyColumn, 'DDD'),'MM/DD/YYYY') from MyTable;
How to retrieve date in MM/DD/YYYY format when the year is leap year?
Your current query:
select to_CHAR(TO_DATE(MyColumn, 'DDD'),'MM/DD/YYYY') from MyTable;
is defaulting to the current year. All of the converted values will show 2018:
-- CTE for dummy values (<= 365)
with mytable(mycolumn) as (
select 1 from dual
union all select 60 from dual
union all select 365 from dual
)
select to_CHAR(TO_DATE(MyColumn, 'DDD'),'MM/DD/YYYY') from MyTable;
TO_CHAR(TO
----------
01/01/2018
03/01/2018
12/31/2018
As 2018 isn't a leap year, day 366 isn't valid. You could make it use an arbitrary hard-coded leap year:
select to_CHAR(TO_DATE('2000' || MyColumn, 'YYYYDDD'),'MM/DD/YYYY') from MyTable;
Demo:
-- CTE for dummy values
with mytable(mycolumn) as (
select 1 from dual
union all select 60 from dual
union all select 365 from dual
union all select 366 from dual
)
select to_CHAR(TO_DATE('2000' || MyColumn, 'YYYYDDD'),'MM/DD/YYYY') from MyTable;
TO_CHAR(TO
----------
01/01/2000
02/29/2000
12/30/2000
12/31/2000
But if the original date wasn't from a leap year then the values will be out by a day - as well as being shown against the wrong year, of course.
You could filter out values with values > 365 and stick to the current year, but again you're likely to get unrealistic/unhelpful converted dates. Or you could use 12c's default ... on conversion error syntax to get say a null result when it won't convert, but again other dates will be inconsistent.
Unless you know the year each DDD values represents you can't get an accurate conversion.
If you have another column that holds the year then concatenate them together, e.g. if that year column is called MyYear:
select to_CHAR(TO_DATE(MyYear || MyColumn, 'YYYYDDD'),'MM/DD/YYYY') from MyTable;
Demo showing varying resulsts:
-- CTE for dummy values
with mytable(mycolumn, myyear) as (
select 1, 2018 from dual
union all select 60, 2016 from dual
union all select 60, 2017 from dual
union all select 365, 2016 from dual
union all select 366, 2016 from dual
union all select 365, 2017 from dual
)
select MyColumn, MyYear,
to_CHAR(TO_DATE(MyColumn default null on conversion error, 'DDD'),'MM/DD/YYYY') as Y2018,
to_CHAR(TO_DATE('2000' || MyColumn, 'YYYYDDD'),'MM/DD/YYYY') as Y2000,
to_CHAR(TO_DATE(MyYear || MyColumn, 'YYYYDDD'),'MM/DD/YYYY') as OK
from MyTable;
MYCOLUMN MYYEAR Y2018 Y2000 OK
---------- ---------- ---------- ---------- ----------
1 2018 01/01/2018 01/01/2000 01/01/2018
60 2016 03/01/2018 02/29/2000 02/29/2016
60 2017 03/01/2018 02/29/2000 03/01/2017
365 2016 12/31/2018 12/30/2000 12/30/2016
366 2016 12/31/2000 12/31/2016
365 2017 12/31/2018 12/30/2000 12/31/2017
I created a logic which would calculate the date as per the input day. When its not a leap year, it would display the date. Incase of leap year it would display the last day if day number is 366. If it's not a leap year, it would display the last day of the year if day number is 366, hence it would never result in the error you faced.
with tb(col1) as (Select level
from dual
connect by level < 367)
-- Actual Query ..Col1 is the day number being passed.
Select Case when col1 < 366 then
to_char(TO_DATE(col1, 'DDD'),'MM/DD/YYYY')
else
to_char(LAST_DAY(ADD_MONTHS(TRUNC(SYSDATE , 'Year'),11)),'MM/DD/YYYY')
end col
from tb ;
DEMO

sql oracle ignore holidays

I am using this code to calculate the difference between two dates ignoring weekends:
SELECT To_date(SYSDATE) -
To_date('01.07.2014', 'DD.MM.YYYY')
- 2 * ( TRUNC(Next_day(To_date(SYSDATE) - 1, 'FRI'))
- TRUNC( Next_day(To_date('01.07.2014' , 'DD.MM.YYYY')
- 1, 'FRI')) ) / 7 AS DAYS_BETWEEN
FROM dual
I have another table called table1 in which the column "date" exists (its type is "DATE") in which all dates where a holiday is are written down.
Example table 1:
DATES
12.06.2011
19.06.2014
09.05.2013
...
I am trying to make my code check this table and that if one date is between the two dates above it makes -1 day in the output.
It should be easy if you divide it into following tasks:
Generate all the dates between the two given dates using Row Generator method as shown here.
Ignore the dates which are weekend, i.e. Saturdays and Sundays
Check whether the dates in the range are having any match in the holiday table.
The following row generator query will give you the total count of weekdays, i.e. not including Saturdays and Sundays:
SQL> WITH dates AS
2 (SELECT to_date('01/01/2014', 'DD/MM/YYYY') date1,
3 to_date('31/12/2014', 'DD/MM/YYYY') date2
4 FROM dual
5 )
6 SELECT SUM(weekday) weekday_count
7 FROM
8 (SELECT
9 CASE
10 WHEN TO_CHAR(date1+LEVEL-1, 'DY','NLS_DATE_LANGUAGE=AMERICAN')
11 NOT IN ('SAT', 'SUN')
12 THEN 1
13 ELSE 0
14 END weekday
15 FROM dates
16 CONNECT BY LEVEL <= date2-date1+1
17 )
18 /
WEEKDAY_COUNT
-------------
261
SQL>
Now, based on above row generator query, let's see a test case.
The following query will calculate the count of working days between 1st Jan 2014 and 31st Dec 2014 excluding the holidays as mentioned in the table.
The WITH clause is only to use it as tables, in your case you can simply use your holiday table.
SQL> WITH dates
2 AS (SELECT To_date('01/01/2014', 'DD/MM/YYYY') date1,
3 To_date('31/12/2014', 'DD/MM/YYYY') date2
4 FROM dual),
5 holidays
6 AS (SELECT To_date('12.06.2011', 'DD.MM.YYYY') holiday FROM dual UNION ALL
7 SELECT To_date('19.06.2014', 'DD.MM.YYYY') holiday FROM dual UNION ALL
8 SELECT To_date('09.05.2013', 'DD.MM.YYYY') holiday FROM dual),
9 count_of_weekdays
10 AS (SELECT SUM(weekday) weekday_count
11 FROM (SELECT CASE
12 WHEN To_char(date1 + LEVEL - 1, 'DY',
13 'NLS_DATE_LANGUAGE=AMERICAN')
14 NOT IN (
15 'SAT',
16 'SUN' ) THEN 1
17 ELSE 0
18 END weekday
19 FROM dates
20 CONNECT BY LEVEL <= date2 - date1 + 1)),
21 count_of_holidays
22 AS (SELECT Count(*) holiday_count
23 FROM holidays
24 WHERE holiday NOT BETWEEN To_date('01/01/2015', 'DD/MM/YYYY') AND
25 To_date('31/03/2015', 'DD/MM/YYYY'))
26 SELECT weekday_count - holiday_count as working_day_count
27 FROM count_of_weekdays,
28 count_of_holidays
29 /
WORKING_DAY_COUNT
-----------------
258
SQL>
There were total 261 weekdays, out of which there were 3 holidays in holiday table. So, total count of working days in the output is 261 - 3 = 258.
SELECT To_date(sysdate)- To_date('01.07.2014','DD.MM.YYYY')
- (2 * (to_char(To_date(sysdate), 'WW') - to_char(To_date('01.07.2014','DD.MM.YYYY'), 'WW'))) AS DAYS_BETWEEN
FROM dual