Проверка временной метки является нулевой или в прошлом - PullRequest
2 голосов
/ 29 декабря 2011

В PostgreSQL 8.4.9 у меня есть небольшая игра, в которой пользователи могут приобрести статус VIP («очень важный человек»):

# \d pref_users;
                Table "public.pref_users"
   Column   |            Type             |   Modifiers
------------+-----------------------------+---------------
 id         | character varying(32)       | not null
 vip        | timestamp without time zone |

Если vip никогда не был куплен, это будет NULL .

Если срок действия vip истек, это будет .

Я пытаюсь создать процедуру PL / pgSQL, позволяющую пользователям с достаточным количеством оставшегося статуса vipподарить неделю другим пользователям в качестве «подарка»:

create or replace function pref_move_week(_from varchar,
    _to varchar) returns void as $BODY$
        declare
                has_vip boolean;
        begin

        select vip > current_timestamp + interval '1 week'
        into has_vip from pref_users where id=_from;

        if (not has_vip) then
                return;
        end if;

        update pref_users set vip = current_timestamp - interval '1 week' where id=_from;
        update pref_users set vip = current_timestamp + interval '1 week' where id=_to;

        end;
$BODY$ language plpgsql;

К сожалению, эта процедура не работает должным образом, если vip данного пользователя имеет значение NULL:

# update pref_users set vip=null where id='DE16290';
UPDATE 1

# select id,vip from pref_users where id in ('DE16290', 'DE1');
   id    |            vip
---------+----------------------------
 DE1     | 2012-01-05 17:35:17.772043
 DE16290 |
(2 rows)

# select pref_move_week('DE16290', 'DE1');
 pref_move_week
----------------

(1 row)

# select id,vip from pref_users where id in ('DE16290', 'DE1');
   id    |            vip
---------+----------------------------
 DE1     | 2012-01-05 17:43:11.589922
 DE16290 | 2011-12-22 17:43:11.589922
(2 rows)

Т.е. вышеприведенное утверждение IF , похоже, не работает и проваливается.

Также мне интересно, нужна ли здесь вообще переменная has_vip ?

И как я могу убедиться, что первичные ключи _from и _to действительно присутствуют в таблице pref_users или об этом уже позаботились (потому что одиниз операторов UPDATE будет «выбрасывать исключение» и транзакция будет отменена)?

ОБНОВЛЕНИЕ:

Спасибо за все ответы, и у меня также есть совет для использования:

          if (not coalesce(has_vip, false)) then
                   return;
          end if;

Но теперь у меня естьновая проблема:

# select id,vip from pref_users where id in ('DE16290', 'DE1');
  id    |            vip
---------+----------------------------
 DE1     | 2012-01-05 17:43:11.589922
 DE16290 |
(2 rows)

(т. е. DE1 имеет vip до мая и должен быть в состоянии дать неделю DE16290, но):

# select pref_move_week('DE1', 'DE16290');
 pref_move_week
----------------

(1 row)

# select id,vip from pref_users where id in ('DE16290', 'DE1');
  id    |            vip
---------+----------------------------
 DE1     | 2012-01-05 17:43:11.589922
 DE16290 |
(2 rows)

(По какой-то причине ничего не изменилось?)

ОБНОВЛЕНИЕ 2: Окончательное решение -

    create or replace function pref_move_week(_from varchar,
        _to varchar) returns void as $BODY$
            begin

            select 1 from pref_users
                where id=_from and
                vip > current_timestamp + interval '1 week';

            if not found then
                    return;
            end if;

            update pref_users set
                vip = vip - interval '1 week'
                where id=_from;

            update pref_users set
                vip = greatest(vip, current_timestamp) + interval '1 week'
                where id=_to;

            end;
    $BODY$ language plpgsql;

Ответы [ 3 ]

2 голосов
/ 12 марта 2016

При сравнении вашей метки времени с чем-то, что может быть нулевым, вы можете использовать функцию COALESE для предоставления значения "по умолчанию" для сравнения.Например,

select 1 from pref_users
  where id=_from and
  vip > COALESCE(current_timestamp, to_timestamp(0));

Функция COALESCE возвращает первое ненулевое значение из своего списка.См. документы .(Также to_timestamp () документы ...)

1 голос
/ 29 декабря 2011

Нулевые значения возвращают ложь при любом сравнении, кроме нуля (поэтому, когда ноль <> 1 ложно, а нуль = 1 ложно ... ноль никогда не равняется или не равняется чему-либо).Вместо

if (not has_vip) then return

используйте

if (not has_vip) or has_vip is null then return

Синтаксис, который вы используете, здесь немного уникален, и я не привык его читать ... думаю, вы пришли издругой фон, чем SQL, и еще не встречал нуля.Null - это особая концепция в SQL, с которой вам придется обращаться по-разному.Помните, что это не равно пустому или 0, и при этом это не меньше или больше чем любое число.

Редактировать в: Я немного смущен этим:

  select vip > current_timestamp + interval '1 week'
  into has_vip from pref_users where id=_from;

Я считаюэто эквивалентно:

  select vip 
  into has_vip from pref_users 
  where id=_from
  and vip > current_timestamp + interval '1 week';

Надеюсь, в любом случае.Теперь это будет возвращать ноль, если запись не найдена или если значение VIP равно нулю.Вы можете просто пойти, если has_vip имеет значение null, а затем вернуть?

1 голос
/ 29 декабря 2011

Возможно, есть более элегантное решение, но я думаю

SELECT (vip IS NOT NULL) AND (vip > current_timestamp + interval '1 week')

сделает работу. Я полагаю, что ваша проблема связана с тем, что NULL не является ни истинным, ни ложным.

Вы можете найти больше информации в документах .

...