Postgresql 带引号的列名的Postgres函数

Postgresql 带引号的列名的Postgres函数,postgresql,plpgsql,Postgresql,Plpgsql,TL;医生: 我试图将一个或多个列的名称和值传递到Postgres中的函数中,该函数用于检查约束。这似乎可以正常工作,直到当我收到“column“x”not exists”消息时,列名需要引用(即包含大写字母)。如果我引用标识符,函数的行为就会改变 如果列标识符需要引用,我似乎找不到一种方法来引用从check约束调用的函数中的列名及其值 完整故事: 在Postgres中,我尝试使用用户定义的函数和检查约束来模拟唯一约束 我之所以要这样做,是因为我需要一个“有条件的”唯一性约束,如果检查中的其他

TL;医生:

我试图将一个或多个列的名称和值传递到Postgres中的函数中,该函数用于检查约束。这似乎可以正常工作,直到当我收到“column“x”not exists”消息时,列名需要引用(即包含大写字母)。如果我引用标识符,函数的行为就会改变

如果列标识符需要引用,我似乎找不到一种方法来引用从check约束调用的函数中的列名及其值


完整故事:

在Postgres中,我尝试使用用户定义的函数和检查约束来模拟唯一约束

我之所以要这样做,是因为我需要一个“有条件的”唯一性约束,如果检查中的其他条件得到/没有得到满足,那么唯一性可能不会被强制

(我很感激一个显而易见的答案可能是“你不想这样做”或“这是个坏主意”,但我希望答案能更直接地解决我面临的问题。)

当前尝试:

由于unique中可能包含多个列,因此我创建了一个函数,该函数接受一个表、一个列数组和一个值数组

CREATE OR REPLACE FUNCTION is_unique(_table text, _columns text[], _values text[]) RETURNS boolean AS
$$
DECLARE 
    result integer;
    statement text = 'SELECT (EXISTS (SELECT 1 FROM ' || quote_ident(_table) || ' WHERE ';
    first boolean = true;
BEGIN
    FOR i IN array_lower(_columns,1)..array_upper(_columns,1) LOOP
        IF first THEN
            statement = statement || quote_ident(_columns[i]) || '=' || _values[i];
            first = false;
        ELSE
            statement = statement || ' AND '|| quote_ident(_columns[i]) || '=' || _values[i];
        END IF;
    END LOOP;
    statement = statement || '))::int';
    EXECUTE statement INTO result;
    RETURN NOT result::boolean;
END
$$
LANGUAGE 'plpgsql';
在这个函数中,我要做的是形成以下形式的语句:

SELECT 1 FROM _table WHERE _column[i]=_value[i] AND ...
然后可以将其用作检查约束的一部分,例如:

CHECK (is_unique('sometable'::text,'{somecolumn}'::text[],ARRAY[somecolumn]::text[]))
发生了什么:

当与不需要引用的列一起使用时,此结构似乎可以工作,但在其他情况下,此行为似乎会中断。插入一行后,即使值是唯一的,也无法插入另一行。我认为问题在于可能正在将列的值与自身进行比较,或者正在比较标识符


对于如何更改代码以解决此问题,有人有什么建议吗?不幸的是,在这种情况下,处理引用的标识符很重要

我想你可能真的在找或。描述有点含糊不清,无法真正说明——没有样本数据,没有“这应该被允许,这不应该”的例子,等等

考虑:

CREATE UNIQUE INDEX some_idx_name
ON some_table (col1, col2, col3) WHERE (col1 != 4 AND col5 IS NOT NULL);
尝试使用检查约束和函数模拟唯一索引注定会失败。这甚至不是“你不想这样做”,而是“这根本无法实现”

唯一约束和索引部分不受事务可见性规则的约束。在创建第一个副本的事务尚未提交的唯一索引中插入副本的尝试将被阻止,直到第一个事务提交或回滚。这就是为什么即使事务不能看到彼此未提交的数据,唯一约束仍然有效。您无法模仿这一点,因为PostgreSQL不为事务提供脏读隔离,根本没有办法做到这一点。(好的,如果你用C编写了检查约束函数,你可以这样做,但是它会有恶劣的竞争条件)

唯一可以做你想做的事情的方法是,如果你
锁定表。。。在执行任何操作之前,请在函数中以独占模式
。不这样做肯定会导致并发相关的错误。但是,如果您使用独占锁,那么所有写入操作都必须连续进行,一次只有一个事务对表进行未提交的更改。更糟糕的是,由于锁升级造成的死锁,并发写入的尝试通常会导致事务中止

因此,唯一可靠的方法是让应用程序
锁表。。。在事务开始时,在使用其他锁之前,如果它认为可能需要写入,则在独占模式下
。我相信你可以想象这对表演来说有多有趣

(顺便说一句,在检查约束中调用的函数严格来说应该是
不可变的
,并且不访问它们传递的参数以外的数据。PostgreSQL目前不会阻止您违反该规则,因为访问几乎总是不变的查找表等非常方便,但它确实意味着您可能会从
如果查看可能更改的数据,请检查约束。)


此外,该函数的效率非常低——当您真的不需要循环时,您可以使用一些简单的SQL进行循环(此外,为了后面的人,请缩进您的代码)

该区块:

FOR i IN array_lower(_columns,1)..array_upper(_columns,1) LOOP
  IF first THEN
    statement = statement || quote_ident(_columns[i]) || '=' || _values[i];
    first = false;
  ELSE
    statement = statement || ' AND '|| quote_ident(_columns[i]) || '=' || _values[i];
  END IF;
END LOOP;
这只是一种缓慢的写作方式:

SELECT
  string_agg( 
    format('%I = %L', _columns[i], _values[i]),
    ' AND '
    ORDER BY i
  )
FROM generate_subscripts(_columns, 1) i;
但即便如此,仍然存在一个bug:如果用户通过
NULL
,您将生成
=NULL
,这显然是错误的。您需要使用特殊的
NULL
值,或者使用与
不同的
,例如

format('%I IS DISTINCT FROM %L', _columns[i], _values[i])
但是,
不同于
不能使用索引,因此,
案例
可能更合适:

CASE
  WHEN _values[i] IS NOT NULL THEN
    format('%I = %L', _columns[i], _values[i]),
  ELSE
    format('%I IS NULL', _columns[i])
END

您的函数(可能更简单,但是)可以工作--您听说过部分唯一索引吗?部分索引可能很有用,但要进一步跟进您的建议--当唯一性测试工作时,我似乎无法使用它创建检查。如果我尝试
ALTER TABLE“test TABLE”添加检查(is_unique('test TABLE',array['int col'],数组['int col']));
在=”的右半部分有一个语法错误。我如何解决这个问题?@obfuscation,语法错误的确切文本是…?我仍然不相信这个使用动态SQL的函数是最好的解决方案。你是否尝试过用部分索引解决它?你提到你正在准备一个更完整的测试用例。你能发布一个fiddl吗旁白:不要引用语言名称。
language plpgsql
是正确的。感谢您的评论–我理解这种情况是非常不寻常的,我正在尝试做一些我不应该做的事情,同时也没有很好地解释我的动机(这可能会让人更加困惑).你能看出我在做什么错事吗