decrease/increase between date in mysql or oracle SQL - mysql

I've problem in query mysql
I want to find hour per date between 2 date divided by day
for example,
I have startdate 14/12/2014 09:00:00AM , and enddate 17/12/2014 03:30:00AM
I want to result like this
14/12/2014 - 15 hour
15/12/1014 - 24 hour
16/12/2014 - 24 hour
17/12/2014 - 3,5 hour
Is that possible in a MySQL or Oracle SQL query?

Finally, I have found the query for oracle sql...
THANKS FOR YOUR HELP
SELECT
TO_DATE (START_DATE + COLUMN_VALUE - 1, 'DD/MM/YYYY') AS THE_DATE,
CASE
WHEN TO_DATE (START_DATE, 'DD/MM/YYYY') = TO_DATE (END_DATE, 'DD/MM/YYYY')
THEN (END_DATE - START_DATE)*24
WHEN (START_DATE + COLUMN_VALUE - 1) = START_DATE
THEN (23 - TO_CHAR (START_DATE, 'HH24')) + ((60 - TO_CHAR (START_DATE, 'MI'))/60) + ((360 - TO_CHAR (START_DATE, 'SS'))/360)
WHEN TO_DATE (START_DATE + COLUMN_VALUE - 1, 'DD/MM/YYYY') <> TO_DATE (END_DATE, 'DD/MM/YYYY')
THEN 24
ELSE TO_CHAR (END_DATE, 'HH24') +
(TO_CHAR (END_DATE, 'MI')/60) +
(TO_CHAR (END_DATE, 'SS')/360) END
FROM MY_TABLE,
TABLE( CAST( MULTISET (
SELECT
LEVEL L
FROM DUAL
CONNECT BY LEVEL <= END_DATE - START_DATE + 1) AS SYS.ODCINUMBERLIST))

Use MySQL TIMESTAMPDIFF or Oracle EXTRACT function to calculate difference between two value fields of Datetime type (2nd and 3rd parameters). 1st parameter passed is your selected measurement to count the difference. This could be HOUR, MINUTE, SECOND, DAY etc.
SELECT
`startdate` AS `date`,
TIMESTAMPDIFF(
HOUR,
`startdate`,
`enddate`) AS `hours_delta`
FROM my_table
The result would be:
___date_______hours_delta____
14/12/2014________66________
15/12/1014________..._________

SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE MY_TABLE ( ID, START_DATE, END_DATE ) AS
SELECT 1, TO_DATE( '14/12/2014 09:00:00AM', 'DD/MM/YYYY HH:MI:SSAM'), TO_DATE( '17/12/2014 03:30:00AM', 'DD/MM/YYYY HH:MI:SSAM') FROM DUAL
UNION ALL SELECT 2, TO_DATE( '20/12/2014 09:00:00AM', 'DD/MM/YYYY HH:MI:SSAM'), TO_DATE( '20/12/2014 03:30:00PM', 'DD/MM/YYYY HH:MI:SSAM') FROM DUAL
Query 1:
WITH Differences AS (
SELECT ID,
TRUNC( START_DATE ) AS Day,
LEAST(
TRUNC( START_DATE ) + INTERVAL '1' DAY,
END_DATE
) - START_DATE AS Diff
FROM MY_TABLE
UNION
SELECT ID,
COLUMN_VALUE,
CASE WHEN COLUMN_VALUE < TRUNC( END_DATE )
THEN 1
ELSE END_DATE - COLUMN_VALUE
END
FROM MY_TABLE,
TABLE( CAST( MULTISET (
SELECT TRUNC( START_DATE ) + LEVEL
FROM DUAL
WHERE TRUNC( START_DATE ) + LEVEL <= TRUNC( END_DATE )
CONNECT BY
TRUNC( START_DATE ) + LEVEL <= TRUNC( END_DATE )
) AS SYS.ODCIDATELIST ) )
)
SELECT ID,
TO_CHAR( Day, 'YYYY-MM-DD' ) AS Day,
TO_CHAR( TO_NUMBER( Diff * 24 ), '99D90' )
|| ' hour ' AS Hours
FROM DIFFERENCES
Results:
| ID | DAY | HOURS |
|----|------------|--------------|
| 1 | 2014-12-14 | 15.00 hour |
| 1 | 2014-12-15 | 24.00 hour |
| 1 | 2014-12-16 | 24.00 hour |
| 1 | 2014-12-17 | 3.50 hour |
| 2 | 2014-12-20 | 6.50 hour |

