sql—返回给定表和列名的表的函数

4ktjp1zp  于 2021-08-01  发布在  Java
关注(0)|答案(2)|浏览(366)

假设我有一个名为 static 在两个不同的数据库中 houses 以及 apartments . static 表包含房屋的静态信息,例如 house_size , no_rooms , pool , spa . static 中的表 houses 数据库有如下列:

pool    spa house_size  sauna   no_rooms
   1    1         25    1       2
   1    0         35    1       3
``` `static` 中的表 `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;

它可以工作,但是当涉及到更多的列时,这个查询会变得混乱。
我要找的是:
创建一个接受列名和表名的函数,然后执行我在上面的查询中执行的连接并返回一个表(应为泛型,适用于参数中给定的列名和表名,并返回表。)
还有什么好办法吗?
flvtvl50

flvtvl501#

sql是一种严格类型化的语言,postgres函数必须声明其返回类型。从函数中返回可变数量的列只能通过变通方法(如多态类型)实现。请参见:
pl/pgsql中如何按行类型返回表
但是我们不能在您的案例中使用行类型,因为不同的数据库的行类型不同。剩下的选项:返回匿名记录并为每个调用提供列定义列表。我通常不建议这样做,因为为每个调用提供一个列定义列表可能会很乏味,而且往往毫无意义。但你的可能是其中一个罕见的用例,它是有意义的。
不过,您必须知道可能缺少的列的数据类型。我假设 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<>在这里摆弄
① 函数采用表名作为 regclass 类型。请参见:
表名作为postgresql函数参数
... 后跟任意列名称列表-按有意义的顺序排列。 VARIADIC 这样应该很方便。请参见:
在单个参数中传递多个值
请注意,我们将列名作为区分大小写的单引号字符串传递。非(双引号)标识符。
② 这可能是我第一次建议从函数返回匿名记录—在[plpgsql]标记上有近1000个答案之后。手册:
如果函数已定义为返回 record 数据类型,然后是别名或关键字 AS 必须存在,后跟表单中的列定义列表 ( column_name data_type [, ... ]) . 列定义列表必须与函数返回的实际列数和类型匹配。
③ 动态sql手册。
④ 对sql注入是安全的,因为表名作为 regclass ,和 SELECT 列表是使用 quote_ident() 小心。请参见:
在plpgsql函数中将表名和列名定义为参数?
⑤ 使用 WITH ORDINALITY 保持列的原始顺序。请参见:
带有元素编号的postgresql unnest()
LEFT JOIN 到系统目录 pg_attribute 以标识现有列。请参见:
在postgresql中选择具有特定列名的列
⑦ 将在传递的列上生成的表达式移到外部 SELECT .
免责声明:如果必须的话,我只会介绍这种复杂程度。也许你可以在每个数据库中使用简单的视图?

u91tlkcl

u91tlkcl2#

别把问题搞砸了。向两个数据库中添加一个包含所有列的视图。在第一个数据库中:

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;

然后使用视图而不是基表。

相关问题