Finding Fifth Saturdays Mathematically? - mysql

So, where I work, my employees work alternating Saturdays: some employees are assigned to work the 1st and 3rd Saturdays of each month, some are assigned to work the 2nd and 4th Saturdays of each month.
One small problem arises: there are four months in the year which have five Saturdays. Which is easy enough to work around: 1st/3rd Employees work the 1st and 3rd Fifth Saturdays in the year and so on.
Some years have five months with fifth Saturdays, but we're not talking about that right now.
Anyway, to generate schedules for my employees, I first use the following code to generate a list of dates:
select
curdate() - interval (a.a + (10 * b.a) + (100 * c.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
) as a
Then, I select the dates from the subquery, and join them to my Scheduling table to create a list of what each employee works on each date.
All of this works fine until Fifth Saturdays rear their ugly heads. At the moment, I use the following code to mathematically figure out which fifth saturday of the year it is:
mod(((datediff(date_add(a.date, interval (7-dayofweek(a.date)) day),date_add(subdate(a.Date,dayofyear(a.date-1)), interval (1-dayofweek(subdate(a.Date,dayofyear(a.date-1)))) day))+1)/7),4)
Or, to summarize, I find the number of days that have elapsed from the current first Sunday of the year to the current Saturday of this week, divide it by seven to figure out how many Saturdays have passed since then, and then find the modulo of 4 to figure out how many "extra" fifth Saturdays have passed since then.
...and this code will work perfectly fine unless the Fifth Saturday happens to fall on a week which is a multiple of four. Thankfully, that doesn't happen at all this year, but next year I'll need to figure out how to deal with this.
Is there a better way to mathematically figure out which fifth Saturday of the month a given fifth Saturday is?

Oh man. I was way overthinking the SQL piece of this. It was actually pretty easy. Create three temporary tables: one to hold the completed date table, one to hold all of the dates of interest in your period, and a third table specifically to find Fifth Saturdays:
drop temporary table if exists DATES;
create temporary table DATES (AdherenceDates date, WhichSaturday int,
WhichFifthSaturday int);
create temporary table DATES1 (AdherenceDates date, WhichSaturday int);
create temporary table DATES2 (FifthSaturdayDates date, WhichFifthSaturday
int auto_increment, primary key (WhichFifthSaturday));
insert into DATES1 (AdherenceDates, WhichSaturday)
select
a.Date as AdherenceDates,
case
when day(adddate(a.date,(7-dayofweek(a.date)))) <= 7
then 1
when day(adddate(a.date,(7-dayofweek(a.date)))) <= 14
then 2
when day(adddate(a.date,(7-dayofweek(a.date)))) <= 21
then 3
when day(adddate(a.date,(7-dayofweek(a.date)))) <= 28
then 4
when day(adddate(a.date,(7-dayofweek(a.date)))) > 28
then 5
end as WhichSaturday
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
) as a
where dayofweek(a.Date) <> 1
and a.Date >= '2017-01-01'
order by a.Date asc
;
insert into DATES2 (FifthSaturdayDates)
select
a.Date as AdherenceDates
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
) as a
where dayofweek(a.Date) = 7
and a.Date >= '2017-01-01'
and day(a.Date) > 28
order by a.date asc;
insert into DATES (AdherenceDates, WhichSaturday, WhichFifthSaturday)
select
DATES1.AdherenceDates,
DATES1.WhichSaturday,
DATES2.WhichFifthSaturday
from
DATES1
left join
DATES2
on DATES1.AdherenceDates = DATES2.FifthSaturdayDates;
drop temporary tables DATES1, DATES2;
select * from DATES
order by AdherenceDates asc;

Related

Mysql query result generate months(1-12) for each category and arrange in same column order by category

