函数为给定的 table 和列名返回 table
Function returning a table for given table and column names
假设我在两个不同的数据库 houses
和 apartments
.
中有一个名为 static
的 table
static
table包含house_size
,no_rooms
,pool
,spa
.[=30等房屋的静态信息=]
static
table 在 houses
数据库中有这样的列:
pool spa house_size sauna no_rooms
1 1 25 1 2
1 0 35 1 3
static
table 在 apartments
数据库中有这样的列:
pool spa house_size sauna
1 1 25 1
1 0 35 1
我想 运行 下面的查询而不引发任何错误。目前,我收到错误消息,因为 no_rooms
列在 apartments.public.static
.
中不存在
select pool, case when spa = 1 then 1 else 0 end as has_spa,
sauna, house_size, case when no_rooms > 2 then 1 else 0 end as rooms
from static;
我试过的解决方案:
WITH static_new AS (SELECT s.*
FROM (SELECT 0 AS no_rooms) AS dummy
LEFT JOIN LATERAL
( SELECT
pool, spa, sauna, house_size, no_rooms
FROM static
) AS s on true)
SELECT * FROM static_new;
它可以工作,但是当涉及更多列时,这个查询会变得混乱。
我在找什么:
创建一个函数,该函数接受列名和 table 名称,然后执行我在上面的查询中所做的连接,returns a table。 (应该是通用的并且适用于给定的列名和参数中的 table 名称以及 return 和 table。)
还有其他漂亮整洁的解决方案吗?
不要乱问。向包含所有列的两个数据库添加一个视图。在第一个数据库中:
create view v_static as
select pool, spa house_size, sauna, no_rooms
from status;
第二个:
create view v_static as
select pool, spa house_size, sauna, null as no_rooms
from status;
然后使用视图而不是基础 table。
SQL 是一种严格类型的语言,Postgres 函数必须声明它们的 return 类型。从函数返回可变数量的列只能通过变通方法实现,例如多态类型。参见:
- How to return a table by rowtype in PL/pgSQL
但是我们无法处理您的行类型,因为它因数据库而异。剩下的选项:return 匿名记录 并在每次调用时提供列定义列表。我通常不推荐这样做,因为在每次调用时都提供一个列定义列表可能很乏味——而且通常毫无意义。但你的可能是少数有意义的用例之一。
不过,您必须知道可能缺失的列的数据类型。出于本演示的目的,我假设 integer
。否则您必须另外传递数据类型并相应地构建查询。
CREATE OR REPLACE FUNCTION f_dynamic_select(_tbl regclass
, _cols VARIADIC text[]) -- ①
RETURNS SETOF record -- ② anonymous records
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE -- ③ dynamic SQL
format(
'SELECT %s FROM %s' -- ④ safe against SQLi
, (
SELECT string_agg(COALESCE(quote_ident(a.attname)
, '0 AS ' || quote_ident(t.col) -- assuming integer!
), ', ' ORDER BY t.ord) -- ⑤
FROM unnest(_cols) WITH ORDINALITY t(col, ord) -- ⑤
LEFT JOIN pg_attribute a ON a.attrelid = _tbl -- ⑥
AND a.attnum > 0
AND NOT a.attisdropped
AND a.attname = t.col
)
, _tbl
);
END
$func$;
打电话(重要!)
SELECT *
FROM f_dynamic_select('static', 'pool', 'spa', 'sauna', 'house_size', 'no_rooms')
AS t(pool int, spa int, house_size int, sauna int, no_rooms int); -- ② column definition list
您的示例调用,使用基于这些列的表达式:
SELECT pool, case when spa = 1 then 1 else 0 end as has_spa -- ⑦ expressions
, sauna, house_size
, case when no_rooms > 2 then 1 else 0 end as rooms
FROM f_dynamic_select('static', 'pool', 'spa', 'sauna', 'house_size', 'no_rooms')
AS t(pool int, spa int, house_size int, sauna int, no_rooms int);
db<>fiddle here
① 该函数将 table 名称作为 regclass
类型。参见:
- Table name as a PostgreSQL function parameter
... 后跟任意列名列表 - 按有意义的顺序排列。 VARIADIC
应该方便这个。参见:
- Pass multiple values in single parameter
请注意,我们将列名作为区分大小写的单引号字符串传递。不是(双引号)标识符。
② 这可能是我第一次推荐 return 从函数中获取匿名记录 - 在 [plpgsql] 标签上有近 1000 个答案之后。 The manual:
If the function has been defined as returning the record
data type,
then an alias or the key word AS
must be present, followed by a column
definition list in the form ( column_name data_type [, ... ])
. The
column definition list must match the actual number and types of
columns returned by the function.
④ 可以安全地防止 SQL 注入,因为 table 名称作为 regclass
传递,并且 SELECT
列表使用 quote_ident()
小心连接。参见:
- Define table and column names as arguments in a plpgsql function?
⑤ 使用 WITH ORDINALITY
保留列的原始顺序。参见:
- PostgreSQL unnest() with element number
⑥ LEFT JOIN
到系统目录 pg_attribute
以识别现有列。参见:
- Select columns with particular column names in PostgreSQL
⑦ 将基于传递的列的表达式移动到外部 SELECT
.
免责声明:如果我有,我只会介绍这种复杂程度。也许您毕竟可以在每个数据库中使用简单的视图?
假设我在两个不同的数据库 houses
和 apartments
.
static
的 table
static
table包含house_size
,no_rooms
,pool
,spa
.[=30等房屋的静态信息=]
static
table 在 houses
数据库中有这样的列:
pool spa house_size sauna no_rooms
1 1 25 1 2
1 0 35 1 3
static
table 在 apartments
数据库中有这样的列:
pool spa house_size sauna
1 1 25 1
1 0 35 1
我想 运行 下面的查询而不引发任何错误。目前,我收到错误消息,因为 no_rooms
列在 apartments.public.static
.
select pool, case when spa = 1 then 1 else 0 end as has_spa,
sauna, house_size, case when no_rooms > 2 then 1 else 0 end as rooms
from static;
我试过的解决方案:
WITH static_new AS (SELECT s.*
FROM (SELECT 0 AS no_rooms) AS dummy
LEFT JOIN LATERAL
( SELECT
pool, spa, sauna, house_size, no_rooms
FROM static
) AS s on true)
SELECT * FROM static_new;
它可以工作,但是当涉及更多列时,这个查询会变得混乱。
我在找什么:
创建一个函数,该函数接受列名和 table 名称,然后执行我在上面的查询中所做的连接,returns a table。 (应该是通用的并且适用于给定的列名和参数中的 table 名称以及 return 和 table。)
还有其他漂亮整洁的解决方案吗?
不要乱问。向包含所有列的两个数据库添加一个视图。在第一个数据库中:
create view v_static as
select pool, spa house_size, sauna, no_rooms
from status;
第二个:
create view v_static as
select pool, spa house_size, sauna, null as no_rooms
from status;
然后使用视图而不是基础 table。
SQL 是一种严格类型的语言,Postgres 函数必须声明它们的 return 类型。从函数返回可变数量的列只能通过变通方法实现,例如多态类型。参见:
- How to return a table by rowtype in PL/pgSQL
但是我们无法处理您的行类型,因为它因数据库而异。剩下的选项:return 匿名记录 并在每次调用时提供列定义列表。我通常不推荐这样做,因为在每次调用时都提供一个列定义列表可能很乏味——而且通常毫无意义。但你的可能是少数有意义的用例之一。
不过,您必须知道可能缺失的列的数据类型。出于本演示的目的,我假设 integer
。否则您必须另外传递数据类型并相应地构建查询。
CREATE OR REPLACE FUNCTION f_dynamic_select(_tbl regclass
, _cols VARIADIC text[]) -- ①
RETURNS SETOF record -- ② anonymous records
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE -- ③ dynamic SQL
format(
'SELECT %s FROM %s' -- ④ safe against SQLi
, (
SELECT string_agg(COALESCE(quote_ident(a.attname)
, '0 AS ' || quote_ident(t.col) -- assuming integer!
), ', ' ORDER BY t.ord) -- ⑤
FROM unnest(_cols) WITH ORDINALITY t(col, ord) -- ⑤
LEFT JOIN pg_attribute a ON a.attrelid = _tbl -- ⑥
AND a.attnum > 0
AND NOT a.attisdropped
AND a.attname = t.col
)
, _tbl
);
END
$func$;
打电话(重要!)
SELECT *
FROM f_dynamic_select('static', 'pool', 'spa', 'sauna', 'house_size', 'no_rooms')
AS t(pool int, spa int, house_size int, sauna int, no_rooms int); -- ② column definition list
您的示例调用,使用基于这些列的表达式:
SELECT pool, case when spa = 1 then 1 else 0 end as has_spa -- ⑦ expressions
, sauna, house_size
, case when no_rooms > 2 then 1 else 0 end as rooms
FROM f_dynamic_select('static', 'pool', 'spa', 'sauna', 'house_size', 'no_rooms')
AS t(pool int, spa int, house_size int, sauna int, no_rooms int);
db<>fiddle here
① 该函数将 table 名称作为 regclass
类型。参见:
- Table name as a PostgreSQL function parameter
... 后跟任意列名列表 - 按有意义的顺序排列。 VARIADIC
应该方便这个。参见:
- Pass multiple values in single parameter
请注意,我们将列名作为区分大小写的单引号字符串传递。不是(双引号)标识符。
② 这可能是我第一次推荐 return 从函数中获取匿名记录 - 在 [plpgsql] 标签上有近 1000 个答案之后。 The manual:
If the function has been defined as returning the
record
data type, then an alias or the key wordAS
must be present, followed by a column definition list in the form( column_name data_type [, ... ])
. The column definition list must match the actual number and types of columns returned by the function.
④ 可以安全地防止 SQL 注入,因为 table 名称作为 regclass
传递,并且 SELECT
列表使用 quote_ident()
小心连接。参见:
- Define table and column names as arguments in a plpgsql function?
⑤ 使用 WITH ORDINALITY
保留列的原始顺序。参见:
- PostgreSQL unnest() with element number
⑥ LEFT JOIN
到系统目录 pg_attribute
以识别现有列。参见:
- Select columns with particular column names in PostgreSQL
⑦ 将基于传递的列的表达式移动到外部 SELECT
.
免责声明:如果我有,我只会介绍这种复杂程度。也许您毕竟可以在每个数据库中使用简单的视图?