列引用 ‘updated_at’ 在Postgres中是模糊的。

huangapple go评论81阅读模式
英文:

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

fiddle

英文:

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
> ```

fiddle

答案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.

huangapple
  • 本文由 发表于 2023年6月26日 01:06:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/76551567.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定