函数为给定的 table 和列名返回 table

Function returning a table for given table and column names

假设我在两个不同的数据库 housesapartments.

中有一个名为 static 的 table

statictable包含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;

它可以工作,但是当涉及更多列时,这个查询会变得混乱。

我在找什么:

  1. 创建一个函数,该函数接受列名和 table 名称,然后执行我在上面的查询中所做的连接,returns a table。 (应该是通用的并且适用于给定的列名和参数中的 table 名称以及 return 和 table。)

  2. 还有其他漂亮整洁的解决方案吗?

不要乱问。向包含所有列的两个数据库添加一个视图。在第一个数据库中:

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.

The manual on dynamic SQL.

④ 可以安全地防止 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.


免责声明:如果我,我只会介绍这种复杂程度。也许您毕竟可以在每个数据库中使用简单的视图?