Php/MySQL: Hours reported, hourly rate - php

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.

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

MySql - SUM of workers per hour

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.

Mysql - Voting once per Day

I have an entry where users can vote once per day. I''m saving this in my database.
Now I need to check, if the user is allowed to vote on this entry again (after one day).
So far I got this:
SELECT count(*)
FROM entries e
WHERE e.voterID =1
AND e.pID =1
AND e.date < NOW( ) - INTERVAL 1
DAY
But this doesn't work to well, when in the DB there are more entries for the voter and pid. A voter can vote multiples times for the same entry.
if there are more entries for the same user and the same projects, count(*) fives me a value more then 1. etc.
How do I check if the user is allowed to vote again properly?
Thanks.
Did the present user vote for the present pID in the most recent 24 hours? If so, the votecount result in this query will be more than zero.
SELECT count(*) AS votecount
FROM entries AS e
WHERE e.voterID = 1
AND e.pID = 1
AND e.date >= NOW() - INTERVAL 1 DAY
Notice the >= comparison for the date. Your sample code says <.
You will need a compound index on (voterID, pID, date) when you need this query to run efficiently on a large table.

Minus one date from another in MySQL request?

I have table with start_date and end_date. I need to find the duration (end_date-start_date). Can someone suggest how I can do so in the query? Will I get a new variable with this somehow like duration=end_date-start_date in the query?
EDIT
If I use mminus it gives me:
2012-07-01 minus 2012-01-01 = 600
How can it be 600 days in 6 months as 2011-07-27 - 2011-07-06 = 21? So i assume it's days?
Is there function to get actually how many months it even if date is in middle of month.
e.g. like "3rd june" and "27 july" is 2 month
use PERIOD_DIFF
Returns the number of months between periods P1 and P2. P1 and P2 should be in the format YYMM or YYYYMM. Note that the period arguments P1 and P2 are not date values. so try
SELECT PERIOD_DIFF(
DATE_FORMAT('2011-07-27','%Y%m'),
DATE_FORMAT('2011-06-03','%Y%m')
) AS durationInMonths
If you want to know DateTime different, you can use TimeDiff
select
(Hour(Duration) / 24)/365 as Year,
(Hour(Duration) / 24)%365 as Day,
(Hour(Duration) % 24) as Hours,
MINUTE(Duration) as Minutes,
SECOND(Duration) as Seconds
from
(
SELECT ADDTIME(NOW(),'1000:27:50') as end_datetime, NOW() as start_datetime,
TIMEDIFF(ADDTIME(NOW(),'1000:27:50'), NOW()) AS Duration
) x;
If you want to know Date different, you can use DateDiff()
select DATEDIFF('2011-06-06','2011-05-01');
You can use * but to get duration, you need to add extra column after it
select *, DATEDIFF(updated_at, created_at) from users;
TO have the best practice, it is better naming u for user, duration for extra column.
So you can see Duration title at the top. It is good if you export your sql procedure to excel.
select u.*, DATEDIFF(updated_at, created_at) as duration from users u;
Also from your php or rails code, you can call that given variable name. In rails,
users =User.find_by_sql("select u.*, DATEDIFF(updated_at, created_at) as duration from users u")
users.first.duration
SELECT end_date - start_date AS duration FROM table ...

Categories