如何将记录传递给 PL/pgSQL 函数?

How to pass a record to a PL/pgSQL function?

我有8个类似的PL/pgSQL函数;它们用作视图的 INSTEAD OF INSERT/UPDATE/DELETE 触发器,使它们成为 writable。每个视图都组合了一个通用 table(在下面的示例中称为 "things")和一个特殊 table(下面的 "shaped_things" 和 "flavored_things")的列。顺便说一句,PostgreSQL 的继承特性不能用在我们的例子中。

触发器必须在通用 table 中 insert/update 行;这些部分在所有 8 个功能中都是相同的。由于通用 table 有 ~30 列,我试图在那里使用辅助函数,但我无法将视图的 NEW 记录传递给需要 things 的函数记录为输入。

(有人问过类似的问题 here and here,但我认为我不能在我的案例中应用建议的解决方案。)

简化架构

CREATE TABLE things (
    id    SERIAL  PRIMARY KEY,
    name  TEXT    NOT NULL
    -- (plus 30 more columns)
);
CREATE TABLE flavored_things (
    thing_id  INT   PRIMARY KEY REFERENCES things (id) ON DELETE CASCADE,
    flavor    TEXT  NOT NULL
);
CREATE TABLE shaped_things (
    thing_id  INT   PRIMARY KEY REFERENCES things (id) ON DELETE CASCADE,
    shape     TEXT  NOT NULL
);
-- etc...

Writable 查看 flavored_things

的实现
CREATE VIEW flavored_view AS
    SELECT t.*,
           f.*
      FROM things t
      JOIN flavored_things f ON f.thing_id = t.id;

CREATE FUNCTION flavored_trig () RETURNS TRIGGER AS $fun$
DECLARE
    inserted_id  INT;
BEGIN
    IF TG_OP = 'INSERT' THEN
        INSERT INTO things VALUES (  -- (A)
            DEFAULT,
            NEW.name
            -- (plus 30 more columns)
        ) RETURNING id INTO inserted_id;
        INSERT INTO flavored_things VALUES (
            inserted_id,
            NEW.flavor
        );
        RETURN NEW;
    ELSIF TG_OP = 'UPDATE' THEN
        UPDATE things SET  -- (B)
            name = NEW.name
            -- (plus 30 more columns)
        WHERE id = OLD.id;
        UPDATE flavored_things SET
            flavor = NEW.flavor
        WHERE thing_id = OLD.id;
        RETURN NEW;
    ELSIF TG_OP = 'DELETE' THEN
        DELETE FROM flavored_things WHERE thing_id = OLD.id;
        DELETE FROM things WHERE id = OLD.id;
        RETURN OLD;
    END IF;
END;
$fun$ LANGUAGE plpgsql;

CREATE TRIGGER write_flavored
    INSTEAD OF INSERT OR UPDATE OR DELETE ON flavored_view
    FOR EACH ROW EXECUTE PROCEDURE flavored_trig();

上面标记为“(A)”和“(B)”的语句是我想用辅助函数调用替换的内容。

INSERT 的辅助函数

我最初的尝试是将语句“(A)”替换为

inserted_id = insert_thing(NEW);

使用这个函数

CREATE FUNCTION insert_thing (new_thing RECORD) RETURNS INTEGER AS $fun$
DECLARE
    inserted_id  INT;
BEGIN
    INSERT INTO things (name) VALUES (
        new_thing.name
        -- (plus 30 more columns)
    ) RETURNING id INTO inserted_id;
    RETURN inserted_id;
END;
$fun$ LANGUAGE plpgsql;

失败并显示错误消息 "PL/pgSQL functions cannot accept type record".

当函数被调用为 insert_thing(NEW) 时,给参数类型 things 不起作用:"function insert_thing(flavored_view) does not exist".

此处似乎无法进行简单的转换; insert_thing(NEW::things) 产生 "cannot cast type flavored_view to things"。为每个视图编写一个 CAST 函数会删除我们通过使用辅助函数获得的内容。

有什么想法吗?

基本上您可以将记录转换为 hstore 变量并将 hstore 变量而不是记录变量传递给函数。您将记录转换为 hstore 即:

DECLARE r record; h hstore;
h = hstore(r);

您的辅助函数也应更改为:

CREATE FUNCTION insert_thing (new_thing hstore) RETURNS INTEGER AS $fun$
DECLARE
    inserted_id  INT;
BEGIN
    INSERT INTO things (name) VALUES (
        new_thing -> 'name'
        -- (plus 30 more columns)
    ) RETURNING id INTO inserted_id;
    RETURN inserted_id;
END;
$fun$ LANGUAGE plpgsql;

然后调用:

inserted_id = insert_thing(hstore(NEW));

希望对您有所帮助

有多种选择,要看完整图。
基本上,您的插入函数可以像这样工作:

CREATE FUNCTION insert_thing (_thing <b>flavored_view</b>)
   RETURNS int AS
$func$
   INSERT INTO things (name) VALUES (.name) -- plus 30 more columns
   RETURNING id;
$func$ LANGUAGE sql;

使用视图的行类型,因为你的触发器中的NEW就是这种类型。
使用简单的 SQL 函数,它可以内联并且性能可能更好。

演示电话:

SELECT insert_thing('(1, foo, 1, bar)');

在你的触发器中 flavored_trig ():

inserted_id := insert_thing(NEW);

或者,基本上重写:

IF TG_OP = 'INSERT' THEN
   INSERT INTO flavored_things(thing_id, flavor)
   VALUES (insert_thing(NEW), NEW.flavor);
   RETURN NEW;
ELSIF ...

record 不是 PL/pgSQL 之外的有效类型,它只是 PL/pgSQL 中未知行类型的通用占位符)因此您不能将它用作函数声明中的输入参数。

对于接受各种行类型 的更动态的函数,您可以使用 polymorphic type。示例:

  • How to return a table by rowtype in PL/pgSQL
  • Refactor a PL/pgSQL function to return the output of various SELECT queries
  • How to write a function that returns text or integer values?

复合类型。 PostgresSQL 有这方面的文档,你基本上需要使用像

这样的东西

'()' 或 ROW() 构造要传递给函数的行的复合类型。