【问题标题】:Function returning a table for given table and column names函数返回给定表名和列名的表
【发布时间】:2020-10-22 00:05:45
【问题描述】:

假设我在两个不同的数据库housesapartments 中有一个名为static 的表。

static表包含house_sizeno_roomspoolspa等房屋的静态信息。

houses 数据库中的static 表有这样的列:

pool    spa house_size  sauna   no_rooms
   1    1         25    1       2
   1    0         35    1       3

apartments 数据库中的static 表有这样的列:

pool    spa house_size  sauna   
   1    1         25    1       
   1    0         35    1       

我想在查询下方运行而不引发任何错误。目前,我收到错误消息,因为apartments.public.static 中不存在no_rooms 列。

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. 创建一个接受列名和表名的函数,然后执行我在上述查询中所做的连接并返回一个表。 (应该是通用的,适用于参数中给定的列名和表名并返回一个表。)

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

【问题讨论】:

  • 只需将no_rooms 添加到没有它的表中即可修复数据。或者在两个数据库中创建一个与添加值的名称相同的视图。

标签: sql postgresql plpgsql dynamic-sql


【解决方案1】:

SQL 是一种严格类型化的语言,Postgres 函数必须声明它们的返回类型。从函数返回可变数量的列只能通过变通方法来实现,例如多态类型。见:

但在您的情况下,我们无法使用行类型,因为这因数据库而异。剩下的选择:返回匿名记录,并为每次调用提供一个列定义列表。我通常不推荐这样做,因为每次调用都提供一个列定义列表可能很乏味——而且通常毫无意义。但你的可能是少数有意义的用例之一。

不过,您必须了解可能丢失的列的数据类型。出于本演示的目的,我将假设 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小提琴here

①该函数将表名作为regclass类型。见:

... 后跟任意列名列表 - 以有意义的顺序排列。 VARIADIC 应该很方便。见:

请注意,我们将列名作为区分大小写的单引号字符串传递。非(双引号)标识符。

② 这可能是我第一次建议从函数返回匿名记录——在 [plpgsql] 标签上得到近 1000 个答案之后。 The manual:

如果函数已定义为返回record 数据类型, 然后必须存在别名或关键字AS,后跟一列 ( column_name data_type [, ... ]) 形式的定义列表。这 列定义列表必须与实际数量和类型相匹配 函数返回的列。

The manual on dynamic SQL.

④ 防止 SQL 注入,因为表名以regclass 传递,SELECT 列表小心使用quote_ident() 连接。见:

⑤ 使用WITH ORDINALITY 保留列的原始顺序。见:

LEFT JOIN 到系统目录pg_attribute 以识别现有列。见:

⑦ 将建立在传递列上的表达式移动到外部SELECT


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

【讨论】:

    【解决方案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;
    

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

    【讨论】:

    • 你知道如何编写一个接受列名作为参数并返回表的函数吗?
    • @AwaishKumar 。 . .对于您在这个问题中提出的问题,这似乎不是一个合理的解决方案。如果你想做这样的事情,你可以问一个新的问题。
    • 我在最后两件事中添加了,我想从问题中得到什么? 1)是我正在寻找一个功能来做这些事情,我在做什么?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-16
    • 2017-05-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多