MySql - SUM of workers per hour - php

With a little (lots) help from StackOverflow last year, I have a function that will show how many employees that are currently at work.
The code works fine - but now we have implemented night shifts and the original query only shows shifts that begin in the current date. People working nightshifts might check-in at 10PM and the leave at 8AM - in the current query, they don't show up, because startdate is the day before...
I have been trying to implement enddate in the query, but with no luck.
teamslots
---------
id
startdate
starttime
enddate
endtime
teamslot_schedule
-----------------
id
slotid (joins to is in teamslots)
userid
shifthours
----------
thehour
This is the original query - I could really use some help that would make the query include employees, that are on a shift that begins the day before, but ends "today".
SELECT
DATE_FORMAT(d.startdate + INTERVAL s.thehour HOUR, '%Y-%m-%d %H') AS date,
COUNT(DISTINCT ts.userid) AS users
FROM
shifthours s
JOIN
(SELECT DISTINCT startdate FROM teamslots) d
LEFT JOIN
teamslots t ON t.startdate = d.startdate AND
s.thehour BETWEEN HOUR(t.starttime) AND HOUR(t.endtime)
LEFT JOIN
teamslot_schedule ts ON ts.slotid = t.id
GROUP BY
d.startdate,
s.thehour
ORDER BY
d.startdate + INTERVAL s.thehour HOUR;
Best regards,
Mark

Use the MySQL CURDATE() function and subtract the appropriate interval for your shift length.

Related

check no dates are missing in table mysql one row for each date [duplicate]

