【问题标题】:Iterate through table, perform calculation on each row遍历表,对每一行进行计算
【发布时间】:2016-06-21 18:50:26
【问题描述】:

我想先说我对 SQL 非常陌生,但我现在的工作需要我在其中工作。

我有一个包含地形点数据 (x,y,z) 的数据集。我正在尝试基于此数据构建 KNN 模型。对于每个点“P”,我在数据集中搜索最接近 P 的 100 个点(最近的意思是地理上最近的)。然后我平均这些点的值(这个平均值称为残差),并将这个值添加到“resid”列中的表中。

作为概念证明,我尝试简单地遍历表,并将每行中“resid”列的值设置为 1.0。

我的查询是这样的:

CREATE OR REPLACE FUNCTION LoopThroughTable() RETURNS VOID AS '
DECLARE row table%rowtype;
BEGIN
    FOR row in SELECT * FROM table LOOP
        SET row.resid = 1.0;
    END LOOP;
END

' LANGUAGE 'plpgsql';

SELECT LoopThroughTable() as output; 

这段代码执行成功并返回,但是当我检查表格时,没有进行任何更改。我的错误是什么?

【问题讨论】:

  • 您必须执行UPDATE查询才能更改表数据。
  • 那么如果我在循环结束后执行 UPDATE 查询,是否会将更改提交到表中?

标签: sql postgresql postgis


【解决方案1】:

以下是更新表中行的简单示例:

假设行id字段id

更新所有行:

UPDATE my_table SET field1='some value'
WHERE id IN (SELECT id FROM staff)

选择性行更新

UPDATE my_table SET field1='some value'
WHERE id IN (SELECT id FROM staff WHERE field2='same value')

【讨论】:

    【解决方案2】:

    在循环中逐行更新几乎总是一个坏主意,并且非常慢并且无法扩展。你真的应该想办法避免这种情况。

    说了这么多:

    你的函数所做的只是改变内存中列值的值——你只是在修改一个变量的内容。如果你想更新数据,你需要一个update 声明:

    您需要在循环内使用UPDATE

    CREATE OR REPLACE FUNCTION LoopThroughTable() 
      RETURNS VOID 
    AS
    $$
    DECLARE 
       t_row the_table%rowtype;
    BEGIN
        FOR t_row in SELECT * FROM the_table LOOP
            update the_table
                set resid = 1.0
            where pk_column = t_row.pk_column; --<<< !!! important !!!
        END LOOP;
    END;
    $$ 
    LANGUAGE plpgsql;
    

    请注意,您必须将主键上的 where 条件添加到 update 语句,否则您将更新 all 行的 each 循环的迭代。

    稍微更有效的解决方案是使用游标,然后使用where current of进行更新

    CREATE OR REPLACE FUNCTION LoopThroughTable() 
      RETURNS VOID 
    AS $$
    DECLARE 
       t_curs cursor for 
          select * from the_table;
       t_row the_table%rowtype;
    BEGIN
        FOR t_row in t_curs LOOP
            update the_table
                set resid = 1.0
            where current of t_curs;
        END LOOP;
    END;
    $$ 
    LANGUAGE plpgsql;
    

    所以如果我在循环完成后执行 UPDATE 查询,那会提交对表的更改吗?

    没有。对函数的调用在调用事务的上下文中运行。因此,如果您在 SQL 客户端中禁用了自动提交,则在运行 SELECT LoopThroughTable() 后需要 commit


    请注意,语言名称是一个标识符,不要在其周围使用单引号。您还应该避免使用像 row 这样的关键字作为变量名。

    使用dollar quoting(和我一样)也可以更轻松地编写函数体

    【讨论】:

    • 优秀。这就是我一直在寻找的答案。非常感谢您的宝贵时间。
    • 如果您必须根据该行更改多个表怎么办?
    【解决方案3】:

    我不确定概念证明示例是否符合您的要求。通常,使用 SQL,您几乎需要 FOR 循环。虽然您可以使用函数,但如果您有 PostgreSQL 9.3 或更高版本,则可以使用 LATERAL subquery 对每一行执行子查询。

    例如,使用随机 value 列创建 10,000 个随机 3D 点:

    CREATE TABLE points(
      gid serial primary key,
      geom geometry(PointZ),
      value numeric
    );
    CREATE INDEX points_geom_gist ON points USING gist (geom);
    INSERT INTO points(geom, value)
    SELECT ST_SetSRID(ST_MakePoint(random()*1000, random()*1000, random()*100), 0), random()
    FROM generate_series(1, 10000);
    

    对于每个点,搜索最近的 100 个点(除了有问题的点),找到点的value 与最近的 100 个的平均值之间的残差:

    SELECT p.gid, p.value - avg(l.value) residual
    FROM points p,
      LATERAL (
        SELECT value
        FROM points j
        WHERE j.gid <> p.gid
        ORDER BY p.geom <-> j.geom
        LIMIT 100
    ) l
    GROUP BY p.gid
    ORDER BY p.gid;
    

    【讨论】:

    • 那么横向子查询是否需要是一个选择?我见过的唯一例子是一个选择,因为它基本上是另一个 FROM。你真的可以用这种方式更新多个表吗?
    【解决方案4】:

    你不需要一个函数。 你只需要运行这个查询:

    UPDATE table SET resid = 1.0;
    

    如果你想用一个函数来做,你可以使用SQL函数:

    CREATE OR REPLACE FUNCTION LoopThroughTable()
      RETURNS VOID AS
    $BODY$
    UPDATE table SET resid = 1.0;
    $BODY$
      LANGUAGE sql VOLATILE
    

    如果你想使用plpgsql 那么函数是:

    CREATE OR REPLACE FUNCTION LoopThroughTable()
      RETURNS void AS
    $BODY$
    begin
           UPDATE table SET resid = 1.0;
    end;
    $BODY$
      LANGUAGE plpgsql VOLATILE
    

    请注意,对于可以使用 Sql 函数完成的任务,不建议使用 plpgsql 函数。

    【讨论】:

    • 再次,我意识到对于这个特定的功能,我不需要 for 循环。我所做的只是尝试确定迭代表中每一行的正确语法,因为最终目标是对每一行执行计算。
    猜你喜欢
    • 2019-06-07
    • 2022-01-12
    • 2019-04-03
    • 2018-09-02
    • 2020-04-10
    • 2021-09-10
    • 1970-01-01
    • 1970-01-01
    • 2020-04-05
    相关资源
    最近更新 更多