【问题标题】:Access dynamic column name of row type in trigger function在触发函数中访问行类型的动态列名
【发布时间】:2019-08-10 05:41:48
【问题描述】:

我正在尝试创建一个用于设置触发器的动态函数。

CREATE OR REPLACE FUNCTION device_bid_modifiers_count_per()
  RETURNS TRIGGER AS
$$
  DECLARE
    devices_count INTEGER;
    table_name    regclass := TG_ARGV[0];
    column_name   VARCHAR  := TG_ARGV[1];
  BEGIN
    LOCK TABLE device_types IN EXCLUSIVE MODE;
    EXECUTE format('LOCK TABLE %s IN EXCLUSIVE MODE', table_name);

    SELECT INTO devices_count device_types_count();

    IF TG_OP = 'DELETE' THEN
      SELECT format(
        'PERFORM validate_bid_modifiers_count(%s, %s, OLD.%s, %s)',
        table_name,
        column_name,
        column_name,
        devices_count
      );
    ELSE
      SELECT format(
        'PERFORM validate_bid_modifiers_count(%s, %s, NEW.%s, %s)',
        table_name,
        column_name,
        column_name,
        devices_count
      );
    END IF;
    RETURN NEW;
  END;
$$ LANGUAGE plpgsql;

我的问题是动态函数validate_bid_modifiers_count() 的执行。目前它抛出:

ERROR:  query has no destination for result data
HINT:  If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT:  PL/pgSQL function device_bid_modifiers_count_per() line 21 at SQL statement

我真的无法理解这一点。我知道format() 返回带有参数的正确函数调用字符串。我该如何解决这个问题并让它发挥作用?

