插入一个 COALESCE(NULL,default)

Posted

技术标签:

【中文标题】插入一个 COALESCE(NULL,default)【英文标题】:Inserting a COALESCE(NULL,default) 【发布时间】:2016-08-26 11:47:59 【问题描述】:

我有使用 UUID 的表。我希望能够插入带有或不带有 UUID 的新行,因为有时客户端会生成 UUID,而有时它不会。

每张桌子的核心都是这个:

CREATE TABLE IF NOT EXISTS person (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid()
);

我正在尝试使用函数来插入行。我希望能够提供一个 NULL id 并获得一个默认值(生成的 UUID)。我有这样的事情:

CREATE OR REPLACE FUNCTION create_person(
    id UUID
) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $$
BEGIN
    INSERT INTO person( id )
    VALUES (
        COALESCE(id,default)
    );
    RETURN FOUND;
END;
$$;

我试过了:

INSERT INTO person ( id ) VALUES (
    COALESCE(id, default),
);

还有这个:

INSERT INTO person ( id ) VALUES (
  CASE WHEN id IS NULL THEN default ELSE id END
);

这可行,但它重复了 gen_random_uuid() 代码:

INSERT INTO person ( id ) VALUES (
  COALESCE(id, gen_random_uuid()),
);

同样,这也有效,但有同样的问题:

INSERT INTO person ( id ) VALUES (
    CASE WHEN id IS NULL THEN gen_random_uuid() ELSE id END
);

有没有办法让我不必重复 gen_random_uuid() 代码?

使用plpgsql 会更好吗?

【问题讨论】:

当您使用 PL/pgSQL 时,我会使用 if id is not null then insert .. values (id) else insert ... values (default) 这是什么default?我看不到它在哪里定义。那是可见的吗?你能打印出来吗?另一个角度是:我会尝试将此 default 命名为其他不是 SQL 关键字的名称,例如 default_value @tamas-rev 这是我想要的 default 关键字。我希望 Postgres 在那里插入一个默认值(在 CREATE TABLE 中定义)。 我认为触发器可以帮助你。例如,他们可以为您运行 before insert。我无法测试它,因为我的机器上没有 postgres。不过,this page 是一个不错的起点。 幸运的第三次我认为 tamas 有正确的答案。 BEFORE INSERT 触发器让您可以更改任何插入的值并将空值替换为新值。 【参考方案1】:

无法重新使用列上定义的默认值。默认值仅用于定义INSERT 未指定值时会发生什么。根据这个定义,null 值仍然是“指定的”,因此不能使用默认值。

您对某人可能不会使用该函数的评论表明触发器比简单函数更符合您的要求。

https://www.postgresql.org/docs/current/static/plpgsql-trigger.html

CREATE OR REPLACE FUNCTION default_id() RETURNS TRIGGER AS $default_id$
    BEGIN       
        IF (NEW.id IS NULL) THEN
            NEW.id := gen_random_uuid();
        END IF;
        RETURN NEW;
    END;
$default_id$ LANGUAGE plpgsql;

CREATE TRIGGER default_id_trigger
BEFORE INSERT OR UPDATE ON person
    FOR EACH ROW EXECUTE PROCEDURE default_id();

如果您确实想使用函数执行此操作,那么最简单的方法就是在插入之前分配值:

CREATE OR REPLACE FUNCTION create_person(
    id UUID
) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $$
BEGIN
    IF id IS NULL THEN
        id := gen_random_uuid();
    END IF;
    -- OR
    -- id := coalesce(id, gen_random_uuid());
    INSERT INTO person( id )
    VALUES (id);
    RETURN FOUND;
END;
$$;

【讨论】:

看来这也行不通。 idPRIMARY KEY,因此不能是 NULLPostgreSQL said: null value in column "id" violates not-null constraint 您确定您创建了触发器BEFORE INSERT 而不是AFTER INSERT。我刚刚对此进行了测试,它适用于INTEGER,所以我看不出为什么它不适用于UUID 确实有。现在这对我来说似乎很好用。谢谢! @Sambeau 请注意,触发器在数据库设计中是一个不起眼的元素,对于没有经验的 SQL 开发人员来说,它可能会在多个方面造成混淆。 @clodoaldo-neto 我意识到了。谢谢你的帮助。这对我来说主要是一个学习练习,也是一个使用客户端生成的 UUID 和表格修订的实验。它还没有投入生产。【参考方案2】:

