【问题标题】:How to convert a PL/PgSQL procedure into a dynamic one?如何将 PL/PgSQL 过程转换为动态过程?
【发布时间】:2020-05-02 19:28:41
【问题描述】:

我正在尝试编写一个 plpgsql 程序来执行 postGIS 表的空间平铺。我可以使用以下对表名进行硬编码的过程成功执行该操作。该过程循环遍历 tile_table 中的图块,并为每个图块剪切 area_table 并将其插入 split_table。

CREATE OR REPLACE PROCEDURE splitbytile()
AS $$
DECLARE
    tile RECORD;
BEGIN
    FOR tile IN 
        SELECT tid, geom FROM test_tiles ORDER BY tid 
    LOOP
        INSERT INTO split_table (id, areaname, ttid, geom)
        SELECT id, areaname, tile.tid,
        CASE WHEN st_within(base.geom, tile.geom) THEN st_multi(base.geom)
        ELSE st_multi(st_intersection(base.geom, tile.geom)) END as geom
        FROM area_table as base
        WHERE st_intersects(base.geom, tile.geom);
        COMMIT;
    END LOOP;
END;
$$ LANGUAGE 'plpgsql';

已经成功测试了这个,现在我需要将它转换为一个动态过程,我可以在其中提供表名作为参数。我尝试了以下部分转换,在循环内部使用 format():

CREATE OR REPLACE PROCEDURE splitbytile(in_table text, grid_table text, split_table text)
AS $$
DECLARE
    tile RECORD;
BEGIN
    FOR tile IN 
        EXECUTE format('SELECT tid, geom FROM %I ORDER BY tid', grid_table) 
    LOOP
        EXECUTE 
            FORMAT(
                'INSERT INTO %1$I (id, areaname, ttid, geom)
                SELECT id, areaname, tile.tid,
                CASE WHEN st_within(base.geom, tile.geom) THEN st_multi(base.geom)
                ELSE st_multi(st_intersection(base.geom, tile.geom)) END as geom
                FROM %2$I as base
                WHERE st_intersects(base.geom, tile.geom)', split_table, in_table
            );
        COMMIT;
    END LOOP;
END;
$$ LANGUAGE 'plpgsql';

但是会报错

missing FROM-clause entry for table "tile"

那么,如何将程序转换为动态程序?更具体地说,如何在循环内使用 for 循环返回的记录数据类型(平铺)?请注意,它在不使用格式时有效。

【问题讨论】:

    标签: postgresql postgis plpgsql


    【解决方案1】:

    您可以使用EXECUTE ... USING 为动态查询提供参数:

    EXECUTE
       format(
          'SELECT r FROM %I WHERE c = $1.val',
          table_name
       )
    INTO result_var
    USING record_var;
    

    USING 的第一个参数将用于$1,第二个用于$2,依此类推。

    详情请见the documentation

    【讨论】:

    • 你的回答真的很有用。谢谢
    • 谢谢@LaurenzAlbe。这为我指明了正确的方向。但是,我无法使用$1.val,必须在USING 表达式中单独提供它们。
    • 也许括号会有所帮助? WHERE c = ($1).val
    【解决方案2】:

    我个人使用某种不同的方式来创建动态函数。通过连接执行功能。你也可以这样做。

    CREATE OR REPLACE FUNCTION splitbytile()
    RETURNS void AS $$
    declare 
    result1 text;
    table_name text := 'test_tiles';
    msi text := '+7 9912 231';
    msi text := 'Hello world';
    code text := 'code_name';
    _operator_id  integer := 2;
    begin
    
            query1 := 'SELECT msisdn from ' || table_name || ' where msisdn = ''' || msi::text ||''';';
            query2 := 'INSERT INTO ' || table_name || '(msisdn,usage,body,pr_code,status,sent_date,code_type,operator_id) 
             VALUES( ''' || msi::text || ''',' || true || ',''' || _body::text || ''',''' || code::text || ''',' || false || ',''' ||  time_now  || ''',' || kod_type || ',' || _operator_id  ||');';
            execute query1 into result1;
            execute query2;
    
    END;
    $function$
    

    您只需将查询设置为文本,然后您可以在任何您想要的地方执行它。也许通过检查 if 语句中的 result1 值或类似的东西。

    【讨论】:

    • 我似乎正在使用 format() 正确地将查询构造为文本。问题是当使用字符串格式化时,记录变量变得不可访问。
    • 这是一个糟糕的答案。该代码易受 SQL 注入攻击。
    • 我同意你的想法。但这也是一种方式。
    • format() 是生成动态 SQL 的更好方法
    猜你喜欢
    • 2016-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-29
    • 2015-12-04
    相关资源
    最近更新 更多