Get sum of integers for UNIQUE ids

自作多情 提交于 2019-12-14 03:12:49

问题


In a game using PostgreSQL 9.3 as backend I am trying to limit the number of games played by a user per week.

I have prepared an SQL Fiddle, but unfortunately it doesn't work.

My (test, not production) code is here:

create table pref_users (
        id varchar(32) primary key,
        last_ip inet
);

create table pref_match (
        id varchar(32) references pref_users on delete cascade,
        completed integer default 0 check (completed >= 0),
        yw char(7) default to_char(current_timestamp, 'IYYY-IW'),
        primary key(id, yw)
);

And here is a stored procedure with which I try to find the number of games played this week:

create or replace function pref_get_user_info(
    IN _id varchar,
    IN _last_ip inet,
    OUT games_this_week integer
) as $BODY$
        begin
            select sum(completed)
            into games_this_week
            from pref_match where
            (id = _id or
            id in (select id from pref_users where last_ip=_last_ip)) and
            yw=to_char(current_timestamp, 'IYYY-IW');
        end;
$BODY$ language plpgsql;

With this condition:

(id = _id or
 id in (select id from pref_users where last_ip=_last_ip))

I am trying to catch users who will try to cheat and join the game with a different player id but from the same IP-address.

But I am worried, that sometimes I will get doubled number of completed games - because in the above condition first the 1st part will match: id = _id and then the 2nd part id in (...) - and this will give me the number of games 2 times.

Is there please any cure for that?

I need to "detect" when an id is used twice in the above condition.


回答1:


Table layout

Do not use char(7) to store a timestamp.

To be precise, do not use char(7) to store anything. Ever. Details:
Compare varchar with char

And do not store date / time data in any text representation. Use timestamp or date.

If you are only interested in the week of the year, you could just store an integer (or even a smallint), which you get with with extract():

SELECT extract(week FROM now())::int;

But I suggest to store a date which occupies 4 bytes, just like an integer, while char(7) occupies 11 bytes. You can extract the week with above function cheaply. Or use date_trunc():

SELECT date_trunc('week', now())

And id should much rather be int - or bigint if you must. varchar(32) is rather inefficient.

And declare your column completed NOT NULL! Or you'd have to deal with possible NULL values. Your check constraint does not cover that. NULL does not violate the constraint.

Query / function

Assuming data type date for yw and int for id:

CREATE OR REPLACE FUNCTION pref_get_user_info(_id int, _last_ip inet
            ,OUT games_this_week int) AS
$func$
DECLARE
   _this_monday date := date_trunc('week', now())::date;
BEGIN

   SELECT sum(completed)::int
   INTO   games_this_week
   FROM   pref_users u
   JOIN   pref_match m USING (id)
   WHERE (u.id = _id OR u.last_ip = _last_ip)
   AND    m.yw BETWEEN _this_monday
                   AND _this_monday + 6;  -- "sargable"

END
$func$ LANGUAGE plpgsql;

If last_ip was defined NOT NULL, you would not need _id as parameter at all. Just _last_ip.




回答2:


I would tried some sort of pivot table, although i don't know if plpgsql supports it.

begin
    select 
    SUM
    (
        CASE WHEN pref_match.id IN (select id from pref_users where last_ip=_last_ip)
        THEN completed
    ) AS ip_matches,
    SUM
    (
        CASE WHEN pref_match.id = _id
        THEN completed
    ) AS id_matches,
    into games_this_week
    from pref_match
    and yw=to_char(current_timestamp, 'IYYY-IW');
end;

Then get max of two values.

But one user can play from more than 1 IP, this is not covered here (most probably you need to log every game IP to catch those situtations)

Also note, that this is will be VERY low-perfomance code. The subquery will run on every matched row at filter stage.



来源:https://stackoverflow.com/questions/24266979/get-sum-of-integers-for-unique-ids

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!