Related

Count Number of a Specific Day(s) Between Two Dates

I have a single line in MySQL table: volunteers
user_id | start_date | end_date
11122 | 2017-04-20 | 2018-02-17
How can I find how many times the 3rd day or 24th day of a month appears? (i.e. 2017-05-03, 2017-06-03, 2017-12-24, 2018-01-24) I'm trying to get to the following count:
Sample Output:
user_id | number_of_third_day | number_of_twenty_fourth_day
11122 | 10 | 10
I look at the documentation online to see if there is a way I can say (pseudo):
SELECT
day, COUNT(*)
FROM volunteers
WHERE day(between(start_date, end_date)) in (3,24)
I tried to create a calendar table to no avail, but I would try to get the days, GROUP BY day, and COUNT(*) times that day appears in the range
WITH calendar AS (
SELECT start_date AS date
FROM volunteers
UNION ALL
SELECT DATE_ADD(start_date, INTERVAL 1 DAY) as date
FROM volunteers
WHERE DATE_ADD(start_date, INTERVAL 1 DAY) <= end_date
)
SELECT date FROM calendar;
Thanks for any help!
This one is more optimized since I generate date range by months not days as other questions, so its faster
WITH RECURSIVE cte AS
(
SELECT user_id, DATE_FORMAT(start_date, '%Y-%m-03') as third_day,
DATE_FORMAT(start_date, '%Y-%m-24') as twenty_fourth_day,
start_date, end_date
FROM table1
UNION ALL
SELECT user_id,
DATE_FORMAT(third_day + INTERVAL 1 MONTH, '%Y-%m-03') as third_day,
DATE_FORMAT(twenty_fourth_day + INTERVAL 1 MONTH, '%Y-%m-24') as twenty_fourth_day,
start_date, end_date
FROM cte
WHERE third_day + INTERVAL 1 MONTH <= end_date
)
SELECT user_id,
SUM(CASE WHEN third_day BETWEEN start_date AND end_date THEN 1 ELSE 0 END) AS number_of_third_day,
SUM(CASE WHEN twenty_fourth_day BETWEEN start_date AND end_date THEN 1 ELSE 0 END) AS number_of_twenty_fourth_day
FROM cte
GROUP BY user_id;
Demo here
A dynamic approach is.
but creating the dateranges, takes a lot of time, so you should have a date table to get the dates
CREATE TABLE table1
(`user_id` int, `start_date` varchar(10), `end_date` varchar(10))
;
INSERT INTO table1
(`user_id`, `start_date`, `end_date`)
VALUES
(11122, '2017-04-20', '2018-02-17')
,(11123, '2019-04-20', '2020-02-17')
;
Records: 2 Duplicates: 0 Warnings: 0
WITH RECURSIVE cte AS (
SELECT
user_id,
`start_date` as date_run ,
`end_date`
FROM table1
UNION ALL
SELECT
user_id,
DATE_ADD(cte.date_run, INTERVAL 1 DAY),
end_date
FROM cte
WHERE DATE_ADD(date_run, INTERVAL 1 DAY) <= end_date
)SELECT user_id,
SUM(DAYOFMONTH(date_run) = 3) as day_3th,
SUM(DAYOFMONTH(date_run) = 24) as day_24th
FROM cte
GROUP BY user_id
user_id
day_3th
day_24th
11122
10
10
11123
10
10
fiddle
In last MySQL version you can use recursion:
-- get list of all dates in interval
with recursive dates(d) as (
select '2017-04-20'
union all
select date_add(d, interval 1 day) from dates where d < '2018-02-17'
) select
-- calculate
sum(day(d) = 10) days_10,
sum(day(d) = 24) days_24
from dates
-- filter 10 & 24 days
where day(d) = 10 or day(d) = 24;
https://sqlize.online/sql/mysql80/c00eb7de69d011a85502fa538d64d22c/
As long as you are looking for days that occur in every month (so not the 29th or beyond), this is just straightforward math. The number of whole calendar months between two dates (exclusive) is:
timestampdiff(month,start_date,end_date) - (day(start_date) <= day(end_date))
Then add one if the start month includes the target day and one if the end month includes it:
timestampdiff(month,start_date,end_date) - (day(start_date) <= day(end_date))
+ (day(start_date) <= 3) + (day(end_date) >= 3)

mysql LAST_DAY() only reads 1 subquery result, how to process all results? using joins?

I have an insurance policies table like this:
+-------------------------------------------------------------+
| id | cancellation_val | cancellation_interval | expire_date |
+-------------------------------------------------------------+
| 1 | 30 | day | 2019-06-09 |
| 2 | 2 | month | 2019-12-01 |
+-------------------------------------------------------------+
I need to get the ids of the policies that are going to expire based on cancellation, from today and within 4 months, calculating the last day of the month, like this pseudo-code:
'today' <= LAST_DAY( expire_date - cancellation_val/interval ) < 'today + 4 months'
Being not a pro I think I should use JOINs but I don't know how, after days of trying the only thing I achieved was this:
SELECT LAST_DAY(
DATE_FORMAT(
STR_TO_DATE(
(SELECT CASE cancellation_interval
WHEN "day" THEN date_sub(expire_date, INTERVAL cancellation_val DAY)
WHEN "month" THEN date_sub(data_scadenzaexpire_date, INTERVAL cancellation_val MONTH)
END
AS newDate
FROM insurance WHERE id=2
), '%Y-%m-%d'
), '%Y-%m-%d'
)
)
This is working but I don't need the "WHERE id=2" clause (because I need to process ALL rows of the table), and if I remove it I got error "subquery returns more than 1 row".
So how I can proceed? And using the result to stay between 'today' AND 'today + 4 months' ?
I think with some kind of JOIN I could do it in a easier way but I don't know how.
Thank you all
The problem is the structure of the query, not the LAST_DAY function.
We want to return the id values of rows that meet some condition. So the query would be of the form:
SELECT t.id
, ...
FROM insurance t
WHERE ...
HAVING ...
Introducing another SELECT keyword basically introduces a subquery. There are restrictions on subqueries... in the SELECT list, a subquery can return a single column and (at most) a single row.
So let's ditch that extra SELECT keyword.
We can derive the newdate as an expression of the SELECT list, and then we can reference that derived column in the HAVING clause. The spec said we wanted to return the id value, so we include that in the SELECT list. We don't have to return any other columns, but for testing/debugging, it can be useful to return the values that were used to derive the newdate column.
Something like this:
SELECT t.id
, LAST_DAY(
CASE t.cancellation_interval
WHEN 'day' THEN t.expire_date - INTERVAL t.cancellation_val DAY
WHEN 'month' THEN t.expire_date - INTERVAL t.cancellation_val MONTH
ELSE t.expire_date
END
) AS newdate
, t.expire_date
, t.cancellation_interval
, t.cancellation_val
FROM insurance t
HAVING newdate >= DATE(NOW())
AND newdate <= DATE(NOW()) + INTERVAL 4 MONTH
ORDER
BY newdate ASC
We don't have to include the newdate in the SELECT list; we could just replace occurrences of newdate in the HAVING clause with the expression.
We could also use an inline view to "hide" the derivation of the newdate column
SELECT v.id
, v.newdate
FROM ( SELECT t.id
, LAST_DAY(
CASE t.cancellation_interval
WHEN 'day' THEN t.expire_date - INTERVAL t.cancellation_val DAY
WHEN 'month' THEN t.expire_date - INTERVAL t.cancellation_val MONTH
ELSE t.expire_date
END
) AS newdate
FROM insurance t
) v
WHERE v.newdate >= DATE(NOW())
AND v.newdate <= DATE(NOW()) + INTERVAL 4 MONTH
ORDER
BY v.newdate ASC
check this query: remove the HAVING Line to see all rows
SELECT
IF(cancellation_interval = 'day',
i.expire_date - INTERVAL i.`cancellation_val` DAY,
i.expire_date - INTERVAL i.`cancellation_val` MONTH
) as cancellation_day,
i.*
FROM `insurance` i
HAVING cancellation_day < NOW() + INTERVAL 4 MONTH;
SAMPLES
MariaDB [test]> SELECT IF(cancellation_interval = 'day', i.expire_date - INTERVAL i.`cancellation_val` DAY, i.expire_date - INTERVAL i.`cancellation_val` MONTH ) as cancellation_day, i.* FROM `insurance` i HAVING cancellation_day < NOW() + INTERVAL 4 MONTH;
+------------------+----+------------------+-----------------------+-------------+
| cancellation_day | id | cancellation_val | cancellation_interval | expire_date |
+------------------+----+------------------+-----------------------+-------------+
| 2019-05-10 | 1 | 30 | day | 2019-06-09 |
+------------------+----+------------------+-----------------------+-------------+
1 row in set (0.001 sec)
When you use a SELECT query as an expression, it can only return one row.
If you want to process all the rows, you need to call LAST_DAY() inside the query, not on the result.
SELECT *
FROM insurance
WHERE CURDATE() <= LAST_DAY(
expire_date - IF(cancellation_interval = 'day',
INTERVAL cancellation_val DAY,
INTERVAL cancellation_val MONTH))
AND LAST_DAY(expire_date - IF(cancellation_interval = 'day',
INTERVAL cancellation_val DAY,
INTERVAL cancellation_val MONTH)) < CURDATE + INTERVAL 4 MONTH

