3

Online poker players can optionally purchase access to playroom 1 or playroom 2.

And they can be temporarily banned for cheating.

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
);

Here the above table is filled with 4 test records:

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);

All 4 records belong to the same person - who has authenticated herself via different social networks (for example through Facebook, Twitter, Apple Game Center, etc.)

I am trying to create a stored function, which would take a list of numeric user ids (as a JSON array) and merge records belonging to same person into a single record - without losing her payments or punishments:

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;

Unfortunately, its usage results in the following error:

# 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

Please help me to solve the problem.

Here is a gist with SQL code for your convenience.

2
  • Is there a specific reasons why you would use a jsonb array? Why not use an int[] array? Commented Feb 27, 2016 at 19:53
  • Yes, I'd like to use JSON, because that is how my mobile app talks to the PHP + PostrgreSQL backend. I'm sure my problem is solvable with JSON, I am just missing smth. minor. Commented Feb 27, 2016 at 20:11

1 Answer 1

7

Result of jsonb_array_elements() is a set of jsonb elements, therefore you need add explicit cast of uid to jsonb with to_jsonb() function, IN will be replaced with <@ operator:

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;

For your case, snippet should be adjusted to something like:

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

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.