0

I am trying to code a script that takes database entries like these:

╔════╦═════════╦════════╦══════════╗
║ id ║ user_id ║ action ║   time   ║
╠════╬═════════╬════════╬══════════╣
║  1 ║       1 ║      1 ║ 12:00:00 ║
║  2 ║       1 ║      2 ║ 12:10:00 ║
║  3 ║       1 ║      1 ║ 18:00:00 ║
║  4 ║       1 ║      2 ║ 18:10:00 ║
╚════╩═════════╩════════╩══════════╝

Then works out the difference between the two actions 1 & 2 and then sum them together, this example would make a total of 20:00

I don't really know where to start for this and can't seem to find anything on the net that helps.

If someone could point me in the right direction, or any help will be greatly appreciated.

Edit: Other actions may be in the database (1-7), as well as multiple users.

Edit2: More complex example table beneath, still should result in the same example of 20:00

╔════╦═════════╦════════╦══════════╗
║ id ║ user_id ║ action ║   time   ║
╠════╬═════════╬════════╬══════════╣
║  1 ║       1 ║      1 ║ 12:00:00 ║
║  2 ║       1 ║      2 ║ 12:10:00 ║
║  3 ║       2 ║      1 ║ 12:30:00 ║
║  4 ║       2 ║      2 ║ 12:40:00 ║
║  5 ║       2 ║      3 ║ 12:50:00 ║
║  6 ║       2 ║      4 ║ 13:00:00 ║
║  7 ║       3 ║      1 ║ 14:00:00 ║
║  8 ║       3 ║      2 ║ 14:10:00 ║
║  9 ║       1 ║      1 ║ 18:00:00 ║
║ 10 ║       1 ║      2 ║ 18:10:00 ║
╚════╩═════════╩════════╩══════════╝
Jamie
  • 467
  • 8
  • 19
  • Maybe try to stock Timestamp more than date time, you can easily do more compute on. – Julien Leray Apr 15 '14 at 15:52
  • How would we know that we should compare `12:00:00` and `12:10:00` as opposed to `12:00:00` and `18:10:00`? In both cases, we would be comparing `user_id=1` and `action=1` to `user_id=1` and `action=2`. Also, is there a possibility of a 3rd action? If so, how do you want that factored in? – Patrick Q Apr 15 '14 at 15:53
  • @PatrickQ Exactly, that's what I've been stuck on... I need some sort of way to order them before doing the maths. – Jamie Apr 15 '14 at 15:55
  • And what about additional users? Should their time differences all be added together? Or do you want sums per user? I think there's a lot of details at play here that you need to hash out (or if you've already done so, then explain so here) before you ask this question. – Patrick Q Apr 15 '14 at 16:01
  • @PatrickQ In my actual database, there are actually 7 actions, so the selection of 1 & 2 would need to be incorporated too. – Jamie Apr 15 '14 at 16:04
  • @PatrickQ And yes there are multiple users too, I just made a simple version of the table for the question. The selection of the user ID is currently being done by sessions. – Jamie Apr 15 '14 at 16:06

2 Answers2

1

After discussion SQL-FIDDLE http://sqlfiddle.com/#!2/8e5ab/5

--

SQL-FIDDLE http://sqlfiddle.com/#!2/ab988/1

SELECT user_id, SUM(time_difference) as time_spent
FROM (    
  SELECT t1.user_id,
  CASE WHEN t1.user_id = t2.user_id 
  THEN time_to_sec(t2.time_duration) - time_to_sec(t1.time_duration) ELSE 0 END time_difference
  FROM T t1 LEFT OUTER JOIN T t2
  ON t1.id = t2.id - 1
  JOIN (SELECT @DIS := 0)R)TAB1
GROUP BY user_id;

What does this query do?

  • The same table is used twice. The calculation result is available in the outer SELECT.
  • The inner SELECT (subquery) does the following:
    • calc time difference (t2-t1)
    • when user_id is the same (user_id = user_id)
    • but different row (id-1)
    • the result is time_difference
  • The outer SELECT gets time_difference and SUMs it up