How can i get two row from 1 in duration mysql?

I have rows like:
id, start_date, end_date
0, 2000-01-01 20:00:00, 2000-01-01 21:00:00
1, 2000-01-01 23:00:00, 2000-01-02 04:00:00
And I need get reporting result like:
date | time_online
2000-01-01 | 02:00:00
2000-01-02 | 04:00:00
My solution was wrong cos i only start_date count.
SELECT DATE_FORMAT(start_date, '%Y-%m-%d') as date,
SUM(CASE WHEN EXTRACT(DAY FROM start_date) <> EXTRACT(DAY FROM end_date)
THEN
TIMESTAMPDIFF(SECOND, start_date, DATE_FORMAT(start_date + INTERVAL 1 DAY, '%Y-%m-%d'))
ELSE
TIMESTAMPDIFF(SECOND, start_date, end_date) END) time_online
FROM online
GROUP BY date
Result:
date | time_online
2000-01-01 | 02:00:00
Can someone help me?
What you need is a (virtual) reference table with a 24 hour timespan for each date in the online table.
You can use the table itself to do that:
SELECT
DATE(start_date) + INTERVAL 0 HOUR ref_start,
DATE(start_date) + INTERVAL 24 HOUR ref_end
FROM
online
WHERE
end_date IS NOT NULL
UNION DISTINCT
SELECT
DATE(end_date) + INTERVAL 0 HOUR ref_start,
DATE(end_date) + INTERVAL 24 HOUR ref_end
FROM
online
WHERE
end_date IS NOT NULL
The + INTERVAL 0 HOUR is not really neccesary, I added that for clarity, the same goes for the DISTINCT keyword.
If you put this in a subquery then you can get with a (kind of) self-join the records with overlaps, and the calculate the difference depending on the the values:
SELECT
DATE(r.ref_start) ref_date,
SEC_TO_TIME(SUM(TIMESTAMPDIFF(SECOND,
CASE WHEN d.start_date >= r.ref_start
THEN d.start_date
ELSE r.ref_start
END,
CASE WHEN d.end_date <= r.ref_end
THEN d.end_date
ELSE r.ref_end
END))) time_online
FROM
(
SELECT
DATE(start_date) + INTERVAL 0 HOUR ref_start,
DATE(start_date) + INTERVAL 24 HOUR ref_end
FROM
online
WHERE
end_date IS NOT NULL
UNION DISTINCT
SELECT
DATE(end_date) + INTERVAL 0 HOUR ref_start,
DATE(end_date) + INTERVAL 24 HOUR ref_end
FROM
online
WHERE
end_date IS NOT NULL
) r
JOIN
online d
ON d.end_date > r.ref_start
AND d.start_date < r.ref_end
GROUP BY ref_date

