使用函数的文本输出作为新查询
Use text output from a function as new query
从@Erwin Brandstetter 和@Craig Ringer 协助的 开始,我已将我的代码修改为如下所示。请注意,我的函数 myresult()
现在输出 text
,而不是 table(事实上,正如在前一个案例中指出的那样,那里输出一个 table 对象是没有意义的,因为我们需要提前定义它的所有列,这基本上违背了整个目的):
CREATE OR REPLACE FUNCTION myresult(mytable text, myprefix text)
RETURNS text AS
$func$
DECLARE
myoneliner text;
BEGIN
SELECT INTO myoneliner
'SELECT '
|| string_agg(quote_ident(column_name::text), ',' ORDER BY column_name)
|| ' FROM ' || quote_ident(mytable)
FROM information_schema.columns
WHERE table_name = mytable
AND column_name LIKE myprefix||'%'
AND table_schema = 'public'; -- schema name; might be another param
RAISE NOTICE 'My additional text: %', myoneliner;
RETURN myoneliner;
END
$func$ LANGUAGE plpgsql;
致电:
select myresult('dkj_p_k27ac','enri');
运行上述过程后,我得到一个文本字符串,这基本上是一个查询。为简单起见,我接下来将其称为 'oneliner-output'。
'oneline-output' 看起来如下(我只是 copy/paste 它来自我在这里进入的一个输出单元格):
"SELECT enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac FROM dkj_p_k27ac"
- 请注意,语句两边的双引号是
myresult()
输出的一部分。我没有添加它们。
我现在更好地理解了构建一个既可以创建 'oneliner-output' 又可以执行它的函数的问题想法。我能够 copy/paste 'oneliner-output' 进入一个新的 Postgres 查询 window 并将其作为正常查询执行就好了,在我的数据输出 window 中接收所需的列和行.
但是,我想自动执行此步骤,以避免 copy/paste 步骤。 Postgres 中有没有办法使用我从 myresult()
函数收到的 text
输出('oneliner-output')并执行它?是否可以创建第二个函数来接收 myresult()
的输出并将其用于执行查询?
按照这些思路,虽然我知道以下脚本(在下面)有效并且实际上输出了所需的列和行:
-- DEALLOCATE stmt1; -- use this line after the first time 'stmt1' was created
prepare stmt1 as SELECT enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac FROM dkj_p_k27ac;
execute stmt1;
我在想,在进行了正确的调整之后,类似以下脚本的东西可能会起作用吗?不知道如何。
prepare stmt1 as THE_OUTPUT_OF_myresult();
execute stmt1;
尝试使用 refcursor
CREATE OR REPLACE FUNCTION show_mytable(ref refcursor) RETURNS refcursor AS $$
BEGIN
OPEN ref FOR SELECT enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac FROM dkj_p_k27ac; -- Open a cursor
RETURN ref; -- Return the cursor to the caller
END;
$$ LANGUAGE plpgsql;
致电:
BEGIN;
SELECT show_mytable('roy');
FETCH ALL IN "roy";
这个过程实际上有效并吐出所需的列和行,但我必须再次提供准确的 SELECT 声明。
我基本上希望能够提供它作为我的 myresult()
函数的输出。像这样:
CREATE OR REPLACE FUNCTION show_mytable(ref refcursor) RETURNS refcursor AS $$
BEGIN
OPEN ref FOR myresult(); -- Open a cursor
RETURN ref; -- Return the cursor to the caller
END;
$$ LANGUAGE plpgsql;
致电:
BEGIN;
SELECT show_mytable('roy');
FETCH ALL IN "roy";
PREPARE
的技巧不起作用,因为它不像 CREATE FUNCTION
那样采用*文本字符串*(值),而是有效的 语句(代码)。
要将 data 转换为 executable code 您需要使用动态 SQL,即 EXECUTE
在 plpgsql 函数或 DO
语句中。只要 return 类型不依赖于第一个函数 myresult()
的结果,这就没有问题。否则你又回到了我之前的回答中概述的 22:
关键部分是以某种方式声明return类型(在本例中为行类型)。您可以为此目的创建 TABLE
、TEMP TABLE
或 TYPE
。或者您可以使用准备好的语句或 refcursor。
准备语句的解决方案
你们一直很亲密。缺少的部分是使用 dynamic SQL.
准备生成的查询
动态准备语句的函数
创建此函数一次。它是您函数的优化和安全版本 myresult()
:
CREATE OR REPLACE FUNCTION f_prep_query (_tbl regclass, _prefix text)
RETURNS void AS
$func$
BEGIN
IF EXISTS (SELECT 1 FROM pg_prepared_statements WHERE name = 'stmt_dyn') THEN
DEALLOCATE stmt_dyn;
END IF; -- you my or may not need this safety check
EXECUTE (
SELECT 'PREPARE stmt_dyn AS SELECT '
|| string_agg(quote_ident(attname), ',' ORDER BY attname)
|| ' FROM ' || _tbl
FROM pg_catalog.pg_attribute
WHERE attrelid = _tbl
AND attname LIKE _prefix || '%'
AND attnum > 0
AND NOT attisdropped
);
END
$func$ LANGUAGE plpgsql;
我使用 regclass
作为 table 名称参数 _tbl
以使其明确且安全地对抗 SQLi。详情:
- Table name as a PostgreSQL function parameter
信息模式不包括系统目录的oid列,所以我切换到pg_catalog.pg_attribute
而不是information_schema.columns
。这也更快。这有利有弊:
- How to check if a table exists in a given schema
如果名称为 stmt_dyn
的预处理语句已经存在,PREPARE
将引发异常。如果是acceptable,去掉系统视图上的勾选pg_prepared_statements
和后面的DEALLOCATE
.
可以使用更复杂的算法来管理每个会话的多个准备语句,或者将准备语句的名称作为附加参数,甚至使用查询字符串的 MD5 哈希作为名称,但这超出了本文的范围问题.
请注意,PREPARE
在 事务 的范围之外运行,一旦 PREPARE
成功,准备好的语句将在会话的生命周期内存在。如果包装事务中止,PREPARE
不受影响。 ROLLBACK
不能 删除准备好的语句。
动态查询执行
两次次查询,但只有一次次调用服务器。而且效率也很高。
SELECT f_prep_query('tbl'::regclass, 'pre'::text);
EXECUTE stmt_dyn;
对于大多数简单用例来说,比创建临时 table 或游标并从中选择/获取(这将是其他选项)更简单、更高效。
我想我也找到了解决方案,使用 refcursor。
如果你能通过它,检查并告诉我你是否认为它是 'Kosher',我将非常高兴。坦率地说,我不太确定我在这里想到了什么,因为我对语法不太熟悉。但是我能够使用我在网上找到的不同示例来综合这一点。它似乎对我有用。如果您能为我和其他用户阐明这个解决方案,我将非常高兴 - 并告诉您您对此有何看法。
首先让我们创建构造动态 SELECT
语句的函数:
CREATE OR REPLACE FUNCTION myresult2()
RETURNS text AS
$func$
DECLARE
myoneliner text;
mytable text := 'dkj_p_k27ac';
myprefix text := 'enri';
BEGIN
SELECT INTO myoneliner
'SELECT '
|| string_agg(quote_ident(column_name::text), ',' ORDER BY column_name)
|| ' FROM ' || quote_ident(mytable)
FROM information_schema.columns
WHERE table_name = mytable
AND column_name LIKE myprefix||'%'
AND table_schema = 'public'; -- schema name; might be another param
-- RAISE NOTICE 'My additional text: %', myoneliner; -- for debugging
RETURN myoneliner;
END
$func$ LANGUAGE plpgsql;
现在,让我们创建第二个函数,它可以执行第一个函数的字符串 TEXT 输出 myresult2()
:
CREATE OR REPLACE FUNCTION show_mytable(ref refcursor)
RETURNS refcursor AS
$func$
DECLARE
mydynamicstatment text := myresult2();
BEGIN
OPEN ref FOR EXECUTE mydynamicstatment;
RETURN ref; -- return cursor to the caller
END;
$func$ LANGUAGE plpgsql;
通话:
BEGIN;
SELECT show_mytable('roy');
FETCH ALL IN "roy";
从@Erwin Brandstetter 和@Craig Ringer 协助的 myresult()
现在输出 text
,而不是 table(事实上,正如在前一个案例中指出的那样,那里输出一个 table 对象是没有意义的,因为我们需要提前定义它的所有列,这基本上违背了整个目的):
CREATE OR REPLACE FUNCTION myresult(mytable text, myprefix text)
RETURNS text AS
$func$
DECLARE
myoneliner text;
BEGIN
SELECT INTO myoneliner
'SELECT '
|| string_agg(quote_ident(column_name::text), ',' ORDER BY column_name)
|| ' FROM ' || quote_ident(mytable)
FROM information_schema.columns
WHERE table_name = mytable
AND column_name LIKE myprefix||'%'
AND table_schema = 'public'; -- schema name; might be another param
RAISE NOTICE 'My additional text: %', myoneliner;
RETURN myoneliner;
END
$func$ LANGUAGE plpgsql;
致电:
select myresult('dkj_p_k27ac','enri');
运行上述过程后,我得到一个文本字符串,这基本上是一个查询。为简单起见,我接下来将其称为 'oneliner-output'。
'oneline-output' 看起来如下(我只是 copy/paste 它来自我在这里进入的一个输出单元格):
"SELECT enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac FROM dkj_p_k27ac"
- 请注意,语句两边的双引号是
myresult()
输出的一部分。我没有添加它们。
我现在更好地理解了构建一个既可以创建 'oneliner-output' 又可以执行它的函数的问题想法。我能够 copy/paste 'oneliner-output' 进入一个新的 Postgres 查询 window 并将其作为正常查询执行就好了,在我的数据输出 window 中接收所需的列和行.
但是,我想自动执行此步骤,以避免 copy/paste 步骤。 Postgres 中有没有办法使用我从 myresult()
函数收到的 text
输出('oneliner-output')并执行它?是否可以创建第二个函数来接收 myresult()
的输出并将其用于执行查询?
按照这些思路,虽然我知道以下脚本(在下面)有效并且实际上输出了所需的列和行:
-- DEALLOCATE stmt1; -- use this line after the first time 'stmt1' was created
prepare stmt1 as SELECT enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac FROM dkj_p_k27ac;
execute stmt1;
我在想,在进行了正确的调整之后,类似以下脚本的东西可能会起作用吗?不知道如何。
prepare stmt1 as THE_OUTPUT_OF_myresult(); execute stmt1;
尝试使用 refcursor
CREATE OR REPLACE FUNCTION show_mytable(ref refcursor) RETURNS refcursor AS $$
BEGIN
OPEN ref FOR SELECT enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac FROM dkj_p_k27ac; -- Open a cursor
RETURN ref; -- Return the cursor to the caller
END;
$$ LANGUAGE plpgsql;
致电:
BEGIN;
SELECT show_mytable('roy');
FETCH ALL IN "roy";
这个过程实际上有效并吐出所需的列和行,但我必须再次提供准确的 SELECT 声明。
我基本上希望能够提供它作为我的 myresult()
函数的输出。像这样:
CREATE OR REPLACE FUNCTION show_mytable(ref refcursor) RETURNS refcursor AS $$
BEGIN
OPEN ref FOR myresult(); -- Open a cursor
RETURN ref; -- Return the cursor to the caller
END;
$$ LANGUAGE plpgsql;
致电:
BEGIN;
SELECT show_mytable('roy');
FETCH ALL IN "roy";
PREPARE
的技巧不起作用,因为它不像 CREATE FUNCTION
那样采用*文本字符串*(值),而是有效的 语句(代码)。
要将 data 转换为 executable code 您需要使用动态 SQL,即 EXECUTE
在 plpgsql 函数或 DO
语句中。只要 return 类型不依赖于第一个函数 myresult()
的结果,这就没有问题。否则你又回到了我之前的回答中概述的 22:
关键部分是以某种方式声明return类型(在本例中为行类型)。您可以为此目的创建 TABLE
、TEMP TABLE
或 TYPE
。或者您可以使用准备好的语句或 refcursor。
准备语句的解决方案
你们一直很亲密。缺少的部分是使用 dynamic SQL.
准备生成的查询动态准备语句的函数
创建此函数一次。它是您函数的优化和安全版本 myresult()
:
CREATE OR REPLACE FUNCTION f_prep_query (_tbl regclass, _prefix text)
RETURNS void AS
$func$
BEGIN
IF EXISTS (SELECT 1 FROM pg_prepared_statements WHERE name = 'stmt_dyn') THEN
DEALLOCATE stmt_dyn;
END IF; -- you my or may not need this safety check
EXECUTE (
SELECT 'PREPARE stmt_dyn AS SELECT '
|| string_agg(quote_ident(attname), ',' ORDER BY attname)
|| ' FROM ' || _tbl
FROM pg_catalog.pg_attribute
WHERE attrelid = _tbl
AND attname LIKE _prefix || '%'
AND attnum > 0
AND NOT attisdropped
);
END
$func$ LANGUAGE plpgsql;
我使用 regclass
作为 table 名称参数 _tbl
以使其明确且安全地对抗 SQLi。详情:
- Table name as a PostgreSQL function parameter
信息模式不包括系统目录的oid列,所以我切换到pg_catalog.pg_attribute
而不是information_schema.columns
。这也更快。这有利有弊:
- How to check if a table exists in a given schema
如果名称为 stmt_dyn
的预处理语句已经存在,PREPARE
将引发异常。如果是acceptable,去掉系统视图上的勾选pg_prepared_statements
和后面的DEALLOCATE
.
可以使用更复杂的算法来管理每个会话的多个准备语句,或者将准备语句的名称作为附加参数,甚至使用查询字符串的 MD5 哈希作为名称,但这超出了本文的范围问题.
请注意,PREPARE
在 事务 的范围之外运行,一旦 PREPARE
成功,准备好的语句将在会话的生命周期内存在。如果包装事务中止,PREPARE
不受影响。 ROLLBACK
不能 删除准备好的语句。
动态查询执行
两次次查询,但只有一次次调用服务器。而且效率也很高。
SELECT f_prep_query('tbl'::regclass, 'pre'::text);
EXECUTE stmt_dyn;
对于大多数简单用例来说,比创建临时 table 或游标并从中选择/获取(这将是其他选项)更简单、更高效。
我想我也找到了解决方案,使用 refcursor。
如果你能通过它,检查并告诉我你是否认为它是 'Kosher',我将非常高兴。坦率地说,我不太确定我在这里想到了什么,因为我对语法不太熟悉。但是我能够使用我在网上找到的不同示例来综合这一点。它似乎对我有用。如果您能为我和其他用户阐明这个解决方案,我将非常高兴 - 并告诉您您对此有何看法。
首先让我们创建构造动态 SELECT
语句的函数:
CREATE OR REPLACE FUNCTION myresult2()
RETURNS text AS
$func$
DECLARE
myoneliner text;
mytable text := 'dkj_p_k27ac';
myprefix text := 'enri';
BEGIN
SELECT INTO myoneliner
'SELECT '
|| string_agg(quote_ident(column_name::text), ',' ORDER BY column_name)
|| ' FROM ' || quote_ident(mytable)
FROM information_schema.columns
WHERE table_name = mytable
AND column_name LIKE myprefix||'%'
AND table_schema = 'public'; -- schema name; might be another param
-- RAISE NOTICE 'My additional text: %', myoneliner; -- for debugging
RETURN myoneliner;
END
$func$ LANGUAGE plpgsql;
现在,让我们创建第二个函数,它可以执行第一个函数的字符串 TEXT 输出 myresult2()
:
CREATE OR REPLACE FUNCTION show_mytable(ref refcursor)
RETURNS refcursor AS
$func$
DECLARE
mydynamicstatment text := myresult2();
BEGIN
OPEN ref FOR EXECUTE mydynamicstatment;
RETURN ref; -- return cursor to the caller
END;
$func$ LANGUAGE plpgsql;
通话:
BEGIN;
SELECT show_mytable('roy');
FETCH ALL IN "roy";