在使用PostgreSQL 9.3作为后端的游戏中,我试图限制用户每周玩的游戏数量。
我准备了an SQL Fiddle,但不幸的是它没有用。
我的(测试,而不是生产)代码在这里:
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)
);
这是一个存储过程,我试着找到本周玩过的游戏数量:
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;
有了这个条件:
(id = _id or
id in (select id from pref_users where last_ip=_last_ip))
我正在尝试抓住那些试图欺骗并加入游戏的用户与其他玩家id
但是来自同一个IP地址的用户。
但是我很担心,有时我会得到翻倍的已完成游戏 - 因为在上述情况下,第一部分首先匹配:id = _id
然后第二部分id in (...)
- 这将是给我2次游戏次数。
请问有什么治疗方法吗?
我需要"检测"在上述条件下使用id
两次时。
答案 0 :(得分:1)
我会尝试某种数据透视表,虽然我不知道plpgsql是否支持它。
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;
然后获得最多两个值。
但是一个用户可以使用超过1个IP进行播放,这里没有介绍(很可能你需要记录每个游戏的IP来捕捉这些情况)
另请注意,这将是非常低性能的代码。子查询将在过滤阶段的每个匹配行上运行。
答案 1 :(得分:1)
请勿使用char(7)
存储时间戳。
准确地说,请勿使用char(7)
存储任何内容。永远。详细信息:
Compare varchar with char
并且不要将日期/时间数据存储在任何文本表示中。使用timestamp
or date
。
如果您只对一年中的感兴趣,那么您只需存储一个integer
(甚至是smallint
),您可以使用{{{ 3}}:
SELECT extract(week FROM now())::int;
但我建议存储占用4个字节的date
,就像integer
一样,而char(7)
占用11个字节。您可以便宜地提取具有上述功能的周。或者使用extract():
SELECT date_trunc('week', now())
如果必须,id
更应该是int
- 或bigint
。 varchar(32)
效率很低。
并声明您的专栏completed
NOT NULL
!或者你必须处理可能的NULL值。您的检查约束不覆盖它。 NULL不违反约束。
假设date
的数据类型yw
和int
的{{1}}:
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;
last_ip
,则根本不需要NOT NULL
作为参数。只需_id
。