【问题标题】:Unique index on expressions doesn't work like I expect表达式的唯一索引不像我期望的那样工作
【发布时间】:2018-03-06 09:24:03
【问题描述】:

这是我的问题的示例:http://dbfiddle.uk/?rdbms=postgres_9.6&fiddle=ddb9cfd2da315ecf36cfffd66853f023

我使用这个功能:

CREATE OR REPLACE FUNCTION inListExistOrNull(list jsonb) RETURNS boolean AS
$BODY$
    DECLARE
        r TEXT;
        i boolean := false;
        vcount int;
    BEGIN
        FOR r IN SELECT * FROM jsonb_array_elements($1) LOOP
            vcount := (SELECT COUNT(*)
                       FROM table_example
                       WHERE data->>'test' LIKE '%' || r || '%');

            i := vcount > 0;

            IF i = true THEN
                EXIT;
            END IF;
        END LOOP;

        IF i = true THEN
            RETURN true;
        END IF;

        RETURN NULL;
    END;
$BODY$
LANGUAGE 'plpgsql'
IMMUTABLE
RETURNS NULL ON NULL INPUT;

我这样创建一个表和索引:

CREATE TABLE IF NOT EXISTS table_example(
   id bigserial primary key,
   data jsonb,
   txid bigint
);

CREATE INDEX IF NOT EXISTS table_example_txid_index
   ON table_example(txid);

CREATE UNIQUE INDEX IF NOT EXISTS unique_table_example
   ON table_example(inListExistOrNull(data->'test'));

我插入了一些行:

INSERT INTO table_example (id, data, txid)
   VALUES (1, '{"test": ["https://example.com/test/123", "https://example.com/test/678"]}', 1);
INSERT INTO table_example (id, data, txid)
   VALUES (2, '{"test": ["https://example.com/test/b4b81fb221d4fa641", "https://example.com/test/624f3e10048245fb1"]}', 2);
INSERT INTO table_example (id, data, txid)
   VALUES (4, '{"test": ["https://example.com/test/ggg", "https://example.com/test/hhh"]}', 4);
INSERT INTO table_example (id, data, txid)
   VALUES (5, '{"test": ["https://example.com/test/ggg"]}', 5);

我不知道为什么我可以用id = 5 创建一行。它应该被唯一索引捕获,但事实并非如此。

这就像我期望的那样工作:

INSERT INTO table_example (id, data, txid)
   VALUES (6, '{"test": ["https://example.com/test/b4b81fb221d4fa641", "https://example.com/test/624f3e10048245fb1"]}', 6);
ERROR:  duplicate key value violates unique constraint "unique_table_example"
DETAIL:  Key (inlistexistornull(data -> 'test'::text))=(t) already exists.

解决方案

触发:

CREATE OR REPLACE FUNCTION inListExistOrNull() RETURNS TRIGGER AS
$BODY$
    DECLARE
        r TEXT;
        i boolean := false;
        vcount int;
        newData jsonb;
    BEGIN
        newData := NEW.data->'test';

        FOR r IN SELECT * FROM jsonb_array_elements(newData) LOOP
            vcount := (SELECT COUNT(*) FROM table_example WHERE data->>'test' LIKE '%' || r || '%');

            i := vcount > 0;

            IF i = true THEN
                RAISE 'Duplicate data: %', r USING ERRCODE = '23505';
            END IF;
        END LOOP;

        RETURN NEW;
    END;
$BODY$
LANGUAGE 'plpgsql'
STABLE;

CREATE TRIGGER inListExistOrNullTrigger
BEFORE INSERT OR UPDATE ON table_example
    FOR EACH ROW EXECUTE PROCEDURE inListExistOrNull();

唯一索引:

CREATE OR REPLACE FUNCTION inListExistOrNull(list jsonb) RETURNS int AS
$BODY$
    DECLARE
        r TEXT;
        i boolean := false;
        vcount int;
    BEGIN
        FOR r IN SELECT * FROM jsonb_array_elements($1) LOOP
            vcount := (SELECT COUNT(*) FROM table_example WHERE data->>'test' LIKE '%' || r || '%');

            i := vcount > 0;

            IF i = true THEN
                RAISE 'Duplicate data: %', r USING ERRCODE = '23505';
            END IF;
        END LOOP;

        IF i = true
        THEN
            RETURN true;
        ELSE
            RETURN NULL;
        END IF;
    END;
$BODY$
LANGUAGE 'plpgsql'
IMMUTABLE
RETURNS NULL ON NULL INPUT;

CREATE UNIQUE INDEX IF NOT EXISTS unique_table_example ON table_example(inListExistOrNull(data->'test'));

【问题讨论】:

  • 为什么不简单地规范化您的模型并创建适当的唯一索引?

标签: postgresql unique-index


【解决方案1】:

一个问题是您的函数并不是真正不可变的,当您将其标记为不可变时,您是在欺骗 PostgreSQL。 IMMUTABLE 意味着它总是必须为相同的参数返回相同的结果,无论数据库中有什么数据以及如何配置。

另一个问题是索引不会按照你的意愿去做:它只会在第二次你插入一个函数结果为TRUE的值时抱怨。这就是您在这里的体验。

您可以使用具有类似功能的AFTER INSERT OR UPDATE 触发器来代替索引(如果遇到重复则引发异常),这将满足您的需求。然后你就可以按你应该的方式给函数STABLE打上标签,一切都应该正常了。

【讨论】:

    【解决方案2】:

    因为您在函数的输出上创建索引,所以...

    1. 您创建记录 - 函数返回 null
    2. 您创建另一个(应该会失败) - 函数返回 true - 没有违反唯一约束
    3. 您创建第三条记录 - 函数返回 true - 违反了唯一约束并且未创建记录。

    你应该添加raise expcetion或类似的东西。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-12-03
      • 1970-01-01
      • 1970-01-01
      • 2019-12-04
      • 1970-01-01
      • 1970-01-01
      • 2013-05-25
      相关资源
      最近更新 更多