views:

60

answers:

2

Using PHP/mySQL, a user is granted a single integer point to their member account each day. The data I will use to determine if a point should be granted are these mysql fields: Creation Date (timestamp) and Last Login (UNIX TIME).

The procedure for granting these points is determined when the user logs in. My question is, what's the most efficient way of determining how many days have passed since the last login? Secondly, if a user logs in each day, how do I determine if 24 hours has passed and a point is to be granted? Days past equates to the points given (1 per day).

Currently I am using this code:

/*
** Updates Points based on days since last visit
*/
static function UpdatePoints($username)
{
    $getlog = System::$mySQL->Select("lastLog, creation FROM users WHERE username='$username'");
    $log = System::$mySQL->Fetch($getlog);

    $offset = (TIME() - $log['lastLog']) / 86400;  // 24hrs
    $lastlog = round($offset); // in days

    if($lastlog > 0)
    {
        System::$mySQL->Update("users SET points=points+".$lastlog." WHERE username='$username'");
    }
}

Markup aside, it's obvious my code is shortsighted. If the user logs in once everyday, they do not gain a point. Therefore I must determine the correct method for doing so using the Creation Date field as well. I just can't wrap my head around it today, any suggestions? Thanks.

+3  A: 

Use a separate field to keep the date when you added the point to user's account. If this happened not today - add a point (or several) and update a field.

Eugene Mayevski 'EldoS Corp
+2  A: 

This is better suited for the database than for PHP. Add a table users_points, with unique index (user_id,login_date). Sample data:

 user_id | login_date
====================== 
 19746   | 2010-09-02
 19746   | 2010-09-03

Then on every login, mark that the user has logged in on that date (if the row already exists, the index will prevent duplication):

INSERT IGNORE INTO `users_points` (`user_id`,`login_date`) VALUES (19746,CURDATE())

And to get the number of points:

SELECT COUNT(*) FROM `users_points` WHERE `user_id` = 19746

This is also good that you have a list of days when the user has logged in, in case you change the criteria and want to do a recount.

user_id is an INT, login_date is a DATE, there's a usable index, so both insert and select should be quick, and the table will be relatively small even with a huge number of users.

In case you insist on having the user score stored in some place (or maybe you want to retrieve it together with other user data), you could do this on login:

  • run the insert ignore
  • run the select count
  • save the result in a column of table users.
Piskvor
Thanks Piskvor! I think your response has changed my mind about doing all those calculations, and just sticking with a table. I've been so caught up in using what I already have I never considered this direction. So simple and obvious (now), yet so effective. Thank you!
mrtwidget
@mrtwidget: You're welcome.
Piskvor
Number of points is a value which is accessed all the time. Calculating it each time it's needed is waste of resources. Calculation must be performed once a period and the value should be read from the DB.
Eugene Mayevski 'EldoS Corp
@Eugene Mayevski 'EldoS Corp: "Number of points is a value which is accessed all the time." 1) [citation-needed] 2) the value *is* read from the DB, in case you didn't notice 3) contrary to popular expectations, indexes are *very* efficient in cases similar to these 4) are you suggesting that the database should be denormalized? Are you seeing significant performance problems? (might be the case, yes, but I'd like to see some data for that; this solution is plenty fast on millions of rows)
Piskvor
@Piskvor: You write "to get the number of points, use select". This is waste of resource. It doesn't matter how quickly the query is executed. It's just extra unneeded work comparing to having a permanent field for points. Now, you are also polluting the database with unneeded records (login dates). Instead of having two fields for the number of points and the last login date you offer to keep several records per user. That's non-sense. For small table this will work, yes, but if you use bad design in small things, chances are you will use the same bad design where it really matters.
Eugene Mayevski 'EldoS Corp
@Eugene Mayevski 'EldoS Corp: You don't seem to know about select optimizations and indexes in MySQL (see link), if you insist that this is bad design (this number will be retrieved from the index, which is much faster than going through every row and actually counting them). If you really, really want to have the score stored somewhere, just do the `select count...` right after the `insert ignore` and store the result in uesrs. Edited that into the answer. http://dev.mysql.com/doc/refman/5.0/en/where-optimizations.html
Piskvor
It doesn't matter if select is optimized or not. You are favoring doing some work to not doing it. If the dog has nothing to do, it lick the balls. Same here ...
Eugene Mayevski 'EldoS Corp
Whereas doing a `SELECT points FROM users` is "not doing any work". *sigh* No point in having a further discussion.
Piskvor