核心问题是附加到INSERTVALUES 表达式中的关键字DEFAULT 的特殊性质。 Per documentation:

在出现在INSERT 顶层的VALUES 列表中, 表达式可以替换为DEFAULT,表示目的地 应该插入列的默认值。 DEFAULT 不能在以下情况下使用 VALUES 出现在其他上下文中。

我的大胆强调。具体来说,DEFAULT 不能作为函数的参数:

<strike>COALESCE(<i>function_parameter</i>, DEFAULT)</strike>  -- not possible

可能的解决方案

有多种方法,具体取决于确切要求。

这个函数不需要知道person.id的实际默认值——这似乎是你所追求的:

CREATE OR REPLACE FUNCTION create_person(_id UUID)
  RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS
$func$
BEGIN
   IF _id IS NULL THEN                 -- no UUID provided
      INSERT INTO myschema.person(id)  -- see below about schema name
      VALUES (DEFAULT);                -- use the key word DEFAULT
   ELSE                                -- UUID provided
      INSERT INTO myschema.person(id)
      VALUES (_id);
   END IF;

   RETURN FOUND;                       -- (return value pointless so far)
END
$func$;

避免对参数和涉及的表列使用相同的名称。由于函数参数在函数体中的每个 SQL 命令中都是可见的,因此可能会导致非常混乱的命名冲突(即使 INSERT 的目标列在现代 Postgres 中不受此限制)。我改用_id 作为参数名。

INSERT 中未提及的其他列的默认值自动填写。我使用关键字DEFAULT,因为我们需要为INSERT 列出至少一个目标列。

boolean 返回值在此演示中毫无意义,因为它总是 true(除非您有可能跳过该行的触发器)。

可能的替代方案和大量解释的相关答案:

Generate DEFAULT values in a CTE UPSERT using PostgreSQL 9.3

另外:您应该在 SECURITY DEFINER 函数中对 all 函数和表名进行模式限定 - (如果您不确定,可能会更好)明确设置search_path 以防御可能的攻击。更多:

How does the search_path influence identifier resolution and the "current schema"

【讨论】:

【参考方案3】:

重载gen_random_uuid函数:

create or replace function gen_random_uuid()
returns uuid as $$
    select 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid;
$$ language sql;


create or replace function gen_random_uuid(_id uuid)
returns uuid as $$
    select coalesce(_id, gen_random_uuid());
$$ language sql;

照常创建表格:

create table if not exists person (
    id uuid default gen_random_uuid()
);

create_person函数内部调用gen_random_uuid函数:

create or replace function create_person(_id uuid)
returns boolean as $$
    insert into person (id) values (gen_random_uuid(_id))
    returning true;
$$ language sql;

然后所有插入变体都将起作用:

insert into person (id) values (default);
select create_person(null);
select create_person('b1eebc99-9c0b-4ef8-bb6d-6bb9bd380a22'::uuid);

【讨论】:

有时我必须将 id 传递给 create 函数。这就是使用 UUID 的重点。有时服务器会创建一个对象,有时客户端会。 @Sambeau 听起来设计很糟糕 这并不能回答这个问题,因为它只是因为你从 id 中删除了PRIMARY KEY。如果你把它放回去,你会得到ERROR: duplicate key value violates unique constraint "person_pkey"DETAIL: Key (id)=(a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11) already exists. @Sambeau 我删除了主键,因为我的虚拟 gen_random_uuid 函数总是返回相同的 uuid。如果它生成一个随机的,它将使用主键。 我很抱歉。我现在看到了。这确实有效。

以上是关于插入一个 COALESCE(NULL,default)的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server中COALESCE函数的用法

如果一列为 Null,则 SQL 返回 Null(与 COALESCE() 相反)

Coalesce(null,'') 在 Oracle 中获取 null 并在 SQL Server 中获取 ''?

ORACLE中NVLl和COALESCE的区别

处理空值,把 Null 值转换为实际值(coalesce,nvl 函数用法)

mysql coalesce函数