I have a Calendar table. I would like get 2 business before Today's Date while skipping Holidays and weekeends.
For example:
Example 1: New years 2013 falls on a Tuesday. If it was Thursday (1/3), then my starting date would be Monday (12/31).
Example 2: If it was Monday (12/31), then my starting date would be Thursday (12/27).
My Clandar table as been prepopulated with 30 years worth of dates and have flags to determine which ones are weekend and hollidays. The table has these fields:
dt: Date and time for 30 years
Y: year
D: Day
M: Month
Isholiday: (Is a holiday are not, flagged with 0 for "no" and 1 for "Yes")
IsWeekday: (Is a weekday? Flagged with 0 for "no" and 1 for "Yes"
HolidayDescritption: Holiday Names
Please help.
This is what you want :
select top 1 *
from ( select *
from Calendar
where IsHoliday = 0
and IsWeekday = 1
and dt between DateAdd( Day, -6, GetDate ) and GetDate()
) tmpCalendar
where dt <= DateAdd( Day, -2, GetDate() )
order by dt desc
Make sure to replace * with only the fields that you want.
Try this one:
SELECT dt FROM
(
SELECT dt, ROW_NUMBER() OVER (ORDER BY dt DESC) AS R
FROM
Calendar
WHERE
IsHoliday = 0
AND IsWeekday = 1
AND dt < GETDATE()
) T
WHERE R = 2
Related
I have data with start date and end date (Say 20th Feb 2018 to 20th Feb 2020), I want to find out the total days in every year inside this range.
For example:
2018 - x days
, 2019 - 365 days
, 2020 - y days etc.
Is there a way I can do in SQL without hardcoding year values?
I tried hardcoding the values and it worked well. But I want a solution without hardcoding year values
I'm not familiar enough with MySql to know if this will port, however here is a tested and confirmed SQL Server solution.
The fiddle link is here for your use.
Given start dates 02/20/2018 and 02/20/2020, the result set is as follows:
Year
periodStart
periodEnd
DaysInPeriod
2018
2018-02-20
2018-12-31
314
2019
2019-01-01
2019-12-31
365
2020
2020-01-01
2020-02-20
51
Declare #StartDate date = '2018-02-20', #EndDate date = '2020-02-20';
WITH x AS (SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n)),
Years AS (
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS Year
FROM x ones, x tens, x hundreds, x thousands)
SELECT Years.Year,
CASE
WHEN Year(#StartDate) = Years.year THEN #StartDate
ELSE DATEFROMPARTS(years.year, 01, 01)
END AS periodStart,
CASE
WHEN Year(#EndDate) = Years.year THEN #EndDate
ELSE DATEFROMPARTS(years.year, 12, 31)
END AS periodEnd,
DATEDIFF(day,
CASE
WHEN Year(#StartDate) = Years.year THEN #StartDate
ELSE DATEFROMPARTS(years.year, 01, 01)
END,
CASE
WHEN Year(#EndDate) = Years.year THEN #EndDate
ELSE DATEFROMPARTS(years.year, 12, 31)
END
) + 1 AS DaysInPeriod
FROM Years
WHERE Years.Year >= Year(#StartDate)
AND Years.Year <= Year(#EndDate)
Using WITH RECURSIVE to create range of dates then we can easly count the number of days for each year using DATEDIFF
WITH RECURSIVE dates AS
(
SELECT min(start_date) as start_date, DATE_FORMAT(min(start_date),'%Y-12-31') as last_day FROM mytable
UNION ALL
SELECT DATE_FORMAT(start_date + INTERVAL 1 YEAR,'%Y-01-01'),
DATE_FORMAT(start_date + INTERVAL 1 YEAR,'%Y-12-31')
FROM dates
WHERE DATE_FORMAT(start_date + INTERVAL 1 YEAR,'%Y-01-01') <= (SELECT MAX(end_date) FROM mytable)
),
cte2 as (
SELECT d.start_date as start_day, if(YEAR(d.start_date) = YEAR(m.end_date), m.end_date, d.last_day) as last_day
FROM dates d, mytable m
)
select *, DATEDIFF(last_day, start_day)+1 as total_days
from cte2;
Demo here
You are looking for the DATEDIFF function.
https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_datediff
DATEDIFF() returns expr1 − expr2 expressed as a value in days from one date to the other. expr1 and expr2 are date or date-and-time expressions.
You are free to specify e.g. "2019-01-01" or "2020-01-01"
as input arguments to DATEDIFF.
You may find it convenient to store several January 1st
dates in a calendar reporting table, if you want SELECT to loop
over several years and report on number of days in each year.
I have a table like this:
I need to sum how many messages were delivered per msisdn in last 8 weeks(but for each week) from date entered. Here is what I came up with:
SELECT count(*) as ukupan_broj, SUM(IF (sent_messages.delivered = 1,1,0 )) as broj_dostavljenih,
count(*) - SUM(IF (sent_messages.delivered = 1,1,0 )) as non_billed,
SUM(IF (sent_messages.delivered = 1,1,0 )) / count(*) as ratio,
`sent_messages`.`msisdn`,
MONTH(`sent_messages`.`datetime`) AS MONTH, WEEK(`sent_messages`.`datetime`) AS WEEK,
DATE_FORMAT(`sent_messages`.`datetime`, '%Y-%m-%d') AS DATE
FROM `sent_messages`
INNER JOIN `received_messages` on `received_messages`.`uniqueid`=`sent_messages`.`originalID`
and `received_messages`.`msisdn`=`sent_messages`.`msisdn`
WHERE `sent_messages`.`datetime` >= '2016-12-12'
AND `sent_messages`.`originalID` = `received_messages`.`uniqueid`
AND `sent_messages`.`datetime` <= '2017-12-30'
AND `sent_messages`.`datetime` >= `received_messages`.`datetime`
AND `sent_messages`.`datetime` <= ( `received_messages`.`datetime` + INTERVAL 2 HOUR )
AND `sent_messages`.`type` = 'PAID'
GROUP BY WEEK
ORDER BY DATE ASC
And because I'm grouping it by WEEK, my result is showing sum of all delivered, undelivered etc. but not per msisdn. Here is how result looks like:
And when I add msisdn in GROUP BY clause I don't get the result the way I need it.
And I need it like this:
Please help me to write optimized query to fetch these results for each msisdn per last 8 weeks, because I'm stuck.
WEEK(...) has a problem near the first of the year. Instead, you could use TO_DAYS:
WHERE datetime > CURDATE() - INTERVAL 8 WEEK -- for the last 8 weeks
GROUP BY MOD(TO_DAYS(datetime), 7) -- group by week
That is quite simple, but there is a bug in it. It only works if today is the last day of a "week". And if date%7 lands on the desired day of week.
WHERE datetime > CURDATE() - INTERVAL 9 WEEK -- for the last 8 weeks
GROUP BY MOD(TO_DAYS(datetime) - 3, 7) -- group by week
Is the first cut at fixing the bugs -- 9-week interval will include the current partial week and the partial week 8 weeks ago. The "- 3" (or whatever number works) will align your "week" to start on Monday or Sunday or whatever.
SUM(IF (sent_messages.delivered = 1,1,0 )) can be shortened to SUM(delivered = 1) or even SUM(delivered) if that column only has 0 or 1 values.
i'm looking to find all records that have a booking date between the previous monday and the next sunday in MySQL.
So far I have:
SELECT firstname
, lastname
, sessions
, (SELECT COUNT(memberid)
FROM bookings
WHERE m.memberid = b.memberid
and b.date between lastMonday and nextSunday) as sessionsused
from members
I'm looking what to substitute into the lastmonday and nextsunday
Any help is much appreciated!
MySQL's YEARWEEK() function selects a unique value for each week that you can use for comparison. It takes a second parameter which specifies whether weeks start on Sunday (0) or Monday (1).
SELECT COUNT(memberid)
FROM bookings
WHERE m.memberid = b.memberid
AND YEARWEEK(b.date, 1) = YEARWEEK(NOW(), 1);
This will always select rows where b.date is in the current week. For a specific week in the past, change NOW() for whatever date expression you require.
For the more generic case where your week does not start on a Sunday or a Monday, you will need some slightly more complicated logic. Here you substitute #weekday with the day on which your weeks begin, 2 = Tues, 3 = Wed, 4 = Thu, 5 = Fri, 6 = Sat.
SELECT COUNT(memberid)
FROM bookings
WHERE m.memberid = b.memberid
AND DATE(b.date)
BETWEEN DATE_SUB(DATE(NOW()), INTERVAL (WEEKDAY(NOW()) - #weekday + 7) % 7 DAY)
AND DATE_ADD(DATE(NOW()), INTERVAL 6 - (WEEKDAY(NOW()) - #weekday + 7) % 7 DAY);
I have data with below format.
id - autoincrement
myDate - timestamp
What I want to extract is get ids of for the last week.
Note By last week, I meant from Sat - Thu and not last 7 days. Considering current date (12-Feb-2013), for me, last week would be 2-Feb-2013 to 8-Feb-2013.
I know you would say, week starts from Sunday, but as I am working in Arab countries, here Friday is OFF and work starts from Saturday.
I know for last 7 days it would be just below code
BETWEEN NOW() and DATE_SUB(NOW(), INTERVAL 7 DAY)
Data at sqlfiddle
Can you try below method to compute the desired result:
SELECT *,WEEKOFYEAR(dt),DAYOFWEEK(dt),DAYNAME(dt), DAYOFWEEK(NOW()),WEEKOFYEAR(NOW())
FROM tab1
WHERE 1
AND
(
(DAYOFWEEK(NOW()) = 1 OR DAYOFWEEK(NOW()) = 6 OR DAYOFWEEK(NOW()) = 7)
AND
WEEKOFYEAR(dt) = WEEKOFYEAR(NOW()) AND DAYOFWEEK(dt) < 6
)
OR
(
(DAYOFWEEK(NOW()) = 2 OR DAYOFWEEK(NOW()) = 3 OR DAYOFWEEK(NOW()) = 4
OR DAYOFWEEK(NOW()) = 5)
AND
(
(
(WEEKOFYEAR(dt) = WEEKOFYEAR(NOW())-2 AND DAYOFWEEK(dt) >= 6)
OR
(WEEKOFYEAR(dt) = WEEKOFYEAR(NOW())-1 AND DAYOFWEEK(dt) > 1 AND DAYOFWEEK(dt) < 6)
)
)
);
I know this is not the smartest way, but based on this you might get hint for better solution.
Demo at sqlfiddle
I have the following query which shows the number of hours billed for each day of the week
SELECT DAYNAME(record_date),
sum(value)
FROM acx_time_records
WHERE
acx_time_records.state = 3
AND record_date >= '2012-10-1' AND record_date <= '2012-10-7'
AND user_id = 7
GROUP BY
dayname(acx_time_records.record_date)
ORDER BY weekday(acx_time_records.record_date)
The above query outputs the following result:
Tuesday 9.75
Friday 1
The issue I have is that I need to plot the values to a graph which has the X-Axis already defined as each day of the week:
Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
Therefore, I'm needing to pass each of the days of the week for the result, instead of just the particular days/hours that show up in the result. I thought about making a join to a new table which has each calendar day of the week and a day index, but I'm not sure how that would be possible. The ideal output would be:
Monday 0
Tuesday 9.75
Wednesday 0
Thursday 0
Friday 1
Saturday 0
Sunday 0
Any tips/suggestions for how I might accomplish this would be greatly appreciated. I know this may also be facilitated by combining MySQL with PHP, but I'm trying to do this in a way that would only require MySQL if at all possible.
The solution with LEFT JOIN.
You can use an additional table or use some SELECTs with UNIONs as newfurniturey suggested. Also I have located WHERE filter inside the joined table.
CREATE TABLE week_days(
week_day_num INT(11) DEFAULT NULL
);
INSERT INTO week_days(week_day_num) VALUES (1),(2),(3),(4),(5),(6),(7);
SELECT
DAYNAME(atrrecord_date),
SUM(atr.value),
COALESCE(SUM(atr.value) , 0)
FROM week_days wd
LEFT JOIN (
SELECT * FROM acx_time_records
WHERE
acx_time_records.state = 3
AND record_date >= '2012-10-1' AND record_date <= '2012-10-7'
AND user_id = 7
) atr
ON wd.week_day_num = DAYOFWEEK(atr.record_date)
GROUP BY
DAYOFWEEK(atr.record_date);
I would actually suggest going with your original idea of joining on another table, but you can make it a simple alias-table with a LEFT JOIN clause:
SELECT
days.day,
IF (value IS NULL, 0, SUM(value))
FROM
(SELECT 'Monday' AS day UNION SELECT 'Tuesday' UNION SELECT 'Wednesday' UNION SELECT 'Thursday' UNION SELECT 'Friday' UNION SELECT 'Saturday' UNION SELECT 'Sunday') AS days
LEFT JOIN (
SELECT * FROM acx_time_records
WHERE
acx_time_records.state = 3
AND record_date >= '2012-10-1' AND record_date <= '2012-10-7'
AND user_id = 7
) AS acx_time_records
ON DAYNAME(record_date) = days.day
GROUP BY
days.day
ORDER BY weekday(acx_time_records.record_date)
This will cause every day of the week to be selected (as an individual row) and the SUM() for each, 0 if no days are defined, will be grouped accordingly.