插入COALESCE(NULL,默认值)

Sam*_*eau 8 sql postgresql plpgsql

我有使用UUID的表.我希望能够在有或没有UUID的情况下插入新行,因为有时客户端会生成UUID,有时则不会.

每个表都有它的核心:

CREATE TABLE IF NOT EXISTS person (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid()
);
Run Code Online (Sandbox Code Playgroud)

我正在尝试使用函数来插入行.我希望能够传递一个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;
$$;
Run Code Online (Sandbox Code Playgroud)

我试过这个:

INSERT INTO person ( id ) VALUES (
    COALESCE(id, default),
);
Run Code Online (Sandbox Code Playgroud)

还有这个:

INSERT INTO person ( id ) VALUES (
  CASE WHEN id IS NULL THEN default ELSE id END
);
Run Code Online (Sandbox Code Playgroud)

这有效,但它重复gen_random_uuid()代码:

INSERT INTO person ( id ) VALUES (
  COALESCE(id, gen_random_uuid()),
);
Run Code Online (Sandbox Code Playgroud)

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

INSERT INTO person ( id ) VALUES (
    CASE WHEN id IS NULL THEN gen_random_uuid() ELSE id END
);
Run Code Online (Sandbox Code Playgroud)

有没有办法在不重复gen_random_uuid()代码的情况下执行此操作?

这会更好plpgsql吗?

Phi*_*ing 6

无法重新使用列上定义的默认值.默认值仅用于定义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();
Run Code Online (Sandbox Code Playgroud)

如果你想用函数做这个,那么最简单的方法就是在插入之前分配值:

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;
$$;
Run Code Online (Sandbox Code Playgroud)


Erw*_*ter 6

核心问题是关键字的特殊性质DEFAULT在一个VALUES连接到表达INSERT.每个文件:

VALUES出现在a 的顶层的列表中INSERT,表达式可以替换DEFAULT为表示应插入目标列的默认值.在其他情境中出现DEFAULT时无法使用 VALUES.

大胆强调我的.具体来说,DEFAULT不能是函数的参数:

COALESCE(function_parameter, DEFAULT)  -- not possible
Run Code Online (Sandbox Code Playgroud)

可能解决方案

根据具体要求,有多种方式.

这个函数不需要知道实际的默认值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$;
Run Code Online (Sandbox Code Playgroud)

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

未在其中提及的其他列的默认值将自动INSERT填写.我使用关键词是因为我们需要列出至少一个目标列.DEFAULTINSERT

boolean返回值是在这个演示毫无意义的,因为它总是 true(除非你有可能会跳过行触发器).

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

除此之外:您应该对函数中的所有函数和表名进行模式限定SECURITY DEFINER- 或者(如果您不确定,可能更好)明确设置search_path以防止可能的攻击.更多: