在 Postgres 13.4 的 SELECT 查询中从 CTE 调用插入 *函数*

Calling an insert *function* from a CTE in a SELECT query in Postgres 13.4

我正在编写 运行 到 pg_cron 的实用程序代码,有时希望例程将一些结果插入 dba.event_log 处的自定义 table。我有一个基本日志 table 作为起点:

DROP TABLE IF EXISTS dba.event_log;

CREATE TABLE IF NOT EXISTS dba.event_log (
    dts      timestamp    NOT NULL DEFAULT now(),
    name     citext       NOT NULL DEFAULT '',
    details  citext       NOT NULL DEFAULT '');

下面的玩具示例执行 select 操作,然后使用该值作为外部查询的结果,并作为 insertvalues 元素进入 event_log:

WITH

values_cte AS (
 select clock_timestamp() as ct
),

log as(
insert into event_log (
               name,
               details)

     values (
             'CTE INSERT check',
             'clock = ' || (select ct::text from values_cte)
             )
)

select * from values_cte;

select * from event_log;

每次我 运行 这个,我都会得到一个新的日志条目,使用 clock_timestamp() 可以很容易地看到正在发生的事情:

+----------------------------+------------------+---------------------------------------+
| dts                        | name             | details                               |
+----------------------------+------------------+---------------------------------------+
| 2021-11-10 11:58:43.919151 | CTE INSERT check | clock = 2021-11-10 11:58:43.919821+11 |
| 2021-11-10 11:58:56.769512 | CTE INSERT check | clock = 2021-11-10 11:58:56.769903+11 |
| 2021-11-10 11:58:59.632619 | CTE INSERT check | clock = 2021-11-10 11:58:59.632822+11 |
| 2021-11-10 12:00:50.442282 | CTE INSERT check | clock = 2021-11-10 12:00:50.442646+11 |
+----------------------------+------------------+---------------------------------------+

稍后我可能会丰富 table,现在我会将日志 inserts 变成一个简单的调用。下面是一个简单的 insert 函数:

DROP FUNCTION IF EXISTS dba.event_log_add(citext,citext);

CREATE FUNCTION dba.event_log_add(
   name_in        citext,
   description_in citext)

RETURNS int4

LANGUAGE sql AS

$BODY$

insert into event_log (name, details)
     values (name_in, description_in)
   returning 1;

$BODY$;

看到 就像我应该能够重写原始查询来调用函数,如下所示:

WITH

values_cte AS (
 select clock_timestamp() as ct
),

log as (
select * from dba.event_log_add(   
             'CTE event_log_add check',
             'clock = ' || (select ct::text from values_cte)
             )               
)

select * from values_cte;

这里唯一的区别是 VALUES 现在作为参数传递给 dba.event_log_add,而不是直接在查询中的 INSERT 中使用。我收到此错误:

ERROR:  function dba.event_log_add(unknown, text) does not exist
LINE 8: select * from dba.event_log_add(   
                      ^
HINT:  No function matches the given name and argument types. You might need to add explicit type casts. 0.000 seconds. (Line 1).

我试过了

似乎没有任何效果。我检查了 search_path,使用合格 名称,检查权限等。有些方法会抛出似乎不适用的错误, 像上面的一样,其他人没有抛出错误,也没有插入数据。 运行 直接,功能没问题,只是在CTE里面炸了。

我想我遗漏了一些有关使用函数而不是直接 INSERT 的内容。有没有好的方法来做到这一点?查看文档并在此处搜索更多信息后,我对规则有了 的了解。但不完全是。如果我没看错,外部查询的数据修改 CTE 是 ruled/regulated。肯定有一些我没有掌握的微妙之处。我是否以某种方式更改上下文以将 INSERT 移动到一个函数中,从而使查询和 CTE 中的代码如何被解释?

https://www.postgresql.org/docs/13/queries-with.html#QUERIES-WITH-MODIFYING

您的函数需要 citext 类型的参数,但您传递的是 text 类型的值。您需要投射参数:

WITH values_cte AS (
 select clock_timestamp() as ct
),log as (
  select event_log_add('CTE event_log_add check'::citext,
                       ('clock = ' || (select ct::text from values_cte))::citext)               
)
select * 
from log;

将参数定义为 text 可能更容易,在 INSERT 期间将自动完成转换:

CREATE FUNCTION event_log_add(
   name_in        text,
   description_in text)
RETURNS int4
LANGUAGE sql AS
$BODY$
  insert into event_log (name, details)
  values (name_in, description_in)
  returning 1;
$BODY$;

WITH values_cte AS (
 select clock_timestamp() as ct
),log as (
  select event_log_add('CTE event_log_add check',
                       'clock = ' || (select ct::text from values_cte))   
)
select * 
from log;

如果需要,可以在函数内添加显式强制转换。