【问题讨论】:

  • 我试过EXECUTE format('SELECT...)EXECUTE/PERFORM/SELECT的不同组合
  • 请显示您尝试使用EXECUTEPERFORM 时的错误。我不是 PLPGSQL 人,但您肯定不想 SELECT 要运行的代码。错误可能会详细说明这种动态 SQL 是否可能(例如,我怀疑在动态执行的代码范围内甚至可能引用NEWOLD。)

标签: sql postgresql triggers plpgsql dynamic-sql


【解决方案1】:

应该这样做:

CREATE OR REPLACE FUNCTION device_bid_modifiers_count_per()
  RETURNS TRIGGER AS
$func$
DECLARE
   devices_count int      := device_types_count();
   table_name    regclass := TG_ARGV[0];
   column_name   text     := TG_ARGV[1];
BEGIN
   LOCK TABLE device_types IN EXCLUSIVE MODE;
   EXECUTE format('LOCK TABLE %s IN EXCLUSIVE MODE', table_name);

   IF TG_OP = 'DELETE' THEN
      PERFORM validate_bid_modifiers_count(table_name
                                         , column_name
                                         , (row_to_json(OLD) ->> column_name)::bigint
                                         , devices_count);
   ELSE
      PERFORM validate_bid_modifiers_count(table_name
                                         , column_name
                                         , (row_to_json(NEW) ->> column_name)::bigint
                                         , devices_count);
   END IF;

   RETURN NEW;
END
$func$  LANGUAGE plpgsql;

错误消息的直接原因是外部SELECT。没有target,需要在plpgsql中用PERFORM替换。但是传递给EXECUTE 的查询字符串中的内部PERFORM 也是错误的。 PERFORM 是一个 plpgsql 命令,在传递给 EXECUTE 的 SQL 字符串中无效,它需要 SQL 代码。你必须在那里使用SELECT。最后OLDNEWEXECUTE 中是不可见的,并且会按照你的方式引发他们自己的异常。删除EXECUTE即可解决所有问题。

一种从行类型OLDNEW 中获取动态列名 值的简单快速的方法:强制转换为json,然后您可以像这样参数化键名证明了。应该比使用动态 SQL 的替代方案更简单、更快 - 这也是可能的,例如:

  ...
  EXECUTE format('SELECT validate_bid_modifiers_count(table_name
                                                    , column_name
                                                    , ($1.%I)::bigint
                                                    , devices_count)', column_name)
  USING OLD;
  ...

相关:

旁白:不知道为什么需要沉重的锁。

旁白 2:考虑为每个触发器编写单独的触发器函数。更嘈杂的 DDL,但执行起来更简单、更快。

【讨论】:

  • 嘿 Erwin,首先让我感谢您对 SO 的所有回答。我最近开始处理原生 SQL(我总是无耻地使用 ORM),如果我学到了什么——通常是基于你的答案。非常感谢你
  • 关于主题:我发誓这正是我最初所拥有的(除了第二个 lock 语句 - 我将它作为 LOCK TABLE table_name IN EXCLUSIVE MODE。Bun 然后我开始遇到错误,这给我带来了动态评估的“需要”。抱歉措辞错误。无论如何,该解决方案的错误如下:ERROR: record "new" has no field "column_name" CONTEXT: SQL statement "SELECTvalidate_bid_modifiers_count(table_name, column_name, NEW.column_name, devices_count)" PL/pgSQL function device_bid_modifiers_count_per() line 15 at PERFORM
  • @AndreyDeineko:更新以涵盖我最初错过的动态列名称。
  • 感谢您更新的答案!如果它确实对执行速度有任何影响,那么我将使用 2 个单独的触发功能。只是它们的定义实际上是相同的,除了 table_namecolumn_name 参数。我想要一个函数,因为至少在 Ruby 中,我们通常针对的是 DRY 代码。关于锁——函数取决于device_types,所以我锁定它在函数执行期间不改变,关于表,函数适用于——好吧,也许它确实不需要。或者SHARE模式,不确定
  • 我已经更新了答案只是为了正确类型传递的勇敢
【解决方案2】:

正如我在Erwin Brandstetter's answer 的评论中指出的,最初我有一个几乎相同的解决方案。

但问题是我收到了错误

ERROR: record "new" has no field "column_name"
CONTEXT: SQL statement "SELECT validate_bid_modifiers_count(table_name, column_name, NEW.column_name, devices_count)"
PL/pgSQL function device_bid_modifiers_count_per() line 15 at PERFORM

这就是为什么我认为我需要一种动态评估事物的方法。

目前使用以下对我来说仍然很难看的解决方案(很难看,因为我不喜欢 2 IF 语句,我希望它是超级动态的,但也许我要求太多了):

CREATE OR REPLACE FUNCTION device_bid_modifiers_count_per()
  RETURNS TRIGGER AS
$func$
  DECLARE
    row           RECORD;
    table_name    regclass := TG_ARGV[0];
    column_name   text := TG_ARGV[1];
    devices_count INTEGER;

  BEGIN
    LOCK TABLE device_types IN EXCLUSIVE MODE;
    EXECUTE format('LOCK TABLE %s IN EXCLUSIVE MODE', table_name);

    devices_count := device_types_count();

    IF TG_OP = 'DELETE' THEN
      row := OLD;
    ELSE
      row := NEW;
    END IF;

    IF column_name = 'campaign_id' THEN
      PERFORM validate_bid_modifiers_count(table_name, column_name, row.campaign_id, devices_count);
    ELSIF column_name = 'adgroup_id' THEN
      PERFORM validate_bid_modifiers_count(table_name, column_name, row.adgroup_id, devices_count);
    ELSE
      RAISE EXCEPTION 'invalid_column_name %', column_name;
    END IF;
    RETURN NEW;
  END;
$func$ LANGUAGE plpgsql;

我愿意接受更可靠的解决方案建议。

基本上,第二个条件几乎违背了拥有单个功能的目的,此时我也可以将其拆分为两个功能。因为目标是使用此函数定义多个 (2) 触发器(为其提供参数)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-14
    • 2011-12-16
    • 2020-04-17
    • 2021-09-22
    • 2023-03-07
    • 2017-07-28
    • 1970-01-01
    • 2011-04-30
    相关资源
    最近更新 更多