如何消除 ON CONFLICT 子句中 plpgsql 变量名称的歧义?

问题描述 投票:0回答:2

鉴于此表:

create table test (
    name text primary key
);

我需要编写一个 plpgsql 函数,其变量名称与主键名称冲突,我必须在

on conflict
子句中使用它:

create or replace function func(
    name text                               -- this variable name...
) returns void language plpgsql as 
$$
begin 
    insert into test (name) values (name) 
    on conflict (name) do update            -- ...conflicts with this line
    set name = func.name; 
end; 
$$;

这可以编译,但随后会抛出一个不明确的列引用:

select * from func('one');
ERROR:  column reference "name" is ambiguous
LINE 2:     on conflict (name) do update 
                        ^
DETAIL:  It could refer to either a PL/pgSQL variable or a table column.
QUERY:  insert into test (name) values (name) 
    on conflict (name) do update 
    set name = func.name
CONTEXT:  PL/pgSQL function func(text) line 3 at SQL statement

我尝试将完整的列名称指定为

on conflict (test.name)
(不编译)或
((test.name))
(编译):

create or replace function func(
    name text
) returns void language plpgsql as 
$$
begin 
    insert into test (name) values (name) 
    on conflict ((test.name)) do            -- this fails too
    update set name = func.name; 
end; 
$$;

但它也失败了:

select * from func('two');
ERROR:  invalid reference to FROM-clause entry for table "test"
LINE 2:     on conflict ((test.name)) do 
                          ^
HINT:  There is an entry for table "test", but it cannot be referenced from this part of the query.
QUERY:  insert into test (name) values (name) 
    on conflict ((test.name)) do 
    update set name = func.name
CONTEXT:  PL/pgSQL function func(text) line 3 at SQL statement

有解决办法吗?


编辑:我找到了解决方法:

on conflict on constraint test_pkey do update

其中

test_pkey
是表名称加上
_pkey
。虽然我不知道这有多可靠。我仍然想指定列名称。

sql postgresql plpgsql upsert name-conflict
2个回答
9
投票

首先,

name
对于变量和属性来说都是一个不好的名字。当两者都存在时,代码看起来不会很好。考虑到这一点,您可以使用标记块“前缀”变量(在下面的示例中
<<fn>>
),并设置
variable_conflict
以优先考虑列名称,请参阅下面的代码:

t=# create or replace function func(
    name text
) returns void language plpgsql as
$$
#variable_conflict use_column
<<fn>>
declare name text :='blah';
begin
    insert into test (name) values (name)
    on conflict (name) do            -- this no longer fails
    update set name = fn.name;
end;
$$;
t=# insert into test select 'b';
INSERT 0 1
Time: 8.076 ms
t=# select func('b');
 func
------

(1 row)

Time: 6.117 ms
t=# select * from test;
 name
------
 b
 blah
(2 rows)

https://www.postgresql.org/docs/current/static/plpgsql-implementation.html#PLPGSQL-VAR-SUBST

默认情况下,如果 SQL 语句中的名称 可以引用变量或表列。你可以修复这样一个 通过重命名变量或列,或者通过限定 不明确的引用,或者通过告诉 PL/pgSQL 哪种解释来 更喜欢。

更进一步 - 基本上整个链接都是关于它的。

然而 - 在演示了如何使用 plpgsql 轻松完成特定任务后,我仍然引用 namual:

最简单的解决方案是重命名变量或列。普通的 编码规则是对 PL/pgSQL 使用不同的命名约定 变量比您用于列名的变量。例如,如果您 一致地命名函数变量 v_something 而你的函数变量都没有 列名以v_开头,不会发生冲突。


2
投票

ON CONFLICT...
语法(如此处记录)使用唯一约束来确定行是否冲突。您可以通过列出它包含的列(此时 Postgres“推断”要使用的正确索引)或直接命名约束来指定此唯一约束。

在您的情况下,使用的唯一约束是在

CREATE TABLE
语句期间隐式创建的主键约束。除非您直接指定,否则 DBMS 将为其指定一个名称;因此,您需要查找 DBMS 为其指定的名称(请注意,如果您稍后重新创建模式,这可能会发生变化),或者在使用语法
CONSTRAINT pk_some_name PRIMARY KEY
.

创建表时显式命名它。

然后,您可以将该子句指定为

ON CONFLICT ON CONSTRAINT pk_some_name DO ...
(注意约束名称周围没有括号)。

(当然,您也可以更改函数以使用明确的参数名称;就我个人而言,我认为使用

p_
in_
等前缀是很好的做法,而不是根据具体情况处理冲突.)

© www.soinside.com 2019 - 2024. All rights reserved.