Print

MySQL: Display Users and Duration in Matrix Timesheet

Given Data
Activities by employees with start dates and end dates in a mySQL database.

Objective
copyraw
User		Mon	Tue	Wed	Thu	Fri	Sat	Sun	Total
--------------- ------- ------- ------- ------- ------- ------- ------- ---------
Me		09:00	07:30	08:00	07:00	06:00	00:00	02:00   36:00
Myself		07:30	07:30	07:30	07:30	07:00	00:00	00:00   37:00
I		03:45	03:30	03:00	03:30	00:00	00:00	00:00   23:00
  1.  User        Mon    Tue    Wed    Thu    Fri    Sat    Sun    Total 
  2.  --------------- ------- ------- ------- ------- ------- ------- ------- --------- 
  3.  Me        09:00    07:30    08:00    07:00    06:00    00:00    02:00   36:00 
  4.  Myself        07:30    07:30    07:30    07:30    07:00    00:00    00:00   37:00 
  5.  I        03:45    03:30    03:00    03:30    00:00    00:00    00:00   23:00 

Method
copyraw
1. Generate query to list all valid users
2. Generate query which will total hours per week per user
3. Combine in a loop to run query against each user.
  1.  1. Generate query to list all valid users 
  2.  2. Generate query which will total hours per week per user 
  3.  3. Combine in a loop to run query against each user. 

Post-Notes (Update 2012)


The performance of the previous version of this query was pretty abysmal and worse if you want to list users who have no associated activities (outer join but all-in-one query). The DBA responsible for the system asked me to look at ways of reducing the load on the server and the solution was to separate both tables (activities and users) rather than trying to join these, and to let PHP do a bit more of the work:

User Query
copyraw
$t_value = $the_team_id_that_i_want_to_look_at;

$user_list_query="
SELECT DISTINCT
	u.UserID,
	CONCAT(u.FirstName, ' ', u.LastName) AS 'Fullname'
FROM 
	Users u
WHERE
	u.StatusType NOT IN ('expired','guest','test account')
AND 
	u.TeamID='$t_value'
ORDER BY 
	CONCAT(u.FirstName, ' ', u.LastName)
";
  1.  $t_value = $the_team_id_that_i_want_to_look_at
  2.   
  3.  $user_list_query=" 
  4.  SELECT DISTINCT 
  5.      u.UserID, 
  6.      CONCAT(u.FirstName, ' ', u.LastName) AS 'Fullname' 
  7.  FROM 
  8.      Users u 
  9.  WHERE 
  10.      u.StatusType NOT IN ('expired','guest','test account') 
  11.  AND 
  12.      u.TeamID='$t_value' 
  13.  ORDER BY 
  14.      CONCAT(u.FirstName, ' ', u.LastName) 
  15.  "

Activity Query
copyraw
$w_value = $number_of_weeks_from_now; 
// usually 0 for this week, 1 for the week before, 2 for the week before that...

$timesheet_query_per_user="
SELECT DISTINCT
	s.StaffUserID AS 'UserID',
	(
		SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed
		FROM StaffActivities s
		WHERE WEEKDAY(s.DateTimeCreated)=0
		AND YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value
		AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION'
	) AS 'Monday',
	(
		SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed
		FROM StaffActivities s
		WHERE WEEKDAY(s.DateTimeCreated)=1
		AND YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value
		AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION'
	) AS 'Tuesday',
	(
		SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed
		FROM StaffActivities s
		WHERE WEEKDAY(s.DateTimeCreated)=2
		AND YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value
		AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION'
	) AS 'Wednesday',
	(
		SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed
		FROM StaffActivities s
		WHERE WEEKDAY(s.DateTimeCreated)=3
		AND YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value
		AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION'
	) AS 'Thursday',
	(
		SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed
		FROM StaffActivities s
		WHERE WEEKDAY(s.DateTimeCreated)=4
		AND YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value
		AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION'
	) AS 'Friday',
	(
		SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed
		FROM StaffActivities s
		WHERE WEEKDAY(s.DateTimeCreated)=5
		AND YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value
		AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION'
	) AS 'Saturday',
	(
		SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed
		FROM StaffActivities s
		WHERE WEEKDAY(s.DateTimeCreated)=6
		AND YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value
		AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION'
	) AS 'Sunday',
	(
		SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed
		FROM StaffActivities s
		WHERE YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value
		AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION'
	) AS 'Total',
	(
		SELECT ROUND((SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))/26640),2) AS SecondsElapsed
		FROM StaffActivities s
		WHERE YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value
		AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION'
	) AS 'Total Days'
FROM StaffActivities s
WHERE s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION'
";

// POST-NOTES: 
// 26640 is 7 hours 24 minutes (our working day).  
// 4 days times 7:30 and 1 day of 7:00 will result in 37:00 total.
// Our working week starts on Monday 00:00 and ends on Sunday 23:59.
  1.  $w_value = $number_of_weeks_from_now
  2.  // usually 0 for this week, 1 for the week before, 2 for the week before that... 
  3.   
  4.  $timesheet_query_per_user=" 
  5.  SELECT DISTINCT 
  6.      s.StaffUserID AS 'UserID', 
  7.      ( 
  8.          SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed 
  9.          FROM StaffActivities s 
  10.          WHERE WEEKDAY(s.DateTimeCreated)=0 
  11.          AND YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value 
  12.          AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION' 
  13.      ) AS 'Monday', 
  14.      ( 
  15.          SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed 
  16.          FROM StaffActivities s 
  17.          WHERE WEEKDAY(s.DateTimeCreated)=1 
  18.          AND YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value 
  19.          AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION' 
  20.      ) AS 'Tuesday', 
  21.      ( 
  22.          SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed 
  23.          FROM StaffActivities s 
  24.          WHERE WEEKDAY(s.DateTimeCreated)=2 
  25.          AND YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value 
  26.          AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION' 
  27.      ) AS 'Wednesday', 
  28.      ( 
  29.          SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed 
  30.          FROM StaffActivities s 
  31.          WHERE WEEKDAY(s.DateTimeCreated)=3 
  32.          AND YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value 
  33.          AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION' 
  34.      ) AS 'Thursday', 
  35.      ( 
  36.          SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed 
  37.          FROM StaffActivities s 
  38.          WHERE WEEKDAY(s.DateTimeCreated)=4 
  39.          AND YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value 
  40.          AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION' 
  41.      ) AS 'Friday', 
  42.      ( 
  43.          SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed 
  44.          FROM StaffActivities s 
  45.          WHERE WEEKDAY(s.DateTimeCreated)=5 
  46.          AND YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value 
  47.          AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION' 
  48.      ) AS 'Saturday', 
  49.      ( 
  50.          SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed 
  51.          FROM StaffActivities s 
  52.          WHERE WEEKDAY(s.DateTimeCreated)=6 
  53.          AND YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value 
  54.          AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION' 
  55.      ) AS 'Sunday', 
  56.      ( 
  57.          SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))) AS SecondsElapsed 
  58.          FROM StaffActivities s 
  59.          WHERE YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value 
  60.          AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION' 
  61.      ) AS 'Total', 
  62.      ( 
  63.          SELECT ROUND((SUM(TIME_TO_SEC(s.EstimateTimeFinish)-TIME_TO_SEC(s.DateTimeCreated))/26640),2) AS SecondsElapsed 
  64.          FROM StaffActivities s 
  65.          WHERE YEARWEEK(s.DateTimeCreated)=YEARWEEK(NOW()) - $w_value 
  66.          AND s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION' 
  67.      ) AS 'Total Days' 
  68.  FROM StaffActivities s 
  69.  WHERE s.StaffUserID='STRING_TO_REPLACE_PER_ITERATION' 
  70.  "
  71.   
  72.  // POST-NOTES: 
  73.  // 26640 is 7 hours 24 minutes (our working day)
  74.  // 4 days times 7:30 and 1 day of 7:00 will result in 37:00 total. 
  75.  // Our working week starts on Monday 00:00 and ends on Sunday 23:59. 

