Reputation: 65
I am creating a clock-in / clock-out system for employees.
There is a tbl_clockins which contains records of each clock-in/clock-out session with information on whether each session is paid, how late the employee was for that session or how much overtime they did, etc.
There is another table called tbl_user_work_settings where the manager can set which days employees are on holiday, or have taken off on sickness etc.
I am creating some reports where I need totals for each employee, e.g. total days taken as holiday by each employee wihin a given date range. I have a very long query which actually gets all the required information, but it is huge and somewhat inefficient. Is there any way to make it smaller/more efficient? Any help is appreciated.
// get total days worked, unpaid days, bank holidays, holidays, sicknesses
// and absences within given date range for given users
$sql = "SELECT us.username, daysWorked, secondsWorked,
unpaidDays, bankHolidays, holidays, sicknesses, absences
FROM
(SELECT username FROM users WHERE clockin_valid='1') us
LEFT JOIN (
SELECT username, selectedDate, count(isUnpaid) AS unpaidDays
FROM tbl_user_work_settings
WHERE isUnpaid = '1'
AND selectedDate>='$startDate'
AND selectedDate<='$endDate'
GROUP BY username
) u ON us.username=u.username
LEFT JOIN (
SELECT username, count(isBankHoliday) AS bankHolidays
FROM tbl_user_work_settings
WHERE isBankHoliday='1'
AND selectedDate>='$startDate'
AND selectedDate<='$endDate'
GROUP BY username
) bh ON us.username=bh.username
LEFT JOIN (
SELECT username, count(isHoliday) AS holidays
FROM tbl_user_work_settings
WHERE isHoliday='1'
AND selectedDate>='$startDate'
AND selectedDate<='$endDate'
GROUP BY username
) h ON us.username=h.username
LEFT JOIN (
SELECT username, count(isSickness) AS sicknesses
FROM tbl_user_work_settings
WHERE isSickness='1'
AND selectedDate>='$startDate'
AND selectedDate<='$endDate'
GROUP BY username
) s ON us.username=s.username
LEFT JOIN (
SELECT username, count(isOtherAbsence) AS absences
FROM tbl_user_work_settings
WHERE isOtherAbsence='1'
AND selectedDate>='$startDate'
AND selectedDate<='$endDate'
GROUP BY username
) a ON us.username=a.username
LEFT JOIN (
SELECT username, count(DISTINCT DATE(in_time)) AS daysWorked,
SUM(seconds_duration) AS secondsWorked
FROM tbl_clockins
WHERE DATE(in_time)>='$startDate'
AND DATE(in_time)<='$endDate'
GROUP BY username
) dw ON us.username=dw.username";
if(count($selectedUsers)>0)
$sql .= " WHERE (us.username='"
. implode("' OR us.username='", $selectedUsers)."')";
$sql .= " ORDER BY us.username ASC";
Upvotes: 2
Views: 266
Reputation: 125855
You can use SUM(condition)
on a single use of the tbl_user_work_settings
table:
// get total days worked, unpaid days, bank holidays, holidays, sicknesses
// and absences within given date range for given users
$sql = "
SELECT users.username,
SUM(ws.isUnpaid ='1') AS unpaidDays,
SUM(ws.isBankHoliday ='1') AS bankHolidays,
SUM(ws.isHoliday ='1') AS holidays,
SUM(ws.isSickness ='1') AS sicknesses,
SUM(ws.isOtherAbsence='1') AS absences,
COUNT(DISTINCT DATE(cl.in_time)) AS daysWorked,
SUM(cl.seconds_duration) AS secondsWorked
FROM users
LEFT JOIN tbl_user_work_settings AS ws
ON ws.username = users.username
AND ws.selectedDate BETWEEN '$startDate' AND '$endDate'
LEFT JOIN tbl_clockins AS cl
ON cl.username = users.username
AND DATE(cl.in_time) BETWEEN '$startDate' AND '$endDate'
WHERE users.clockin_valid='1'";
if(count($selectedUsers)>0) $sql .= "
AND users.username IN ('" . implode("','", $selectedUsers) . "')";
$sql .= "
GROUP BY users.username
ORDER BY users.username ASC";
By the way (and perhaps more for the benefit of other readers), I really hope that you are avoiding SQL injection attacks by properly escaping your PHP variables before inserting them into your SQL. Ideally, you shouldn't do that at all, but instead pass such variables to MySQL as the parameters of a prepared statement (which don't get evaluated for SQL): read more about Bobby Tables.
Also, as an aside, why are you handling integer types as strings (by enclosing them in single quote characters)? That's needless and a waste of resource in MySQL having to perform unnecessary type conversion. Indeed, if the various isUnpaid
etc. columns are all 0
/1
, you can change the above to remove the equality test and just use SUM(ws.isUnpaid)
etc. directly.
Upvotes: 2
Reputation: 526
Put each table that would join in a temp table... then create indexes on joinable fields of temp tables... and make your query with temp tables.
Example:
SELECT username, selectedDate, count(isUnpaid) AS unpaidDays
INTO #TempTable1
FROM tbl_user_work_settings
WHERE isUnpaid = '1'
AND selectedDate>='$startDate'
AND selectedDate<='$endDate'
GROUP BY username
create clustered index ix1 on #TempTable1 (username)
Upvotes: 0