For now, you get time_spent in seconds. If you want your exact result '20:00', you might change the first line to include a time conversion, like so:

SELECT user_id, TIME_FORMAT(SEC_TO_TIME(SUM(time_difference)),'%i') as time_spent
better
SELECT user_id, TIME_FORMAT(SEC_TO_TIME(SUM(time_difference)),'%Hh %im') as time_spent

To get the time_spent for a user_id, you might add

JOIN (SELECT @DIS := 0)R)TAB1 WHERE user_id = 1

SQL FIDDLE with time conversion and WHERE user_id http://sqlfiddle.com/#!2/ab988/9

Jens A. Koch
  • 39,862
  • 13
  • 113
  • 141
  • What if one user performs an action between actions of another user? – Patrick Q Apr 15 '14 at 17:57
  • It's not relevant (i think), because the calculation is only executed, `when user_id is the same` AND it's `grouped by user_id`. Or in other words: time calculation is done per user. – Jens A. Koch Apr 15 '14 at 18:00
  • What do you mean by "not relevant"? Are you saying that those records aren't relevant? My guess is that the OP would disagree. So assuming `id/user_id/action/time`, say we have rows `1/1/1/12:00:00`, `2/2/1/12:05:00`, and `3/1/2/12:10:00`. The difference between the first and last id is greater than 1, but the difference between the times of the actions should still be counted. – Patrick Q Apr 15 '14 at 18:05
  • That would result in 10m for user_id 1 - user_id 2 has no second time event (time_duration) to do the calculation on. http://sqlfiddle.com/#!2/ab988/9 – Jens A. Koch Apr 15 '14 at 18:13
  • I just did: http://sqlfiddle.com/#!2/8e5ab/1 Instead of the expected 20:00, the result is 10:00. The sum of the time difference between user1's actions is still 20:00, regardless of when user2 performed action1. – Patrick Q Apr 15 '14 at 18:16
  • That's because you are asking the time for user_id =1. Just remove `WHERE user_id = 1`and see the time_spent listing for all users.. – Jens A. Koch Apr 15 '14 at 18:18
  • 1
    Really? http://sqlfiddle.com/#!2/8e5ab/2 Still 10:00 for user1 when it should be 20:00. The problem is your use of `ON t1.id = t2.id - 1`. Unless this is a very odd application, I doubt that we can assume that it is only used by one user at a time. – Patrick Q Apr 15 '14 at 18:21
  • You are right, the id-1 kills it. This is the closest starting point to the solution i can give you: http://sqlfiddle.com/#!2/8e5ab/5 – Jens A. Koch Apr 15 '14 at 18:26
  • Thanks alot @Jens-AndréKoch for all of your hard work, this definitely helps – Jamie Apr 15 '14 at 18:36
1

Here's a php side approach that should get you started. Assuming $arrQueryResults is a DB result set from "SELECT * FROM table ORDER BY user_id, time, action" using whatever syntax is appropriate for your DB.

$db = mysqli_connect("server", "username", "password", "database");
$query = "SELECT * FROM action WHERE date = '".$todaysDate."' ORDER BY user_id, time, action";
$result = mysqli_query($db, $query);

while ($arrResult = mysqli_fetch_assoc($result)) { // For each row in the query result
    $user=$arrResult['user_id'];
    $time=strtotime($arrResult['time']); // Converts H:M:S to an integer timestamp
    $action=$arrResult['action'];

    // Add the timestamp for this user and action to track until we have a matched pair
    if ($action == '2') {
        $arrUserAction[$user][$action]=$time;
    }

    if ($action == '5' && isset($arrUserAction[$user]['2'])) {
        if (!isset($arrDiffSums[$user])) {  // If we have no running total for the user yet, create one.
            $arrDiffSums[$user]=$time - $arrUserAction[$user]['2'];
        } else { // IF we have a total for the user already, add to it.
            $arrDiffSums[$user]=$arrDiffSums[$user] + ($time - $arrUserAction[$user]['2']);
        }
        unset($arrUserAction[$user]);  // We just added a sum, now remove our tracking to be ready for the next pair.
    } 
}
print_r($arrDiffSums);

