3

Online игроки могут дополнительно доступ покупную игровая комната 1 или игровая комната 2.Использование JSONB_ARRAY_ELEMENTS с WHERE ... IN условия

И они могут быть временно запрещены для обмана.

CREATE TABLE users (
     uid SERIAL PRIMARY KEY, 

     paid1_until timestamptz NULL,  -- may play in room 1 
     paid2_until timestamptz NULL,  -- may play in room 2 

     banned_until timestamptz NULL, -- punished for cheating etc. 
     banned_reason varchar(255) NULL 
); 

Здесь выше таблица заполняется 4 тестовых записей:

INSERT INTO users (paid1_until, paid2_until, banned_until, banned_reason) 
VALUES (NULL, NULL, NULL, NULL), 
     (current_timestamp + interval '1 month', NULL, NULL, NULL), 
     (current_timestamp + interval '2 month', current_timestamp + interval '4 month', NULL, NULL), 
     (NULL, current_timestamp + interval '8 month', NULL, NULL); 

Все 4 записи принадлежат одному и тому же человеку - кто аутентифицированного себя с помощью различных социальных сетей (например, через Facebook, Twitter, Apple, Game Center и т.д.)

Я пытаюсь создать хранимую функцию, которая будет принимать список числовых идентификаторов пользователей (как массив JSON) и слияния записей, принадлежащих к тому же человеку в одной запись - withou т потерять свои платежи или наказания:

CREATE OR REPLACE FUNCTION merge_users(
     IN in_users jsonb, 
     OUT out_uid integer) 
     RETURNS integer AS 
$func$ 
DECLARE 
     new_paid1 timestamptz; 
     new_paid2 timestamptz; 
     new_banned timestamptz; 
     new_reason varchar(255); 
BEGIN 
     SELECT min(uid), 
       current_timestamp + sum(paid1_until - current_timestamp), 
       current_timestamp + sum(paid2_until - current_timestamp), 
       max(banned_until) 
     INTO 
       out_uid, new_paid1, new_paid2, new_banned 
     FROM users 
     WHERE uid IN (SELECT JSONB_ARRAY_ELEMENTS(in_users)); 

     IF out_uid IS NOT NULL THEN 
       SELECT banned_reason 
       INTO new_reason 
       FROM users 
       WHERE new_banned IS NOT NULL 
       AND banned_until = new_banned 
       LIMIT 1; 

       DELETE FROM users 
       WHERE uid IN (SELECT JSONB_ARRAY_ELEMENTS(in_users)) 
       AND uid <> out_uid; 

       UPDATE users 
       SET paid1_until = new_paid1, 
        paid2_until = new_paid2, 
        banned_until = new_banned, 
        banned_reason = new_reason 
       WHERE uid = out_uid; 
     END IF; 
END 
$func$ LANGUAGE plpgsql; 

К сожалению, результаты его использования в следующей ошибке:

# TABLE users; 
uid |   paid1_until   |   paid2_until   | banned_until | banned_reason 
-----+-------------------------------+-------------------------------+--------------+--------------- 
    1 |        |        |    | 
    2 | 2016-03-27 19:47:55.876272+02 |        |    | 
    3 | 2016-04-27 19:47:55.876272+02 | 2016-06-27 19:47:55.876272+02 |    | 
    4 |        | 2016-10-27 19:47:55.876272+02 |    | 
(4 rows) 

# select merge_users('[1,2,3,4]'::jsonb); 
ERROR: operator does not exist: integer = jsonb 
LINE 6:   WHERE uid IN (SELECT JSONB_ARRAY_ELEMENTS(in_users)) 
         ^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts. 
QUERY: SELECT min(uid), 
       current_timestamp + sum(paid1_until - current_timestamp), 
       current_timestamp + sum(paid2_until - current_timestamp), 
       max(banned_until) 
                       FROM users 
     WHERE uid IN (SELECT JSONB_ARRAY_ELEMENTS(in_users)) 
CONTEXT: PL/pgSQL function merge_users(jsonb) line 8 at SQL statement 

Пожалуйста, помогите мне решить эту проблему.

Для вашего удобства здесь a gist with SQL code.

+0

Есть ли конкретные причины, почему вы использовали бы массив 'jsonb'? Почему бы не использовать массив 'int []'? – Patrick

+0

Да, я хотел бы использовать JSON, потому что это мое мобильное приложение говорит с бэкэндом PHP + PostrgreSQL. Я уверен, что моя проблема разрешима с JSON, я просто пропускаю что-л. незначительный. –

ответ

3

Результат jsonb_array_elements() представляет собой набор jsonb элементов, поэтому вам нужно добавить явное приведение в uid к jsonb с to_jsonb() функции, IN будет заменен <@ оператора:

WITH t(val) AS (VALUES 
    ('[1,2,3,4]'::JSONB) 
) 
SELECT TRUE 
FROM t,jsonb_array_elements(t.val) element 
WHERE to_jsonb(1) <@ element; 

Для вашего случая, сниппета должен быть настроен на что-то вроде:

...SELECT ...,JSONB_ARRAY_ELEMENTS(in_users) user_ids
WHERE to_jsonb(uid) <@ user_ids...

Смежные вопросы