英文:
Column reference 'updated_at' is ambiguous in Postgres
问题
我试图执行一次upsert操作:
但是我遇到了错误:
{
code: '42702',
details: '它可能是指PL/pgSQL变量或表列。',
hint: null,
message: '列引用 "updated_at" 有歧义'
}
我已经在我所知道的所有地方使用了别名。如何在不为我的 upsert_post
参数添加前缀 i_
或类似名称的情况下使其工作?
英文:
I'm trying to upsert a post:
DROP FUNCTION IF EXISTS upsert_post;
CREATE OR REPLACE FUNCTION upsert_post(
title text,
content text,
short_id text DEFAULT NULL,
image text DEFAULT NULL,
status post_status DEFAULT 'draft',
updated_at timestamptz DEFAULT NULL,
published_at timestamptz DEFAULT NULL,
created_at timestamptz DEFAULT NULL
)
RETURNS SETOF posts
AS $$
DECLARE
post_id uuid;
BEGIN
-- Get the id based on the provided short_id
SELECT id INTO post_id FROM posts as p
WHERE p.short_id = upsert_post.short_id;
-- Upsert the post and return the changed row
RETURN QUERY
INSERT INTO posts as i (
id,
status,
title,
content,
image,
updated_at,
published_at,
created_at
)
VALUES (
COALESCE(post_id, uuid_generate_v4()),
upsert_post.status,
upsert_post.title,
upsert_post.content,
upsert_post.image,
COALESCE(upsert_post.updated_at, now()),
COALESCE(upsert_post.published_at, now()),
COALESCE(upsert_post.created_at, now())
)
ON CONFLICT (id, updated_at) DO UPDATE
SET
title = EXCLUDED.title,
content = EXCLUDED.content,
image = COALESCE(EXCLUDED.image, i.image),
updated_at = COALESCE(EXCLUDED.updated_at, i.updated_at),
published_at = COALESCE(EXCLUDED.published_at, i.published_at),
created_at = COALESCE(EXCLUDED.created_at, i.created_at)
RETURNING *;
END;
$$ LANGUAGE plpgsql;
But I get the error:
{
code: '42702',
details: 'It could refer to either a PL/pgSQL variable or a table column.',
hint: null,
message: 'column reference "updated_at" is ambiguous'
}
I have used aliases everywhere I know possible. How can I get this working without renaming my upsert_post
parameters with a prefix i_
or something similar?
答案1
得分: 0
我需要调整函数,因为提供了用户输入和函数,并且创建表格也会有所帮助。
简而言之,您不应该将变量命名为列名,这样PostgreSQL就不会再有问题区分它们。
关于short_id的想法,我只能猜测。但我认为在插入新行时,您也应该输入它。
CREATE TABLE posts (id text PRIMARY KEY,
status text DEFAULT 'draft',
title text,
content text,
short_id text DEFAULT NULL,
image text DEFAULT NULL,
updated_at timestamptz DEFAULT NULL,
published_at timestamptz DEFAULT NULL,
created_at timestamptz DEFAULT NULL,
UNIQUE (id, updated_at)
)
CREATE OR REPLACE FUNCTION upsert_post(
f_title text,
f_content text,
f_short_id text DEFAULT NULL,
f_image text DEFAULT NULL,
f_status text DEFAULT 'draft',
f_updated_at timestamptz DEFAULT NULL,
f_published_at timestamptz DEFAULT NULL,
f_created_at timestamptz DEFAULT NULL
)
RETURNS SETOF posts
AS $$
DECLARE
post_id uuid;
BEGIN
-- 根据提供的short_id获取id
SELECT id INTO post_id FROM posts as p
WHERE p.short_id = f_short_id;
-- 更新帖子并返回已更改的行
RETURN QUERY
INSERT INTO posts as i (
id,
status,
title,
content,
image,
updated_at,
published_at,
created_at
)
VALUES (
COALESCE(post_id, gen_random_uuid ()),
f_status,
f_title,
f_content,
f_image,
COALESCE(f_updated_at, now()),
COALESCE(f_published_at, now()),
COALESCE(f_created_at, now())
)
ON CONFLICT (id, updated_at) DO UPDATE
SET
title = EXCLUDED.title,
content = EXCLUDED.content,
image = COALESCE(EXCLUDED.image, i.image),
updated_at = COALESCE(EXCLUDED.updated_at, i.updated_at),
published_at = COALESCE(EXCLUDED.published_at, i.published_at),
created_at = COALESCE(EXCLUDED.created_at, i.created_at)
RETURNING *;
END;
$$ LANGUAGE plpgsql;
SELECT
routine_name
FROM
information_schema.routines
WHERE
routine_type = 'FUNCTION'
AND
routine_schema = 'public';
routine_name |
---|
upsert_post |
SELECT upsert_post('A'::TEXT,'b'::TEXT,'C'::TEXT,'D'::TEXT,'draft'::TEXT,NOW(),NOW(), NOW())
upsert_post |
---|
(f9bb1bd4-62f7-4380-b088-5185db0fca40,draft,A,b,,D,"2023-06-25 18:50:17.61884+01","2023-06-25 18:50:17.61884+01","2023-06-25 18:50:17.61884+01") |
SELECT * FROM posts
id | status | title | content | short_id | image | updated_at | published_at | created_at |
---|---|---|---|---|---|---|---|---|
f9bb1bd4-62f7-4380-b088-5185db0fca40 | draft | A | b | null | D | 2023-06-25 18:50:17.61884+01 | 2023-06-25 18:50:17.61884+01 | 2023-06-25 18:50:17.61884+01 |
英文:
i needed to adept the function, as user typ and function are supplied and create table would also help much
In short you should never name your variables like column names, so that postgres has no more problem differentiate them
the idea behind short_id i can only guess. but i think you should also enter it, when inserting a new row
CREATE TABLe posts (id text PRIMARY KEY,
status text DEFAULT 'draft',
title text,
content text,
short_id text DEFAULT NULL,
image text DEFAULT NULL,
updated_at timestamptz DEFAULT NULL,
published_at timestamptz DEFAULT NULL,
created_at timestamptz DEFAULT NULL,
UNIQUE (id, updated_at)
)
> status
> CREATE TABLE
>
CREATE OR REPLACE FUNCTION upsert_post(
f_title text,
f_content text,
f_short_id text DEFAULT NULL,
f_image text DEFAULT NULL,
f_status text DEFAULT 'draft',
f_updated_at timestamptz DEFAULT NULL,
f_published_at timestamptz DEFAULT NULL,
f_created_at timestamptz DEFAULT NULL
)
RETURNS SETOF posts
AS $$
DECLARE
post_id uuid;
BEGIN
-- Get the id based on the provided short_id
SELECT id INTO post_id FROM posts as p
WHERE p.short_id = f_short_id;
-- Upsert the post and return the changed row
RETURN QUERY
INSERT INTO posts as i (
id,
status,
title,
content,
image,
updated_at,
published_at,
created_at
)
VALUES (
COALESCE(post_id, gen_random_uuid ()),
f_status,
f_title,
f_content,
f_image,
COALESCE(f_updated_at, now()),
COALESCE(f_published_at, now()),
COALESCE(f_created_at, now())
)
ON CONFLICT (id, updated_at) DO UPDATE
SET
title = EXCLUDED.title,
content = EXCLUDED.content,
image = COALESCE(EXCLUDED.image, i.image),
updated_at = COALESCE(EXCLUDED.updated_at, i.updated_at),
published_at = COALESCE(EXCLUDED.published_at, i.published_at),
created_at = COALESCE(EXCLUDED.created_at, i.created_at)
RETURNING *;
END;
$$ LANGUAGE plpgsql;
> status
> CREATE FUNCTION
>
SELECT
routine_name
FROM
information_schema.routines
WHERE
routine_type = 'FUNCTION'
AND
routine_schema = 'public';
routine_name |
---|
upsert_post |
> ``` status |
> SELECT 1 |
> ``` |
SELECT upsert_post('A'::TEXT,'b'::TEXT,'C'::TEXT,'D'::TEXT,'draft'::TEXT,NOW(),NOW(), NOW())
upsert_post |
---|
(f9bb1bd4-62f7-4380-b088-5185db0fca40,draft,A,b,,D,"2023-06-25 18:50:17.61884+01","2023-06-25 18:50:17.61884+01","2023-06-25 18:50:17.61884+01") |
> ``` status |
> SELECT 1 |
> ``` |
SELECT * FROM posts
id | status | title | content | short_id | image | updated_at | published_at | created_at |
---|---|---|---|---|---|---|---|---|
f9bb1bd4-62f7-4380-b088-5185db0fca40 | draft | A | b | null | D | 2023-06-25 18:50:17.61884+01 | 2023-06-25 18:50:17.61884+01 | 2023-06-25 18:50:17.61884+01 |
> ``` status | ||||||||
> SELECT 1 | ||||||||
> ``` |
答案2
得分: 0
我的问题在于输入参数与ON CONFLICT
参数冲突。
最终我找到了三个真正的解决方法。
1. #variable_conflict
Postgres有三个用于此用例的变量:
#variable_conflict error
#variable_conflict use_variable
#variable_conflict use_column
但是多亏了上面提供的变量替代文档,我成功解决了它:
RETURNS SETOF posts
AS $$
#variable_conflict use_column
DECLARE
post_id uuid;
BEGIN
2. ON CONFLICT ON CONSTRAINT
我上面的代码问题在于ON CONFLICT
不允许您与其一起使用别名。如果您知道一种方法,请告诉我。否则,我只是使用了复合约束的名称,而不是ON CONSTRAINT
:
ON CONFLICT ON CONSTRAINT posts_pkey DO UPDATE
编辑:您还可以在模式中为约束指定任何名称:
CREATE TABLE posts (
...
CONSTRAINT id_updated_at_pk PRIMARY KEY (id, updated_at)
...
);
3. 创建子函数
基本上,您可以创建一个名为_upsert_post()
的函数,该函数接受带有前缀的参数。然后,只需将您的非带前缀的参数传递给此函数:
upsert_post
RETURN QUERY SELECT * FROM _upsert_post(id, status, title...);
说必须使用前缀
是不正确的。在我的情况下,用户必须更改前端代码以使用与后端代码不同的参数。这对于团队来说可能很重要。
英文:
My issue here was the input parameter conflicting with the ON CONFLICT
parameter.
There are three real answers I finally found.
1. #variable_conflict
Postgres has three variables to use for this use case:
#variable_conflict error
#variable_conflict use_variable
#variable_conflict use_column
But I got it working thanks to the Variable Substitution Docs provided above:
RETURNS SETOF posts
AS $$
#variable_conflict use_column
DECLARE
post_id uuid;
BEGIN
2. ON CONFLICT ON CONSTRAINT
The problem with my code above was the ON CONFLICT
not allowing you to use aliases with it. If you know a way, please let me know. Otherwise, I simply just used the name of the compound constraint with ON CONSTRAINT
instead.
ON CONFLICT ON CONSTRAINT posts_pkey DO UPDATE
Edit: You could also name the constraint anything you want, in the schema:
CREATE TABLE posts (
...
CONSTRAINT id_updated_at_pk PRIMARY KEY (id, updated_at)
...
);
3. Create a sub function
Basically you could just create a function _upsert_post()
which does take prefixed parameters. Then just pass your non prefixed parameters into this function:
upsert_post
RETURN QUERY SELECT * FROM _upsert_post(id, status, title...);
To say that you have to use prefixes
is simply not true. In my case, the user would have to change the frontend code to use different parameters than the backend code. This can be important for teams.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论