I need some help with a mysql query. I've got db table that has data from Jan 1, 2011 thru April 30, 2011. There should be a record for each date. I need to find out whether any date is missing from the table.
So for example, let's say that Feb 2, 2011 has no data. How do I find that date?
I've got the dates stored in a column called reportdatetime. The dates are stored in the format: 2011-05-10 0:00:00, which is May 5, 2011 12:00:00 am.
Any suggestions?
This is a second answer, I'll post it separately.
SELECT DATE(r1.reportdate) + INTERVAL 1 DAY AS missing_date
FROM Reports r1
LEFT OUTER JOIN Reports r2 ON DATE(r1.reportdate) = DATE(r2.reportdate) - INTERVAL 1 DAY
WHERE r1.reportdate BETWEEN '2011-01-01' AND '2011-04-30' AND r2.reportdate IS NULL;
This is a self-join that reports a date such that no row exists with the date following.
This will find the first day in a gap, but if there are runs of multiple days missing it won't report all the dates in the gap.
CREATE TABLE Days (day DATE PRIMARY KEY);
Fill Days with all the days you're looking for.
mysql> INSERT INTO Days VALUES ('2011-01-01');
mysql> SET #offset := 1;
mysql> INSERT INTO Days SELECT day + INTERVAL #offset DAY FROM Days; SET #offset := #offset * 2;
Then up-arrow and repeat the INSERT as many times as needed. It doubles the number of rows each time, so you can get four month's worth of rows in seven INSERTs.
Do an exclusion join to find the dates for which there is no match in your reports table:
SELECT d.day FROM Days d
LEFT OUTER JOIN Reports r ON d.day = DATE(r.reportdatetime)
WHERE d.day BETWEEN '2011-01-01' AND '2011-04-30'
AND r.reportdatetime IS NULL;`
It could be done with a more complicated single query, but I'll show a pseudo code with temp table just for illustration:
Get all dates for which we have records:
CREATE TEMP TABLE AllUsedDates
SELECT DISTINCT reportdatetime
INTO AllUsedDates;
now add May 1st so we track 04-30
INSERT INTO AllUsedData ('2011-05-01')
If there's no "next day", we found a gap:
SELECT A.NEXT_DAY
FROM
(SELECT reportdatetime AS TODAY, DATEADD(reportdatetime, 1) AS NEXT_DAY FROM AllUsed Dates) AS A
WHERE
(A.NEXT_DATE NOT IN (SELECT reportdatetime FROM AllUsedDates)
AND
A.TODAY <> '2011-05-01') --exclude the last day
If you mean reportdatetime has the entry of "Feb 2, 2011" but other fields associated to that date are not present like below table snap
reportdate col1 col2
5/10/2011 abc xyz
2/2/2011
1/1/2011 bnv oda
then this query works fine
select reportdate from dtdiff where reportdate not in (select df1.reportdate from dtdiff df1, dtdiff df2 where df1.col1 = df2.col1)
Try this
SELECT DATE(t1.datefield) + INTERVAL 1 DAY AS missing_date FROM table t1 LEFT OUTER JOIN table t2 ON DATE(t1.datefield) = DATE(t2.datefield) - INTERVAL 1 DAY WHERE DATE(t1.datefield) BETWEEN '2020-01-01' AND '2020-01-31' AND DATE(t2.datefield) IS NULL;
If you want to get missing dates in a datetime field use this.
SELECT CAST(t1.datetime_field as DATE) + INTERVAL 1 DAY AS missing_date FROM table t1 LEFT OUTER JOIN table t2 ON CAST(t1.datetime_field as DATE) = CAST(t2.datetime_field as DATE) - INTERVAL 1 DAY WHERE CAST(t1.datetime_field as DATE) BETWEEN '2020-01-01' AND '2020-07-31' AND CAST(t2.datetime_field as DATE) IS NULL;
The solutions above seem to work, but they seem EXTREMELY slow (taking possibly hours, I waited for 30 min only) at least in my database.
This clause takes less than a second in same database (of course you need to repeat it manually dozen times and possibly change function names to find the actual dates). pvm = my datetime, WEATHER = my table.
mysql> select year(pvm) as _year,count(distinct(date(pvm))) as _days from WEATHER where year(pvm)>=2000 and month(pvm)=1 group by _year order by _year asc;
--ako

Show all dates even those with zero data in it in mySQL?

My following query shows the date and the count of the emails found on each day (last 2 days)
My problem is that if no emails are found today, the today date will not be displayed on the output. (if yesterday has emails, it will show only 1 row with yesterday date and email).
How can I edit my query to always show 2 rows, today and yesterday, date and number of emails even zero?
SELECT maildate,
COUNT(*) AS totalEmails
FROM emails
WHERE maildate >= Date_add(Curdate(), interval - 2 DAY)
AND company_id = 1
GROUP BY DATE(maildate)
ORDER BY maildate desc
There are many tricks to creating a list of dates (or numeric sequences similarly). The one I like to use with MySQL is using #sqlvariables. I will typically start with a baseline value such as your date -2 days. I will do a cross-join to any other table in the database that has at least as many records as you expect in your output... Say 30 days, or a whole year 366 days, or longer. The inner sql variable prep will keep increasing itself by whatever increment (you could even do date ranges such as begin/end of a week, month, etc). Now you have your table of all possible dates you are looking to fill.
Now, I do a secondary query by the value -- in this case your email date and apply the group by. Using the where clause in this query will make IT faster since it can utilize the date on its query result set before returning for the LEFT-JOIN to the date range result set.
Now, your simple left-join gets both parts of all dates to be included and those corresponding counts that do exist.
Note the table alias "AnyTableWithAtLeast3RecordInIt" in the "JustDates" query could in-fact be your "emails" table. Since we don't care about any criteria except a record exists, and we are applying a limit of 30 days in my example, it will be instantaneous.
select
JustDates.DateToInclude,
coalesce( SumCnts.TotalEmails, 0 ) TotalEmails
from
( select
#myDate := DATE_ADD( #myDate, INTERVAL 1 DAY ) as DateToInclude
from
( select #myDate := Date_add(Curdate(), interval - 2 DAY) ) as SQLVars,
AnyTableWithAtLeast3RecordInIt
limit 30 ) JustDates
left join
( select
maildate,
COUNT(*) AS totalEmails
FROM
emails
WHERE
maildate >= Date_add(Curdate(), interval - 2 DAY)
AND company_id = 1
GROUP BY
DATE(maildate) ) SumCnts
ON JustDates.DateToInclude = SumCnts.MailDate
Now, judging by your query, but unclarified request... Your emails table CAN HAVE FUTURE DATES? Is that correct? Such as a Dr. Office and appointments are for the future and you want to get emails out for a given range. This is what I was inferring and hence had my limit to only go out 30 days... If you need longer, just extend the LIMIT clause.
You need a table that contains all dates in the needed range. If its only about today and yesterday, you can easily create it as a subquery (derived table).
SELECT Curdate() as maildate
UNION ALL
SELECT Curdate() - INTERVAL 1 DAY
http://rextester.com/ALH50651
Now you can LEFT JOIN your table and count the rows:
SELECT sub.maildate,
COUNT(m.maildate) AS totalEmails
FROM (
SELECT Curdate() as maildate
UNION ALL
SELECT Curdate() - INTERVAL 1 DAY
) sub
LEFT JOIN emails m
ON DATE(m.maildate) = sub.maildate
AND m.company_id = 1
GROUP BY sub.maildate
ORDER BY sub.maildate desc

Extract records for 1 Week Time interval and Them start the next timer when the new week starts

I am using the query given below to extract the records for 1 Week Time interval.
mysql_query("SELECT t1.username, SUM(t2.points) AS total
FROM actcontest t2 JOIN user t1 ON t1.userid = t2.userid WHERE t2.date
BETWEEN (CURDATE() - INTERVAL 1 WEEK) AND CURDATE() GROUP BY t1.username ORDER BY
total DESC LIMIT 20");
Once the records are fetched I am using the different query to award bonuses to the top 3 users.
This works fine.
Problem:
Once bonus has been awarded to the last week, top 3 members, new dates shall start from the new week starting dates, where last week ended.
suppose: we started the timer on date: [dd/mm/yy]
Week 1 = {01-01-2010 to 07/01/2010}
Week 2 = {07-01-2010 to 14/01/2010}
Week 3 = {14-01-2010 to 21/01/2010}
Week 4 = {21-01-2010 to 28/01/2010}
Can you please help me in building this logic for my script.
I am using php and Mysql.
What I want to Do:
One i have awarded bonus to week one winners. I want timer to start fetching the members for the next week only, It should not include the previous dates .
I hope i clarified the question.
Consider something like this:
SET #weekNo := 4;
SET #dateStart := '2014-01-01';
SELECT ADDDATE(CONCAT(#dateStart, ' 00:00:00'), INTERVAL 7*(#weekNo-1) DAY),
ADDDATE(CONCAT(#dateStart, ' 23:59:59'), INTERVAL 7*#weekNo-1 DAY);
You can check the above script in sql. You can change the 2 variables at the top: #weekNo is the week you need to compute the date interval since the start date. And the #dateStart is the date the timer started.
In your php code you can compute the date by replacing the dateStart and weekNo with php variables.
select field1,field2
from table_name
where date_sub( field_date , INTERVAL 7 day) <= now()
see this you will get hint: https://stackoverflow.com/questions/26358240/show-data-from-the-last-7-days/26358454#26358454

PHP/MySQL Select the day before and day after a range from the same table

I have two tables, one called check_ins and another called holidays.
check_ins has a datetime_start and datetime_end columns (in addition to other stuff that isn't needed for this question). The holidays table has a date range of two columns for the start and end of the holiday.
I need to figure out who was in the day prior to and the day directly after the holiday range to determine who gets holiday pay. In other words, I need only results from the table that the same employee was in one day before and one day after, ignoring the rest.
I've been racking my brain all day trying to figure out a way to do this and have found nothing. Am I barking up the wrong tree here? Should I do this via PHP?
Thanks!
Edit: this is what I used and though I had it until I realized that their might be multiple check-ins in a single day:
SELECT DISTINCT count(check_ins.Employee_ID), check_ins.ShiftStart_Datetime, check_ins.ShiftEnd_Datetime, holidays.* FROM check_ins, holidays WHERE holidays.ID = 2 AND DATE(DATE_ADD(Datefrom, INTERVAL -1 DAY)) = DATE(ShiftStart_Datetime) GROUP BY Employee_ID HAVING count(check_ins.Employee_ID) >1 UNION SELECT DISTINCT check_ins.Employee_ID, check_ins.ShiftStart_Datetime, check_ins.ShiftEnd_Datetime, holidays.* FROM check_ins, holidays WHERE DATE(DATE_SUB(Dateto, INTERVAL -1 DAY)) = DATE(ShiftStart_Datetime) GROUP BY Employee_ID HAVING count(check_ins.Employee_ID) >1
You can (inner) join the check_ins table twice. Once for the day before the start of the holiday and once for the day after.
If the datetime_start and datetime_end may have different dates, you need to use BETWEEN. Cast both of them to a date instead of a datetime, since you don't care about the time.
Add a GROUP BY in case the employee has multiple check ins on one day.
SELECT holidays.id AS holiday_id, ci_before.employee_id FROM holidays
INNER JOIN check_ins ci_before ON holidays.holiday_start - INTERVAL 1 DAY
BETWEEN DATE(ci_before.datetime_start) AND DATE(ci_before.datetime_end)
INNER JOIN check_ins ci_after ON holidays.holiday_end + INTERVAL 1 DAY
BETWEEN DATE(ci_after.datetime_start) AND DATE(ci_after.datetime_end)
AND ci_before.employee_id = ci_after.employee_id
GROUP BY ci_before.employee_id
See the SQL fiddle here
The used tables are
CREATE TABLE `holidays` (id int, holiday_start date, holiday_end date);
CREATE TABLE `check_ins` (employee_id int, datetime_start datetime, datetime_end datetime);
holidays.id is an ID for a holiday, not an employee.
you can do it via php in 2 queries, but why would you... this'll be fun!
SELECT * FROM check_ins
LEFT JOIN holidays AS holi_before
ON (
datediff(holi_before.datetime_end,check_ins.datetime_end) <= 1
)
LEFT JOIN holidays AS holi_after
ON (
holi_before.id = holi_after.id
AND datediff(holi_after.datetime_end,check_ins.datetime_end) >= 1
)
That should about do it (might need to tweak the datediff comparison a bit to get it exactly right.)
haven't test it, but i hope it gives you the right push :-)

Php/MySQL: Hours reported, hourly rate

I have the following tables (simplified):
hours hour_rates
- user_id - user_id
- date - date
- hours - hourly_rate
Hours table example:
1 - 2012-03-19 - 8
This means that user with id=1, at 2012-03-19 worked 8 hours in total.
The hourly rate for a person can change in time, so I have the second table:
hour_rates table Example
1 - 2011-12-01 - 20
1 - 2011-12-20 - 25
So for user with id=1, we set a hourly rate of 20$ at 2011-12-01.
We changed his hourly rate at 2011-12-20, to 25$.
What I want is, to calculate how much I have to pay for a given user (ex. id=1) for a given period (ex. 2012-01-01 -> 2012-02-01).
Can I calculate this simply mysql side?
If not, how to do it in an efficient way?
In hour_rates table you should have two dates: start_date and end_date this means from start_date to end_date the emplyer has been paid x$ per hour.
Then use the same query proposed by bpgergo modified like this:
select sum(h.hours * hr.hourly_rate) as pay
from hours h, hour_rates hr
where h.user_id = :user_id --here you will set the user id parameter
and h.user_id = hr.user_id and (h.date BETWEEN hr.start_date AND hr.end_date
and h.date between STR_TO_DATE('01,1,2012','%d,%m,%Y') and STR_TO_DATE('01,1,2011','%d,%m,%Y')
Can I calculate this simply mysql side?
Yes, this is the SQL
select sum(outer_h.hours *
(select inner_hr.hourly_rate
from hour_rates inner_hr
where inner_hr.user_id = outer_h.user_id
and inner_hr.date >= outer_h.date
order by inner_hr.date asc
limit 1)
) as pay
from hours outer_h
where outer_h.user_id = :user_id --here you will set the user id parameter
and outer_h.date between STR_TO_DATE('01,1,2012','%d,%m,%Y') and STR_TO_DATE('01,1,2011','%d,%m,%Y')
EDIT: So, sorry but what do you need 2 tables for that? They are totally equal, if every day can have its own hourly_rate, you only need to store it like this:
hours: user_id, date, hours, hourly_rate
e.g. 1 | 2012-03-19 | 8 | 25
You use
SELECT user_id, date, hours, hourly_rate FROM hours WHERE (user_id=$var_of_user_id AND date ...)
and then multiply for each row hours*hourly_rate and add it to $sum, e.g.
while {...
$sum=$sum+($row['hours']*$row['hourly_rate']);
}
what does the hourly_rate depend on? probably you don't want to have the field date in the hour_rates table.
if hourly_rate is different for each job, you want to have only one table with user_id, start_date, end_date (or hours worked) and hourly_rate.
if hourly_rate depends only on user_id, then you want to have two tables:
hours: user_id, start_date, end_date (or hours worked)
hourly_rate: user_id, hourly_rate
and join the tables on user_id. If you already have a table users, you could store the hourly_rate there too, in the second case.
Then use php to simply multiply the hours worked with the hourly rate where user_id... etc.
This query should work:
SELECT SUM( h.hours*COALESCE(hr.hourly_rate,0) ) AS salary
FROM hours h
LEFT JOIN hour_rates hr
ON h.user_id = hr.user_id
AND hr.date = ( SELECT MAX(hd.date) FROM hour_rates hd
WHERE hd.user_id = h.user_id AND hd.date <= h.date )
WHERE h.user_id = 1
AND h.date BETWEEN '2012-01-01' AND '2012-04-01'
The coalesce part is there just in case the hourly pay is not defined for a given user and date, you can put a default rate there instead of 0. Also make sure that every pair (user_id,date) in your hour_rates table is unique.

Categories