【问题标题】:How to pass a record to a PL/pgSQL function?如何将记录传递给 PL/pgSQL 函数?
【发布时间】:2017-09-28 10:54:44
【问题描述】:

我有 8 个类似的 PL/pgSQL 函数;它们被用作视图上的INSTEAD OF INSERT/UPDATE/DELETE 触发器以使它们可写。每个视图都组合了一个通用表(在下面的示例中称为“事物”)和一个特殊表(下面的“shape_things”和“flavored_things”)的列。顺便说一下,PostgreSQL 的继承特性不能在我们的例子中使用。

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

herehere 提出了类似的问题,但我认为我无法在我的案例中应用建议的解决方案。)

简化架构

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

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 函数不能接受类型记录”

当函数被称为insert_thing(NEW) 时,给参数类型things 不起作用:“function insert_thing(flavored_view) 不存在”

这里似乎没有简单的转换; insert_thing(NEW::things) 产生“无法将类型 flavored_view 转换为事物”。为每个视图编写一个 CAST 函数会消除我们通过使用辅助函数获得的东西。

有什么想法吗?

【问题讨论】:

  • 首先,有各种优雅的解决方案。您要插入/更新thing所有 列还是仅更新选定的列?你的 Postgres 版本是什么?你的目标是什么?只是让它发挥作用?使用单一功能?优化性能?您是否必须处理可能的独特违规/竞争条件?

标签: postgresql function parameters triggers plpgsql


【解决方案1】:

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

CREATE FUNCTION insert_thing (_thing flavored_view)
   RETURNS int AS
$func$
   INSERT INTO things (name) VALUES ($1.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。例子:

【讨论】:

  • 我可以使用视图的行类型,但是我必须为每个视图编写一个辅助函数,这有悖于目的。然而,ANYELEMENT 正是我所需要的。非常感谢!
【解决方案2】:

基本上,您可以将记录转换为 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));

希望对你有帮助

【讨论】:

  • 谢谢。 hstore 的缺点是它只能很好地处理字符串。例如,它将所有 NULL 值转换为空字符串。
  • 由于转换为 text 并返回,这将更加昂贵且容易出错。但是,@Zilk:hstore can deal with NULL values properlyA value (but not a key) can be an SQL NULLkey => NULL
  • 你是对的,它可以。不知道我之前测试时出了什么问题……我的错。
  • 我基本上是在研究 greenplum 而 hstore 类型在那里不存在
【解决方案3】:

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

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

【讨论】:

  • 你能分享那个文档的补丁吗?还是一些工作示例?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-01
  • 1970-01-01
  • 2021-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-29
相关资源
最近更新 更多