This should result in a $arrDiffSums with # secs by user. In your 2nd table example:

array(
  '1' => 1200,
  '2' => 1200,
  '3' => 600
)

Which can be converted to H:M:S or other formats with gmdate(); (PHP - Convert seconds to Hour:Minute:Second):

foreach ($arrDiffSums as $user => $secs) {
     print "User: ".$user." Sum: ".gmdate("H:i:s", $secs)."\n";
}
Community
  • 1
  • 1
Jef
  • 1,128
  • 9
  • 11
  • I'm just attempting to implement it now, this code is very advanced for me, so excuse the novice questions... First of all, where should I print the results? Outside of the foreach or inside? And would this query be OK? `$query = mysql_query("SELECT * FROM action WHERE date = $todaysDate ORDER BY user_id, time, action");while($row=mysql_fetch_assoc($query)){$arrQueryResults[] = $row["time"];}` – Jamie Apr 15 '14 at 18:35
  • Updated to get you a little closer using mysqli_ in preference to mysql_ driver (which is deprecated.) – Jef Apr 15 '14 at 19:06
  • Also as @Patrick Q is pointing out there are an awful lot of assumptions baked into this. Are the start/stop action #s always sequential? Can a user have multiple actions going at once? If these are clock times, how sure are you that there isn't a sequence overlapping a day boundary? (I.e., start 23:50:00 stop 00:10:00 = 20:00). Think through these kinds of things. – Jef Apr 15 '14 at 19:13
  • Yeah I should have been more clear to begin with, I'm fairly new to stackoverflow and not an expert at PHP as you can probably see. Users can't have multiple actions going on at once, and in this scenario users wouldn't overlap during a day change. It's implemented now and seems to be picking things up, the only thing I need to do is change action 1 & 2 to 2 & 5. Looking at your code I can't seem to see where I can change it? – Jamie Apr 15 '14 at 19:26
  • Not sure what you mean. Currently it'll figure the first action in a sequential pair is the start and the second one is the stop. So 1 to 2, 3 to 4, 5 to 6, 99 to 100, etc. I'll add some comments to help make the code clearer. – Jef Apr 15 '14 at 19:33
  • The example table at the top, I used actions 1 & 2 as an example, in my actual database I need to calculate the time difference between 2 & 5 – Jamie Apr 15 '14 at 19:40
  • Oh - and you don't care about any others? – Jef Apr 15 '14 at 19:41
  • Sorry if it came across that way but in this case no, I'll give you a brief description of the application that I'm making.. It's a clocking in system for the workplace, and actions (2 & 5) are the start of and end of a tea-break. The reason why I was struggling was because a user can have multiple breaks OR none at all for example. The other actions I have done already simply because the user can only have 1 lunch-break if you see what I mean? Sorry for being a complete pain.. – Jamie Apr 15 '14 at 19:44
  • See updated solution. It could be simplified, knowing you are concerned only with certain actions, and not trying to figure them dynamically. – Jef Apr 15 '14 at 19:46
  • It's implemented now but I'm getting a strange value back? `User: 1 Sum: 15:51:44` where is should be `00:20:00` – Jamie Apr 15 '14 at 19:56
  • Sorry - my fault. Updated answer changing [$action -1] to ['2'] in two places. – Jef Apr 15 '14 at 19:59
  • Excellent, thanks Jef, all sorted now. Sorry for all of the trouble! – Jamie Apr 15 '14 at 20:02
  • 1
    My pleasure, good luck, and please offer my apologies to anyone who gets in trouble for taking a 21:00 break now :) – Jef Apr 15 '14 at 20:02