将表中的记录与2列主键

时间:2017-04-02 12:38:26

标签: sql postgresql primary-key plpgsql postgresql-9.4

我为我的问题准备了一个简单的测试用例 -

在游戏玩家ID中,名称存储在表users中:

CREATE TABLE users (
        uid SERIAL PRIMARY KEY,
        name varchar(255) NOT NULL
);

玩家可以在表reviews中使用2列PK:

对彼此进行评分
CREATE TABLE reviews (
        uid integer NOT NULL CHECK (uid <> author) REFERENCES users ON DELETE CASCADE,
        author integer NOT NULL REFERENCES users(uid) ON DELETE CASCADE,
        review varchar(255),
        PRIMARY KEY(uid, author)
);

这两个表都填充了样本数据:

INSERT INTO users (uid, name) VALUES (1, 'User 1');
INSERT INTO users (uid, name) VALUES (2, 'User 2');
INSERT INTO users (uid, name) VALUES (3, 'User 3');
INSERT INTO users (uid, name) VALUES (4, 'User 4');

INSERT INTO reviews (uid, author, review) VALUES (1, 2, 'User 2 says: 1 is nice');
INSERT INTO reviews (uid, author, review) VALUES (1, 3, 'User 3 says: 1 is nice');
INSERT INTO reviews (uid, author, review) VALUES (1, 4, 'User 4 says: 1 is nice');

INSERT INTO reviews (uid, author, review) VALUES (2, 1, 'User 1 says: 2 is nice');
INSERT INTO reviews (uid, author, review) VALUES (2, 3, 'User 3 says: 2 is nice');
INSERT INTO reviews (uid, author, review) VALUES (2, 4, 'User 4 says: 2 is ugly');

INSERT INTO reviews (uid, author, review) VALUES (3, 1, 'User 1 says: 3 is nice');
INSERT INTO reviews (uid, author, review) VALUES (3, 2, 'User 2 says: 3 is ugly');
INSERT INTO reviews (uid, author, review) VALUES (3, 4, 'User 4 says: 3 is ugly');

INSERT INTO reviews (uid, author, review) VALUES (4, 1, 'User 1 says: 4 is ugly');
INSERT INTO reviews (uid, author, review) VALUES (4, 2, 'User 2 says: 4 is ugly');
INSERT INTO reviews (uid, author, review) VALUES (4, 3, 'User 3 says: 4 is ugly');

当我的移动应用程序注意到同一个玩家正在使用多个用户ID时,它会将记录与下面显示的自定义存储功能合并。

合并(到out_uid)时,用户对自己的评论会被删除,任何产生的重叠评论都会被删除。

(在合并记录的背景下:这是非常必要的,因为我运行另一个玩家评论多年的游戏,用户一直纠缠着我 - 为什么他们的评论和游戏统计数据不同,当他们登录时通过Facebook,通过Google+,通过Apple Game Center ...)

由于没有UPDATE ... ON CONFLICT DO NOTHING - 我试图在自定义存储功能中帮助自己处理以下两个INSERT ... SELECT ... ON CONFLICT DO NOTHING

CREATE OR REPLACE FUNCTION merge_users(
                in_uids integer[],
                OUT out_uid integer
        ) RETURNS integer AS
$func$
BEGIN
        SELECT
                MIN(uid)
        INTO STRICT
                out_uid 
        FROM users
        WHERE uid = ANY(in_uids);

        -- delete self-reviews
        DELETE FROM reviews
        WHERE uid = out_uid
        AND author = ANY(in_uids);

        DELETE FROM reviews
        WHERE author = out_uid
        AND uid = ANY(in_uids);

        -- try to copy as many reviews OF this user as possible
        INSERT INTO reviews (
                uid,
                author,
                review
        ) SELECT
                out_uid,        -- change to out_uid
                author,
                review
        FROM reviews
        WHERE uid <> out_uid
        AND uid = ANY(in_uids)
        ON CONFLICT DO NOTHING;

        DELETE FROM reviews
        WHERE uid <> out_uid
        AND uid = ANY(in_uids);

        -- try to copy as many reviews BY this user as possible
        INSERT INTO reviews (
                uid,
                author,
                review
        ) SELECT
                uid,
                out_uid,        -- change to out_uid
                review
        FROM reviews
        WHERE author <> out_uid
        AND author = ANY(in_uids)
        ON CONFLICT DO NOTHING;

        DELETE FROM reviews
        WHERE author <> out_uid
        AND author = ANY(in_uids);

        DELETE FROM users
        WHERE uid <> out_uid
        AND uid = ANY(in_uids);
END
$func$ LANGUAGE plpgsql;

不幸的是,有问题 - 请运行2个命令来查看它们:

test=> SELECT out_uid FROM merge_users(ARRAY[1,2]);
 out_uid 
---------
       1
(1 row)

test=> SELECT out_uid FROM merge_users(ARRAY[1,2,3,4]);
ERROR:  new row for relation "reviews" violates check constraint "reviews_check"
DETAIL:  Failing row contains (1, 1, User 4 says: 3 is ugly).
CONTEXT:  SQL statement "INSERT INTO reviews (
                uid,
                author,
                review
        ) SELECT
                uid,
                out_uid,        -- change to out_uid
                review
        FROM reviews
        WHERE author <> out_uid
        AND author = ANY(in_uids)
        ON CONFLICT DO NOTHING"
PL/pgSQL function merge_users(integer[]) line 38 at SQL statement

所以删除自我评论似乎不起作用,请帮忙。

另外,我想知道是否有更好的方法来合并reviews记录,而不是我的INSERT ... SELECT ... ON CONFLICT DO NOTHING技巧。

为方便起见,我创建了SQL Fiddle

此外,我在非常有用的pgsql-general邮件列表中提出了这个问题。

1 个答案:

答案 0 :(得分:2)

我想我会接受这个:

  • 根据合并的用户ID删除任何自我评论。
  • 将其余部分合并在一起。

我认为这是失败的第一部分。试试这个delete

DELETE FROM reviews
WHERE uid = ANY(in_uids) AND author = ANY(in_uids);

也就是说,旧uids的任何组合都是个问题。我不确定in_uids是否包含所有等效的uid,但我们的想法是整个等效类用于此目的。