Finding month difference and then remaining days after months - mysql

I need to find the difference between two dates to do some calculations based on the result.
Let's say column start_date is having value 1/Jan/2014 and column and end_date is having value 15/Mar/2014. The result I want is following format:
months | days_remain |
----------------------
2 15
I can find MONTH difference and also DAY difference between separately (as 2 Months & 74 days) using TIMESTAMPDIFF function. But how to find out the remaining 15 days ?
You can use DATEDIFF to see the difference between 2 dates
SELECT DATEDIFF('2006-04-01','2005-04-01');
http://lists.mysql.com/mysql/196414
Try This, I hope it will Work For You.
select DateDiff(d, datepart(month,[Start_Date]),datepart(month,End_Date)) as Months,
(30-day(end_date)) as Days_remain
from Sdate
This will work for sure.
Declare #StartDate datetime
Declare #EndDate datetime
Declare #years varchar(40)
Declare #months varchar(30)
Declare #days varchar(30)
set #StartDate ='2014/01/01'
set #EndDate = '2014/03/15'
select #years=datediff(year,#StartDate,#EndDate)
select #months=datediff(month,#StartDate,#EndDate)-(datediff(year,#StartDate,#EndDate)*12)
select #days=datepart(d,#EndDate)-datepart(d,#StartDate)
select #years +' years, ' +#months +' months, '+#days +' days' asYearMonthDay
I think this might be what you want. It does however return 14 days for remaining, but as Jaugar Chang pointed out in a comment that should be correct if as the difference between March 1st and March 15th is 14 days.
select
timestampdiff(
month,
start_date,
end_date
) as months,
datediff(
end_date,
timestampadd(
month,
timestampdiff(
month,
start_date,
end_date
)
,start_date
)
) as days_remain
from test;
Sample SQL Fiddle
Sample result:
| START_DATE | END_DATE | MONTHS | DAYS_REMAIN |
|------------------|----------------|--------|-------------|
| January, 01 2014 | March, 15 2014 | 2 | 14 |
| January, 10 2014 | March, 13 2014 | 2 | 3 |
Try this. See the sample data as well:
select
dt1, dt2,
trunc( months_between(dt2,dt1) ) mths,
dt2 - add_months( dt1, trunc(months_between(dt2,dt1)) ) days
from
(
select date '2012-01-01' dt1, date '2012-03-25' dt2 from dual union all
select date '2012-01-01' dt1, date '2013-01-01' dt2 from dual union all
select date '2012-01-01' dt1, date '2012-01-01' dt2 from dual union all
select date '2012-02-28' dt1, date '2012-03-01' dt2 from dual union all
select date '2013-02-28' dt1, date '2013-03-01' dt2 from dual union all
select date '2013-02-28' dt1, date '2013-04-01' dt2 from dual
) sample_data;
Hope it helps.

