views:

377

answers:

4

Every visit to my website updates a user's individual hit counter and updates a column for time() based on their ip address and id stored in a cookie. So when coming to output the data, what's a more efficient way of my following code with less database calls, as it's essentially a copy of itself:

<?
$last1Min = time()-60;
$last5Mins = time()-300;
$last1Hr = time()-6000;
$last1Dy = time()-144000;
$last1Wk = time()-1008000;
$last1Mnth = time()-30240000;

//last1Min
$sql = "SELECT COUNT(*) FROM usersonline WHERE lastOnline > $last1Min";
while($rows = mysql_fetch_array(mysql_query($sql))) {
    echo "Users online in the last minute: " . $rows['COUNT(*)'] . "<br />\n";
}

//last5Mins
$sql = "SELECT COUNT(*) FROM usersonline WHERE lastOnline > $last5Mins";
while($rows = mysql_fetch_array(mysql_query($sql))) {
    echo "Users online in the last 5 minutes: " . $rows['COUNT(*)'] . "<br />\n";
}

//last1Hr
$sql = "SELECT COUNT(*) FROM usersonline WHERE lastOnline > $last1Hr";
while($rows = mysql_fetch_array(mysql_query($sql))) {
    echo "Users online in the last hour: " . $rows['COUNT(*)'] . "<br />\n";
}

//last1Dy
$sql = "SELECT COUNT(*) FROM usersonline WHERE lastOnline > $last1Dy";
while($rows = mysql_fetch_array(mysql_query($sql))) {
    echo "Users online in the last day: " . $rows['COUNT(*)'] . "<br />\n";
}

//last1Wk
$sql = "SELECT COUNT(*) FROM usersonline WHERE lastOnline > $last1Wk";
while($rows = mysql_fetch_array(mysql_query($sql))) {
    echo "Users online in the last week: " . $rows['COUNT(*)'] . "<br />\n";
}

//last1Mnth
$sql = "SELECT COUNT(*) FROM usersonline WHERE lastOnline > $last1Mnth";
while($rows = mysql_fetch_array(mysql_query($sql))) {
    echo "Users online in the last month: " . $rows['COUNT(*)'] . "<br /><br />\n";
}

If there is a more efficient way of presenting this data, I'm wanting to extend it to show not only how many users for each of these metrics is online on my entire site, but record and output the data for every page on my site.

A: 

Instead of calling the database for each of the times you could just make a call for all within the last month and order them by date. Then in php you can compare the date and time to see how long ago the user last logged in.

Josh Curren
That seems a straight forward solution, but what would be an efficient way of stepping through each record to sort it into the different metrics? Would this still be fine going through 100,000+ records?
Peter
The disadvantage of doing the work in PHP, is you have to deal with the transmission of all that data into PHP via a socket; often, this data transfer can be pretty expensive, for large quantities of data. If you can get your database to do the math for you and return the results, you save a lot of communications overhead.
Frank Farmer
you dont have to actually compare each record. You could use a binary search to find where the break point is for each of the separate times you want to display.
Josh Curren
+3  A: 

Set up a cron job that calculates the correct values only once every minute/5 minutes/etcetera. Cache the result and display that instead. There's really no need to calculate these kinds of stats X times a second when they only change once a minute or once ever half hour.

Sander Marechal
That's a good point and focuses on periodic calls instead of for EVERY visit which could be thousands, so simple I should have thought of that.
Peter
+2  A: 
SELECT 
  SUM(lastOnline <= 60) AS one_minute,
  SUM(lastOnline <= 300) AS five_minutes,
  ...
  SUM(lastOnline <= 30240000) AS one_month
FROM usersonline

Using this method, you can get everything you need in a single query with a single table scan; it doesn't get much more efficient than that. As others have mentioned, you should cache the result, as it's relatively expensive (even in this optimized form). There's no point in calculating this on every page load, especially if you're seeing multiple hits per second (which is extremely likely if you, say, hit the front page of digg)

lastOnline <= 60 evaluates to 1 for rows where the condition is true, and 0 for rows where the condition is false; SUM() sums these 1s and zeros, giving you a count of the number of rows for which the condition is true.

Learned this technique from a user comment in the mysql docs a few years ago; there are similar examples elsewhere

Frank Farmer
Yes this is what I was after for a single database call, but as you say would be used directly with a cron job and cache.
Peter
A: 

If you save your sessions to files, you could count the number of files that have been accessed in a time period. Then there'd be no database access at all.

psayre23
Would a single log file be better? It would seem unmanageable over time to have 100,000+ session files
Peter
Yes, but PHP manages those files and deletes them once the session has expired.
psayre23
So that's a fair solution to see who's online now, but separate session files would need to be created then to cover each metric where sessions expire 1min, 5mins, 1hr, 1day, 1month etc
Peter