I want to generate months no for 1-12 for each category. There are two categories, DEBIT and CREDIT. So, after finish print 1-12 for DEBIT. Then, it should go to next category and print the same with CREDIT in category column.
SELECT mon, #c:=#c+1 as cat_no, category
FROM
(
SELECT #m:=#m+1 as mon FROM
( 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 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12)a,
(SELECT #m:=0)c
)d,
(SELECT 'DEBIT' as category UNION SELECT 'CREDIT' as category)b,
(SELECT #c:=0)e
The fiddle here.
Result:
The result show the column category display two categories for each month before go to next. But, I expected to output all 1-12 before go to next month.
Expected:
Thank you.
SELECT mon.mon, cat.cat_no, cat.category
FROM ( SELECT 1 mon UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION
SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION
SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12 ) AS mon
CROSS JOIN ( SELECT 1 cat_no, 'DEBIT' category UNION
SELECT 2, 'CREDIT' ) cat
ORDER BY cat_no, mon
https://dbfiddle.uk/?rdbms=mysql_5.6&fiddle=60416e7875ba7eb886e804c0bddbcadb

MySQL include empty WEEKS data as 0

is there way to include empty week value from empty result ? or how i can unionn empty missing weeks
there is bit of my query
SELECT
o.user_id , WEEK(FROM_UNIXTIME(o.cdate, '%Y-%m-%d'),7) as week_number,
FROM
(_orders AS `o`)
WHERE
o.cdate BETWEEN '1505409460' AND '1540815218'
GROUP BY
week_number
Result
1
2
4
6
8
requested result
1
2
3
4
5
6
7
8
This is just an example, there are numerous ways to achieve this. The first step is to have, or generate, a set on integers. Having a table of these is very handy actually. Here I use 2 subqueries cross joined to generate 100 rows (with n = 0 to 99)
select
ns.n, sq.*
from (
select
d1.digit + (d10.digit*10) as n
from (
SELECT 0 AS digit 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
) d1
cross join (
SELECT 0 AS digit 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
) d10
) ns
left join (
your query goes here
) sq on ns.n = sq.week_number
where n between 1 and 52
order by n

MySQL query - convert to left join to show results with zero?

I'm not very good when it comes to using joins - so I have a single table where I'm counting the number of records that meet certain conditions, and returns those counts by week. The problem is, I need the weeks that have a zero count too....I tried to get this to work with a left join, but I'm struggling...any help appreciated: (Stamp is a datetime field)
Query:
SELECT week(stamp), count(*) AS mycount, YEAR(stamp) as theyear
FROM merges
WHERE completed = 1
AND stamp BETWEEN '2017/4/1 00:00:00' AND '2017/6/1 00:00:00' GROUP BY week(stamp)
This returns:
week(stamp) | mycount | theyear
15 | 21 |2017
17 | 10 |2017
18 | 62 |2017
19 | 13 |2017
20 | 76 |2017
21 | 22 |2017
Notice week 16 is missing? I need to have this result included in the above, like:
16 | 0 |2017
I appreciate any help - I know this isn't too difficult, but I'm pulling my hair out trying to understand how to do this while I read other posts....
select weekValue, yearValue, coalesce(mycount,0)
from
( SELECT distinct week(#startDate := #startDate + Interval 1 day) as weekValue,
year(#startDate := #startDate + Interval 1 day) as yearValue
FROM
(select 0 union all select 1 union all select 3 union all select 4
union all select 5 union all select 6 union all select 6 union all select 7
union all select 8 union all select 9) t,
(select 0 union all select 1 union all select 3
union all select 4 union all select 5 union all select 6
union all select 6 union all select 7 union all select 8 union all select 9) t2,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t4,
(SELECT #startDate := '2017-03-31 00:00:00' ) as g
where
#startDate < '2017-06-01 00:00:00' ) as generateWeekYear left join
(SELECT week(stamp) as theweek, count(*) AS mycount, YEAR(stamp) as theyear
FROM merges
WHERE completed = 1
AND stamp BETWEEN '2017/4/1 00:00:00' AND '2017/6/1 00:00:00' GROUP BY week(stamp) ) as actualQuery
on generateWeekYear.weekValue = actualQuery.theweek
and generateWeekYear.yearValue = actualQuery.theyear
Let me explain the above query,
Sub Query generateWeekYear = This is used to genearate distinct week and year based on two inputs
lets say startDate and endDate. startDate should be 1 day less to actual startDate. Because if you do not
subtract 1 day then there might chance to loose one week.
Now you have all week and year which needs to be displayed.
Now you are thinking generateWeekYear is going to be more time to execute but this is not case. You can
check this generate an integer sequence in MySQL.
After that you simply join your table with above table and you can get your required result.

mysql select date day by day

I have table shown below :
login
date user
2016-11-23 1
2016-11-23 2
2016-11-23 3
2016-11-25 2
2016-11-25 5
2016-11-27 1
from above table what I want to get is like this:
date count(*)
2016-11-21 0
2016-11-22 0
2016-11-23 3
2016-11-24 0
2016-11-25 2
2016-11-26 0
2016-11-27 1
But, because there are only dates 2016-11-23 and 2016-11-25 and 2016-11-27, when I query like this :
select date, count(*)
from login
where date between (current_date()-interval 7 day) and current_date()
group by date
order by date asc
It can't get result like what I really want to get. Is that result possible from my login table?
One way is to generate all days before JOIN
select GenDate, count(Date)
from login
right join
(select a.GenDate
from (
select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as GenDate
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
) a
where a.GenDate between (current_date()-interval 7 day) and current_date())x
ON x.GenDate=login.Date
group by GenDate
order by GenDate asc
Use a derived table with the wanted dates :
SELECT t.date, count(s.date)
FROM (SELECT '2016-11-21' as `date` UNION ALL
SELECT '2016-11-22' as `date` UNION ALL
...) t
LEFT JOIN login s
ON(t.date = s.date)
WHERE
t.date between (current_date()-interval 7 day) and current_date()
GROUP BY t.date
ORDER BY t.date
This is a very well known problem in programming. There are several solutions.
Go over the result with PHP, and fill the missing days in the resulting array.
AS sagi proposed, create a separate table that contains all the dates in the range of days your application works with, then you can JOIN that table with your query. One of the issues is that from time to time you have to add more days to this table, if you suddenly have missing days in future or in past.

MySQL group by month and day for current year

I was wondering if this is possible:
I have some data where i have an datetime field. Now i want to make an sql query where i can make groups by month and in each month by day.
Something like this:
Month day COUNT(*)
1 1 200
1 2 300
1 3 500
2 1 600
2 2 0
Why i need this? I need to make an sql query to make an chart XY and show fill this requeriments:
SELECT series,value1,value2 FROM...WHERE...GROUP BY...ORDER BY.
So i want to make each month to be an SERIE, and then each day is value1, and the count value 2
Hope everyone understand my bot question...
Best Regards and tks in advanced
Is this all you're looking for?
SELECT MONTH(m), DAY(d), COUNT(*)
FROM sparkles
WHERE YEAR(y) = 2013
GROUP BY MONTH(m), DAY(d)
If your dates have gaps, you will need to use a date lookup table.
Use the MONTH() and DAYOFMONTH() functions.
Here is the documentation: dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html
Try this:
SELECT Month, Day, Count(*) FROM yout_table group by Month, Day
First you are going to need a table that holds every day this year:
CREATE TABLE DaysThisYear
(
dt datetime not null,
mm int, dd int,
primary key (dt)
);
INSERT INTO DaysThisYear (dt,mm,dd)
SELECT ymd,MONTH(ymd),DAY(ymd) FROM
(SELECT IFNULL(ymd + INTERVAL 0 SECOND,'1980-01-01 00:00:00') ymd
FROM (SELECT CONCAT(yy,'-',SUBSTR(mm+100,2),'-',SUBSTR(dd+100,2)) ymd,yy
FROM (SELECT 1 dd UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8
UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12
UNION SELECT 13 UNION SELECT 14 UNION SELECT 15 UNION SELECT 16
UNION SELECT 17 UNION SELECT 18 UNION SELECT 19 UNION SELECT 20
UNION SELECT 21 UNION SELECT 22 UNION SELECT 23 UNION SELECT 24
UNION SELECT 25 UNION SELECT 26 UNION SELECT 27 UNION SELECT 28
UNION SELECT 29 UNION SELECT 30 UNION SELECT 31 UNION SELECT 32) AAA,
(SELECT YEAR(NOW()) yy,mm FROM
(SELECT 1 mm UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5
UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION
SELECT 10 UNION SELECT 11 UNION SELECT 12) M) BBB) AA) A
WHERE YEAR(ymd) <> 1980
ORDER BY ymd;
To see that every day for this year was loaded, run this:
SELECT * FROM DaysThisYear;
Now, if you have a table with a DATETIME column, you can join the DaysThisYear table to it
For example, lets say your table looks like this:
CREATE TABLE mydata
(
id int not null auto_increment,
dt DATETIME,
.
.
.
PRIMARY KEY (id),
KEY dt (dt)
);
You could perform something like this:
SELECT A.mm,A.dd,SUM(IF(ISNULL(B.mm),0,1)) mmdd_count
FROM DaysThisYear A LEFT JOIN
(SELECT MONTH(dt) mm,DAY(dy) dd FROM mydata) B
ON A.mm=B.mm AND A.dd=B.dd;
Give it a Try !!!