Mysql : Finding empty time blocks between two dates and times?

I wanted to find out user's availability from database table:
primary id | UserId | startdate | enddate
1 | 42 | 2014-05-18 09:00 | 2014-05-18 10:00
2 | 42 | 2014-05-18 11:00 | 2014-05-18 12:00
3 | 42 | 2014-05-18 14:00 | 2014-05-18 16:00
4 | 42 | 2014-05-18 18:00 | 2014-05-18 19:00
Let's consider above inserted data is user's busy time, I want to find out free time gap blocks from table between start time and end time.
BETWEEN 2014-05-18 11:00 AND 2014-05-18 19:00;
Let me add here schema of table for avoiding confusion:
Create Table availability (
pid int not null,
userId int not null,
StartDate datetime,
EndDate datetime
);
Insert Into availability values
(1, 42, '2013-10-18 09:00', '2013-10-18 10:00'),
(2, 42, '2013-10-18 11:00', '2013-10-18 12:00'),
(3, 42, '2013-10-18 14:00', '2013-11-18 16:00'),
(4, 42, '2013-10-18 18:00', '2013-11-18 19:00');
REQUIREMENT:
I wanted to find out free gap records like:
'2013-10-27 10:00' to '2013-10-28 11:00' - User is available for 1 hours and
'2013-10-27 12:00' to '2013-10-28 14:00' - User is available for 2 hours and
available start time is '2013-10-27 10:00' and '2013-10-27 12:00' respectively.
Here you go
SELECT t1.userId,
t1.enddate, MIN(t2.startdate),
MIN(TIMESTAMPDIFF(HOUR, t1.enddate, t2.startdate))
FROM user t1
JOIN user t2 ON t1.UserId=t2.UserId
AND t2.startdate > t1.enddate AND t2.pid > t1.pid
WHERE
t1.endDate >= '2013-10-18 09:00'
AND t2.startDate <= '2013-11-18 19:00'
GROUP BY t1.UserId, t1.endDate
http://sqlfiddle.com/#!2/50d693/1
Using your data, the easiest way is to list the hours when someone is free. The following gets a list of hours when someone is available:
select (StartTime + interval n.n hour) as FreeHour
from (select cast('2014-05-18 11:00' as datetime) as StartTime,
cast('2014-05-18 19:00' as datetime) as EndTime
) var join
(select 0 as n union all select 1 union all select 2 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 7 union all select 8 union all select 9
) n
on StartTime + interval n.n hour <= EndTime
where not exists (select 1
from availability a
where StartTime + interval n.n hour < a.EndDate and
StartTime + interval n.n hour >= a.StartDate
);
EDIT:
The general solution to your problem requires denormalizing the data. The basic query is:
select thedate, sum(isstart) as isstart, #cum := #cum + sum(isstart) as cum
from ((select StartDate as thedate, 1 as isstart
from availability a
where userid = 42
) union all
(select EndDate as thedate, -1 as isstart
from availability a
where userid = 42
) union all
(select cast('2014-05-18 11:00' as datetime), 0 as isstart
) union all
(select cast('2014-05-18 19:00' as datetime), 0 as isstart
)
) t
group by thedate cross join
(select #cum := 0) var
order by thedate
You then just choose the values where cum = 0. The challenge is getting the next date from this list. In MySQL that is a real pain, because you cannot use a CTE or view or window function, so you have to repeat the query. This is why I think the first approach is probably better for your situation.
The core query can be this. You can dress it up as you like, but I'd handle all that stuff in the presentation layer...
SELECT a.enddate 'Available From'
, MIN(b.startdate) 'To'
FROM user a
JOIN user b
ON b.userid = a.user
AND b.startdate > a.enddate
GROUP
BY a.enddate
HAVING a.enddate < MIN(b.startdate)
For times outside the 'booked' range, you have to extend this a little with a UNION, or again handle the logic at the application level