在触发功能中访问行类型的动态列名称

时间:2019-03-19 16:05:46

标签: sql postgresql triggers plpgsql dynamic-sql

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

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()返回带有参数的函数调用的正确字符串。如何解决并使其正常工作?

2 个答案:

答案 0 :(得分:2)

这应该做到:

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。如果没有目标,则需要在plpgsql中将其替换为PERFORM。但是传递给PERFORM的查询字符串中的内部EXECUTE也是错误的。 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,但执行起来更简单,更快。

答案 1 :(得分:0)

正如我在对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;

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

基本上,第二种条件'a几乎破坏了具有单个功能的目的,在这一点上,我可以将其分为两个功能。因为目标是使用此函数定义多个(2)触发器(为其提供参数)。