views:

95

answers:

4

Hello, i have the following Oracle table:

GAMES    
    id_game int
    id_user int
    dt_game DATE

Before creating a new record i must check that the same user has not inserted a game more that N times a day.

I'm actually selecting the number of games played today in this way:

select count(1) as num from GAMES 
where id_user=ID_USER and
to_char(dt_game,'DD/MM/YYYY')=to_char(sysdate,'DD/MM/YYYY')

I don't like it too much: is there a better way to do it?

Thanks in advance c.

+6  A: 

The date conversions are a bit pointless and prevent any index on that column being used; you could simplify that bit with dt_game > trunc(sysdate).

Alex Poole
+4  A: 

There is a problem (related to concurrency) with checking the table and counting the rows before inserting a new row.

Let us say the user currently has played 9 games and you want to limit the rows to 10..

if you open two different sessions, then the both of them will see the currently uncommitted value of 9 games and hence both the games are allowed, By the time the transaction is complete, the user would have played 11 games..which is against the rule that you are trying to enforce.

Rajesh
+1  A: 

In Oracle you can create a function based index on id_user, to_char(dt_game,'DD/MM/YYYY') to improve lookup performance.

Albin Sunnanbo
In the table description above, the column dt_game is already a date. So there would be no point in converting it to a String.
Rajesh
+2  A: 

You can use a unique constraint to ensure that a fixed maximum number of records may be inserted for a particular day. For example, if N=5:

CREATE TABLE GAMES (
  id_game         NUMBER NOT NULL,
  id_user         NUMBER NOT NULL,
  dt_game         DATE   NOT NULL,
  user_game_count NUMBER NOT NULL,
  CONSTRAINT max_games_per_day
  CHECK (user_game_count BETWEEN 1 AND 5),
  CONSTRAINT dt_game_notime
  CHECK (dt_game = TRUNC(dt_game)),
  CONSTRAINT games_unique
  UNIQUE (id_user, dt_game, user_game_count),
);

The max_games_per_day constraint means that it can only take one of 5 different values. The dt_game_notime constraint means that we won't get various time values. The unique constraint, finally, ensures that for any particular date and user, they can only insert up to 5 rows.

Jeffrey Kemp
The application needs to discover the next value of user_game_count for the (id_user, dt_game) of interest. Can that be done reliably in a concurrent environment without locking access to the table?
Janek Bogucki
Yes - if two sessions try to insert a row with the same `user_game_count`, one will succeed, the other will raise an exception. Then it's just a matter of coding the application with a "back off/try again" loop.
Jeffrey Kemp
@Jeffrey,Do you see this table having 5 rows per user per date (if he plays 5 games) or 1 row with the count as 5?The max_games_per_day constraint leads me to believe that there would be one row with the count a 5... but the games_unique constraint and your comment "any particular date and user, they can only insert up to 5 rows" seem to indicate otherwise.If you insert 5 rows (or even otherwise), why would you need to include user_game_count in the games_unique constraint? Instead , shouldn't it contain User_id, game_id and the trunc(game_date) ?
Rajesh
@Rajesh, if `game_id` is unique by itself, then adding it to the constraint would add no value and would break this feature. Look at the constraint `max_games_per_day`: it constrains `user_game_count` to exactly five values: 1, 2, 3, 4 or 5. It is required in order to limit the table to 5 rows per user per day.
Jeffrey Kemp