如何在 Postgres 函数中使用文本输入作为列名?

Posted

技术标签:

【中文标题】如何在 Postgres 函数中使用文本输入作为列名?【英文标题】:How to use text input as column name(s) in a Postgres function? 【发布时间】:2021-07-24 18:15:55 【问题描述】:

我正在使用 Postgres 和 PostGIS。尝试编写一个根据给定参数选择特定列的函数。

我使用WITH 语句创建结果表,然后将其转换为bytea 以返回。我需要帮助的部分是$4 部分。我试过它如下所示和$4::text,如果cols=name,两者都给我输入的文本值而不是表中的列值,所以我从查询名称而不是表中的实际名称中返回。我也尝试data($4) 并得到类型错误。 代码是这样的:

CREATE OR REPLACE FUNCTION select_by_txt(z integer,x integer,y integer, cols text)
        RETURNS bytea
        LANGUAGE 'plpgsql'
    
AS $BODY$
declare
res bytea;
begin
    WITH bounds AS (
      SELECT ST_TileEnvelope(z, x, y) AS geom
    ),
    mvtgeom AS (
      SELECT ST_AsMVTGeom(ST_Transform(t.geom, 3857), bounds.geom) AS geom, $4
      FROM table1 t, bounds
      WHERE ST_Intersects(t.geom, ST_Transform(bounds.geom, 4326))
    )
    
    SELECT ST_AsMVT(mvtgeom, 'public.select_by_txt')
    INTO res
    FROM mvtgeom;
    RETURN res;
end;
$BODY$;

函数调用示例:

select_by_txt(10,32,33,"col1,col2")

参数 cols 可以是从 1 开始的多个列名,不受上述限制。 cols 中的列名将在调用函数之前检查它们是否为有效列。

【问题讨论】:

cols 听起来您正在尝试传递 多个 列名。是这样吗?可以有多少?请显示示例调用。 是的,当你问@ErwinBrandstetter 时,我已经用一个例子更新了帖子,JGH 下面的答案只用一栏对我有用 【参考方案1】:

将多个列名作为连接字符串传递以进行动态执行迫切需要去污。我建议使用VARIADIC 函数参数,并使用正确引用的标识符(在这种情况下使用quote_ident()):

CREATE OR REPLACE FUNCTION select_by_txt(z int, x int, y int, VARIADIC cols text[] = NULL, OUT res text)
  LANGUAGE plpgsql AS
$func$
BEGIN
   EXECUTE format(
$$
SELECT ST_AsMVT(mvtgeom, 'public.select_by_txt')
FROM  (
   SELECT ST_AsMVTGeom(ST_Transform(t.geom, 3857), bounds.geom) AS geom%s
   FROM   table1 t
   JOIN  (SELECT ST_TileEnvelope($1, $2, $3)) AS bounds(geom)
          ON ST_Intersects(t.geom, ST_Transform(bounds.geom, 4326))
   ) mvtgeom
$$, (SELECT ', ' || string_agg(quote_ident (col), ', ') FROM unnest(cols) col)
   )
   INTO  res
   USING z, x, y;
END
$func$;

db小提琴here

format() 的格式说明符 %I 处理 单个 标识符。您必须为 多个 标识符投入更多的工作,尤其是对于可变数量的 0-n 标识符。此实现引用每个列名,并且仅在传递任何列名时添加,。所以它适用于所有可能的输入,甚至根本没有输入。注意 VARIADIC cols text[] = NULL 作为最后一个输入参数,默认值为 NULL:

Optional argument in PL/pgSQL function

相关:

quote_ident() does not add quotes to column name "first"

在这种情况下,列名区分大小写!

呼吁你的榜样(重要!):

SELECT select_by_txt(10,32,33,'col1', 'col2');

替代语法:

SELECT select_by_txt(10,32,33, VARIADIC 'col1,col2');

更具启发性的调用,带有第三列名称和恶意(尽管徒劳)意图:

SELECT select_by_txt(10,32,33,'col1', 'col2', $$col3'); DROP TABLE table1;--$$);

关于奇怪的第三列名称和 SQL 注入:

https://www.explainxkcd.com/wiki/index.php/Little_Bobby_Tables

关于VAIRADIC参数:

Return rows matching elements of input array in plpgsql function Pass multiple values in single parameter

为简单起见,使用OUT 参数。这完全是可选的。见:

Returning from a function with OUT parameter

我会做什么

如果您真的、真的相信输入始终是 1 个或多个有效列名的格式正确的列表 - 并且您断言 ...

cols 中的列名将在调用函数之前检查它们是否为有效列

可以简化:

CREATE OR REPLACE FUNCTION select_by_txt(z int, x int, y int, cols text, OUT res text)
  LANGUAGE plpgsql AS
$func$
BEGIN
   EXECUTE format(
$$
SELECT ST_AsMVT(mvtgeom, 'public.select_by_txt')
FROM  (
   SELECT ST_AsMVTGeom(ST_Transform(t.geom, 3857), bounds.geom) AS geom, %s
   FROM   table1 t
   JOIN  (SELECT ST_TileEnvelope($1, $2, $3)) AS bounds(geom)
          ON ST_Intersects(t.geom, ST_Transform(bounds.geom, 4326))
   ) mvtgeom
$$, cols
   )
   INTO  res
   USING z, x, y;
END
$func$;

(你怎么能确定输入总是可靠的?)

【讨论】:

【参考方案2】:

您需要使用dynamic 查询:

CREATE OR REPLACE FUNCTION select_by_txt(z integer,x integer,y integer, cols text)
        RETURNS bytea
        LANGUAGE 'plpgsql'
    
AS $BODY$
declare
res bytea;
begin
EXECUTE format('
    WITH bounds AS (
      SELECT ST_TileEnvelope($1, $2, $3) AS geom
    ),
    mvtgeom AS (
      SELECT ST_AsMVTGeom(ST_Transform(t.geom, 3857), bounds.geom) AS geom, %I
      FROM table1 t, bounds
      WHERE ST_Intersects(t.geom, ST_Transform(bounds.geom, 4326))
    )
    
    SELECT ST_AsMVT(mvtgeom, ''public.select_by_txt'')
    FROM mvtgeom', cols)
    INTO res
    USING z,x,y;
    
    RETURN res;
end;
$BODY$;

【讨论】:

您好,首先感谢您的回复!你给我的答案部分对我有用。它在 cols 是单列输入时工作,但当 cols='col1,co2' 函数失败并出现此错误 - 错误:列 \"col1,col2\" 不存在 (SQLSTATE 42703)。有没有办法“告诉”函数 cols 不是单列? @Yoad 这回答了您最初编写的问题。如果您有新问题,请随时在新帖子中提出新问题。 最初的问题是针对列的,所以它仍然是同一个问题......我添加了一个示例,因为我看到这可能不太清楚。如果这对您很重要,我可以在新帖子中重新提出问题

以上是关于如何在 Postgres 函数中使用文本输入作为列名?的主要内容,如果未能解决你的问题,请参考以下文章

如何在带有 Postgres 的动态框架中使用窗口函数中的列值?

如何拆分列包括键和值到postgres中的单独列中

如何在 Postgres 中使用 Plpython3 返回结果集

如何在 JSON Postgres 数据类型列中搜索特定字符串?

Postgres - 如何对窗口函数列的每 x 行求和?

当列值是 84 字节文本字段时,postgres 中保持列唯一的最有效方法是啥?