Putting it together
Your first query should loop through the users list, within the loop run the activities query and replace the string "STRING_TO_REPLACE_PER_ITERATION" with the User ID. Something like:
copyraw
$result=mysql_query($user_list_query);
while($row=mysql_fetch_assoc($result)) {
	$this_user_id=$row['UserID'];
	$this_user_full=$row['Fullname'];
	$this_user_query=str_replace("STRING_TO_REPLACE_PER_ITERATION", $this_user_id, $timesheet_query_per_user);
	$this_mon=$this_tue=$this_wed=$this_thu=$this_fri=$this_sat=$this_sun=$this_t=$this_t2="";


	$sub_result=mysql_query($this_user_query);
	while($sub_row=mysql_fetch_assoc($sub_result)) {
		$this_mon=$sub_row['Monday'];
		$this_tue=$sub_row['Tuesday'];
		$this_wed=$sub_row['Wednesday'];
		$this_thu=$sub_row['Thursday'];
		$this_fri=$sub_row['Friday'];
		$this_sat=$sub_row['Saturday'];
		$this_sun=$sub_row['Sunday'];
		$this_t=$sub_row['Total'];
		$this_t2=$sub_row['Total Days'];

		echo $this_user_full . "\t" . $this_mon . "\t" . $this_tue . "\t" . $this_wed . "\t" . $this_thu . "\t" . $this_fri . "\t" . $this_sat . "\t" . $this_sun . "\t" . $this_t . "\t" . $this_t2;

                ...do some more here ???...
        }
}
  1.  $result=mysql_query($user_list_query)
  2.  while($row=mysql_fetch_assoc($result)) { 
  3.      $this_user_id=$row['UserID']
  4.      $this_user_full=$row['Fullname']
  5.      $this_user_query=str_replace("STRING_TO_REPLACE_PER_ITERATION", $this_user_id, $timesheet_query_per_user)
  6.      $this_mon=$this_tue=$this_wed=$this_thu=$this_fri=$this_sat=$this_sun=$this_t=$this_t2=""
  7.   
  8.   
  9.      $sub_result=mysql_query($this_user_query)
  10.      while($sub_row=mysql_fetch_assoc($sub_result)) { 
  11.          $this_mon=$sub_row['Monday']
  12.          $this_tue=$sub_row['Tuesday']
  13.          $this_wed=$sub_row['Wednesday']
  14.          $this_thu=$sub_row['Thursday']
  15.          $this_fri=$sub_row['Friday']
  16.          $this_sat=$sub_row['Saturday']
  17.          $this_sun=$sub_row['Sunday']
  18.          $this_t=$sub_row['Total']
  19.          $this_t2=$sub_row['Total Days']
  20.   
  21.          echo $this_user_full . "\t" . $this_mon . "\t" . $this_tue . "\t" . $this_wed . "\t" . $this_thu . "\t" . $this_fri . "\t" . $this_sat . "\t" . $this_sun . "\t" . $this_t . "\t" . $this_t2
  22.   
  23.                  ...do some more here ???... 
  24.          } 
  25.  } 
Yields
copyraw
-- Note this data matches the brief as we've converted the seconds to time.
-- Using MySQL to convert rather than PHP so as not to have to deal with hour changes and the magical 23:00 hour.

-- 2nd Note: We use "WEEKDAY" instead of "DAYOFWEEK" as our working week starts on Monday (Mon=0, Tue=1, Sun=6).  

User		Mon	Tue	Wed	Thu	Fri	Sat	Sun	Total	Days
--------------- ------- ------- ------- ------- ------- ------- ------- ------- -----
Me		06:30	08:00	07:30	06:30	07:30	NULL	NULL    36:00   4.86
Myself		NULL	07:30	07:30	07:30	07:30	NULL	NULL    30:00   4.05
I		NULL	NULL	08:00	08:00	08:00	NULL	NULL    24:00   3.24
  1.  -- Note this data matches the brief as we've converted the seconds to time. 
  2.  -- Using MySQL to convert rather than PHP so as not to have to deal with hour changes and the magical 23:00 hour. 
  3.   
  4.  -- 2nd Note: We use "WEEKDAY" instead of "DAYOFWEEK" as our working week starts on Monday (Mon=0, Tue=1, Sun=6)
  5.   
  6.  User        Mon    Tue    Wed    Thu    Fri    Sat    Sun    Total    Days 
  7.  --------------- ------- ------- ------- ------- ------- ------- ------- ------- ----- 
  8.  Me        06:30    08:00    07:30    06:30    07:30    NULL    NULL    36:00   4.86 
  9.  Myself        NULL    07:30    07:30    07:30    07:30    NULL    NULL    30:00   4.05 
  10.  I        NULL    NULL    08:00    08:00    08:00    NULL    NULL    24:00   3.24 
Category: MySQL :: Article: 416