Getting particular day using MySql - mysql

I need to get Bth working day in a month when the value of B is entered.
For example, If b=12 in the month of January,2013 the resultant value should be in the date format as '17-01-2013' as the result is calculated
after excluding Saturdays, Sundays & holidays in the month.
I have tried it in SQLserver with the following code & its working fine, but Im finding it difficult to execute it in MySql as some functions are not
available as in Sqlserver.
Declare
#fromDate Date,
#Daydiff int
Set #fromDate ='01 jan 2013'
Set #Daydiff=datediff(day, #fromdate, dateadd(month, 1, #fromdate))
Select * from
(
Select
dateadd(day,DayNo,#fromDate) as Date,
dateName(weekday,(dateadd(day,DayNo,#fromDate))) As WeekDate,
Datename(month,(dateadd(day,DayNo,#fromDate))) as MonthName,
Row_number() Over (partition by (DatePart(month,(dateadd(day,DayNo,#fromDate))))
order by (dateadd(day,DayNo,#fromDate))) as Business_day
from
(Select top (#Daydiff) row_number() over(order by (select 1))-1 as DayNo
from sys.syscolumns a cross join sys.syscolumns b)Dates
Where
dateName(weekday,(dateadd(day,DayNo,#fromDate))) Not In ('Saturday','Sunday') and
dateadd(day,DayNo,#fromDate) Not In (Select hdate from Holidays)
)A
Where Business_day=1
Note
Holidays is the static holidays table which contains list of holidays of 2013
I need a similar instance in Mysql.
Kindly help me with this.

SQLFiddle demo
If you need first day set OFFSET 0 in the end. If the second OFFSET 1, if 15-th set OFFSET 14
select d
FROM
(
SELECT #row := #row + 1 as row,
DATE_ADD('2013-01-01', INTERVAL #row-1 DAY) d
from
(SELECT #row := 0) r,
(
select 1 n
union all
select 2 n
union all
select 3 n
union all
select 4 n
union all
select 5 n
union all
select 6 n
) t1,
(
select 1 n
union all
select 2 n
union all
select 3 n
union all
select 4 n
union all
select 5 n
union all
select 6 n
) t2
) num_seq
where
d<DATE_ADD('2013-01-01', INTERVAL 1 MONTH)
and d not in (select hdate from Holidays )
and DAYNAME(d) not in ('Saturday','Sunday')
order by d
LIMIT 1 OFFSET 20
Version without OFFSET and LIMIT. See the latest where r=1 it is the 1-st day. If you need 15-th day change to where r=15
SQLFiddle demo
select d
from
(
select d,#r := #r + 1 as r
FROM
(SELECT #r := 0) r1,
(
SELECT #row := #row + 1 as row,
DATE_ADD('2013-01-01', INTERVAL #row-1 DAY) d
from
(SELECT #row := 0) r,
(
select 1 n
union all
select 2 n
union all
select 3 n
union all
select 4 n
union all
select 5 n
union all
select 6 n
) t1,
(
select 1 n
union all
select 2 n
union all
select 3 n
union all
select 4 n
union all
select 5 n
union all
select 6 n
) t2
) num_seq
where
d<DATE_ADD('2013-01-01', INTERVAL 1 MONTH)
and d not in (select hdate from Holidays )
and DAYNAME(d) not in ('Saturday','Sunday')
order by d
) rTable
where r=1

how to get the same result when only month and year are passed as parameters. Coz when i checked the code... its working when the date is 1st of the respective month, Like if I enter parameter as '2013-01-01' the result is absolute, but if the date is given as '2013-01-15' the procedure is counting the 1st day 15th and calculating the nth day starting from there.

Related

Mysql View where a value is split over multiple rows according to date

MySql version: 5.6.47
Assuming I have a table like this:
[from:Datetime][to:Datetime][amount:Decimal(10,2)]
[2020/01/15 ][2020/02/15 ][300 ]
I want to create a view like this out of it:
[period:char(7)][amount:Decimal(10,2)]
[2020/01 ][150 ]
[2020/02 ][150 ]
The from and to dates are split up in the singular months. The amount is multiplied with the amount of days in that particular month over the total amount of days between from and to. From and to could span n amount of month.
Is that even possible or am I wasting my time researching this?
Assuming that the amount of months in a range is not over 100:
SELECT id,
datefrom,
datetill,
amount,
monthstart,
monthfinish,
amount * (DATEDIFF(LEAST(datetill, monthfinish), GREATEST(datefrom, monthstart)) + 1) / (DATEDIFF(datetill, datefrom) + 1) monthamount
FROM ( SELECT test.*,
(test.datefrom - INTERVAL DAY(test.datefrom) - 1 DAY) + INTERVAL numbers.num MONTH monthstart,
LAST_DAY((test.datefrom - INTERVAL DAY(test.datefrom) - 1 DAY) + INTERVAL numbers.num MONTH) monthfinish
FROM test
JOIN ( SELECT t1.num*10+t2.num num
FROM (SELECT 0 num UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t1
JOIN (SELECT 0 num UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t2
) numbers
HAVING monthstart <= test.datetill
AND monthfinish >= test.datefrom
) subquery
ORDER BY id, monthstart;
fiddle
PS. Don't be surprised if the total sum doesn't match in the last digit.

How do I create a sequence of dates?

I want to count the number of actions per day in my dataset.
date action_id
2010-01-01 id00
2010-01-03 id01
2010-01-05 id02
This is just a sample, but the point is that my data does not include actions for every day and I want to include days where there are zero actions in my result.
My plan is to do this.
with dates as (
select [sequence of dates from 2010-01-01 to 2010-02-01] as day)
select day, coalesce(count(distinct action_id), 0) as actions
from dates
left join my_table
on dates.date = my_table.date
How do I create the sequence of dates?
You example shows a CTE. So, you can use a recursive CTE:
with recursive dates as (
select date('2010-01-01') as day
union all
select day + interval 1 day
from dates
where day < '2010-02-01'
)
select d.day, count(distinct t.action_id) as actions
from dates d left join
my_table t
on d.day = my_table.date
group by d.day;
Note that COUNT() never returns NULL, so COALESCE() is unnecessary.
In older versions, you can use a calendar table or generate the data on the fly. Assuming your table has enough rows:
select d.day, count(distinct t.action_id) as actions
from (select date('2010-01-01') + interval (#rn := #rn + 1) - 1 day as day
from my_table cross join
(select #rn := 0) params
limit 31
) d left join
my_table t
on d.day = my_table.date
group by d.day;
it seems just you need group by and count
select date, count(distinct action_id) as action
from my_table left join
dates on dates.date = my_table.date
group by date
with dates as
(
select a.Date
from (
select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a) + (1000 * d.a) ) DAY as Date
from (select 0 as a 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) as a
cross join (select 0 as a 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) as b
cross join (select 0 as a 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) as c
cross join (select 0 as a 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) as d
) a
where a.Date between '<start_date>' and '<end_date>' )
select day, count(distinct action_id) as actions
from dates
left join my_table
on dates.date = my_table.date

Return 0 as SUM result if no records are found

I'm trying to summarize the calories of activities per day for a specific user for the last 7 days (actual day is 7th day). There are tables user and activities and the mapping table user_activities.
The following example is for the user with id=1;
Sum up calories each day last 7 days (this day 7th day) */
SELECT
DATE(a.end_time), SUM(a.calories)
FROM
activities a
JOIN
user_activities uc ON uc.activity_id = a.id
WHERE
uc.user_id = 1
AND DATE(a.end_time) >= CURRENT_DATE - INTERVAL 6 DAY
GROUP BY
DATE(a.end_time) DESC
That query returns this result set:
2018-12-28 9600
2018-12-27 1200
2018-12-26 1200
2018-12-25 1200
2018-12-24 4800
2018-12-22 1200
Which is correct but now my problem is, as you can see in the list the 12-23-2018 is not listed because there are no activities on this date. Now I want to display
2018-12-23 0
instead of nothing.
How can I get the desired result?
Thanks for your help
I also tried with IFNULL and COALESCE but no luck so far
Sum up calories each day last 7 days (this day 7th day) */
SELECT
DATE(a.end_time), SUM(a.calories)
FROM
activities a
JOIN
user_activities uc ON uc.activity_id = a.id
WHERE
uc.user_id = 1
AND DATE(a.end_time) >= CURRENT_DATE - INTERVAL 6 DAY
GROUP BY
DATE(a.end_time) DESC
Result:
2018-12-28 9600
2018-12-27 1200
2018-12-26 1200
2018-12-25 1200
2018-12-24 4800
2018-12-22 1200
Expected result:
2018-12-28 9600
2018-12-27 1200
2018-12-26 1200
2018-12-25 1200
2018-12-24 4800
2018-12-23 0
2018-12-22 1200
Activities table:
If there's a chance your activities table doesn't have an activity listed on a particular day, then fa06's trick won't work out. A simple way to cover that case is to add some zero records on before you do your sum:
SELECT DATE(d), SUM(c)
FROM (
SELECT a.end_time as d, a.calories as c
FROM activities a
JOIN user_activities uc ON uc.activity_id = a.id
WHERE uc.user_id = 1
AND DATE(a.end_time) >= CURRENT_DATE - INTERVAL 6 DAY
UNION ALL
SELECT CURRENT_DATE, 0
UNION ALL
SELECT CURRENT_DATE - INTERVAL 1 DAY, 0
UNION ALL
SELECT CURRENT_DATE - INTERVAL 2 DAY, 0
UNION ALL
SELECT CURRENT_DATE - INTERVAL 3 DAY, 0
UNION ALL
SELECT CURRENT_DATE - INTERVAL 4 DAY, 0
UNION ALL
SELECT CURRENT_DATE - INTERVAL 5 DAY, 0
UNION ALL
SELECT CURRENT_DATE - INTERVAL 6 DAY, 0
) z
GROUP BY DATE(d)
fa06's method relies on there being one record every day in the activities table. It's a valid way to approach the problem, but you haven't been specific about what data these tables contain (hint: when posting a db question, do include sample data from each table)
With this method we do our lookup as normal, but we also generate 7 fake rows with dates from the last 7 days and a 0 calorie count. When added onto real calorie counts, these are fat free ;) and when there is no data for a particular day, these stand alone to provide the 0
If you want more days, consider moving to a row generating pattern. MySQL doesn't have row generators like other DBs, but the simplest trick is to create
variable, init it with 0 then increment and use it upon every row returned from a table with at least 30 rows:
SELECT CURRENT_DATE - INTERVAL (#row := #row + 1) DAY as dt, 0 as cal
FROM activities t, (SELECT #row := -1) r
LIMIT 30
The theory behind this is: the table has at least 30 rows, #row variable is inited to -1, and exists session-wide for the query. As rows are pulled out and returned, #row is incremented and then returned (so it's 0, 1, 2.. ) and this increasing count is used to subtract 0, 1, 2 etc days off of the current data, giving us a sequence of dates for the past 30 days
SELECT DATE(d), SUM(c)
FROM (
SELECT a.end_time as d, a.calories as c
FROM activities a
JOIN user_activities uc ON uc.activity_id = a.id
WHERE uc.user_id = 1
AND DATE(a.end_time) >= CURRENT_DATE - INTERVAL 29 DAY
UNION ALL
SELECT CURRENT_DATE - INTERVAL (#row := #row + 1) DAY as dt, 0 as cal
FROM activities t, (SELECT #row := -1) r
WHERE #row < 30
) z
GROUP BY DATE(d)
Note that I wasn't able to test either of these queries; the second might have some minor syntax error. If it turns out not to work, and the error is nontrivial/something you cant fix let me know.
Debugging:
Run each of these queries in isolation. I don't know how many rows this will produce:
SELECT a.end_time as d, a.calories as c
FROM activities a
JOIN user_activities uc ON uc.activity_id = a.id
WHERE uc.user_id = 1
AND DATE(a.end_time) >= CURRENT_DATE - INTERVAL 29 DAY
And this one should produce 30 rows. If it doesn't it will be because you didn't use a table with at least 30 rows:
SELECT CURRENT_DATE - INTERVAL (#row := #row + 1) DAY as dt, 0 as cal
FROM activities t, (SELECT #row := -1) r
LIMIT 30
Edit:
Fixed the bug with this - LIMIT was being applied after the union; this was giving undesired results
You can try below - using left join
SELECT DATE(a.end_time), SUM(a.calories)
FROM activities a
left JOIN user_activities uc ON uc.activity_id = a.id
Where uc.user_id = 1 AND DATE(a.end_time) >= CURRENT_DATE - INTERVAL 6 DAY
GROUP BY DATE(a.end_time) order by DATE(a.end_time) DESC
I'm trying it on my end and just updated the SUM(a.calories) to IF(SUM(a.calories) IS NULL, 0, SUM(a.calories))
Here you may try...
SELECT DATE(a.end_time), IF(SUM(a.calories) IS NULL, 0, SUM(a.calories))
FROM activities a
JOIN user_activities uc ON uc.activity_id = a.id
WHERE uc.user_id = 1
AND
DATE(a.end_time) >= CURRENT_DATE - INTERVAL 6 DAY
GROUP BY DATE(a.end_time) DESC
This query should to the trick :
the CTE dynamically generates a date range (this will go up to about 300 years ahead)
the result is LEFT JOINed with the activities and user_activities tables
the COALESCE function turns empty SUMs into 0 values
Query :
WITH v AS (
SELECT * FROM
(SELECT adddate('1970-01-01',t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) selected_date FROM
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t0,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t1,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t2,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t3,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t4) v
WHERE selected_date BETWEEN CURRENT_DATE - INTERVAL 6 DAY AND CURRENT_DATE
)
SELECT v.selected_date, COALESCE(SUM(a.calories), 0)
FROM
v
LEFT JOIN activities a on DATE(a.end_time) = v.selected_date
LEFT JOIN user_activities uc ON uc.activity_id = a.id AND uc.user_id = 1
GROUP BY DATE(a.end_time) DESC
ORDER BY v.selected_date

Select records of previous month and show 0 if no records are there in specific time-window

Hello I have this query to get a list of drives that occurred for a specific month in a time window of 1 year back.
SELECT COUNT( drives.id ) AS drives, DATE_FORMAT( drives.timestamp, '%d-%m-%Y' ) AS mdate
FROM drives, users
WHERE drives.user = '146'
AND DATE_FORMAT( drives.timestamp, '%b' ) = 'Feb'
AND drives.timestamp > DATE_SUB(now(), INTERVAL 12 MONTH)
GROUP BY DATE(drives.timestamp) ORDER BY drives.timestamp ASC
I get the following result:
drives mdate
1 14-02-2013
2 17-02-2013
However I would like a result with every date of the month even if no records are found for that date, and display 0 next to the date that no drives took place.The tricky part for me is how to get the exact dates of the specific month in 1 year back timewindow.
I could implement this with php but I would prefer a cleaner solution.
Not sure where the users table comes into this (you are cross joining it, but not actually using it anywhere), but something like this should do what you require (not tested).
SELECT Sub1.aDay, COUNT( drives.id ) AS drives
FROM
(
SELECT DATE_ADD('2013-02-01', INTERVAL units.i + tens.i * 10 DAY) AS aDay
FROM
(SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) units
CROSS JOIN
(SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) tens
HAVING MONTH(aDay) = 2
) Sub1
LEFT OUTER JOIN drives
ON Sub1.aDay = DATE(drives.timestamp)
WHERE drives.user = '146'
GROUP BY DATE(drives.timestamp)
ORDER BY drives.timestamp ASC

Get the last working day in the last month of a Quarter using MySql

The result of the below query is giving me the last working day in a month.
select DATE_FORMAT(max(dates), '%m-%d-%Y') Last_day
from
(select dates,#r := #r + 1 as r
FROM
(SELECT #r := 0) r1,
(SELECT #row := #row + 1 as row, DATE_ADD('2013-03-01', INTERVAL #row-1 DAY) dates
From
(SELECT #row := 0) r,
(select 1 n union all select 2 n union all select 3 n union all select 4 n union all select 5 n union all select 6 n) t1,
(select 1 n union all select 2 n union all select 3 n union all select 4 n union all select 5 n union all select 6 n) t2
) num_seq
where
dates<DATE_ADD('2013-03-01', INTERVAL 1 MONTH)
and DAYNAME(dates) not in ('Saturday','Sunday')
order by dates )rTable;
The result of the below query is giving me the last day in quarter of particular given day.
select MAKEDATE(YEAR('2013-03-01'), 1)+ INTERVAL QUARTER('2013-03-01') QUARTER - INTERVAL 1 DAY a;
The issue which i have is to get the last working day from previous, present and next quarter.
Is there a way to get the result using the above both sql into one.
kindly help me with this.
If you only want the last working days for the previous, present and next quarter, it might be much easier to just compute these dates in your application code and then pass them to the database.