遍历table,对每一行进行计算

Iterate through table, perform calculation on each row

首先我想说我是 SQL 的新手,但我现在的工作要求我在其中工作。

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

作为概念证明,我试图简单地遍历 table,并将每一行的 '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; 

此代码执行并 returns 成功,但是当我检查 table 时,没有进行任何更改。我的错误是什么?

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

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 函数。

我不确定概念验证示例是否符合您的要求。一般来说,使用 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;

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

说完之后:

您的函数所做的只是更改内存中列值的值 - 您只是在修改变量的内容。如果你想更新你需要一个 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;

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

一个稍微更有效的解决方案是使用游标,然后使用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;

So if I execute the UPDATE query after the loop has finished, will that commit the changes to the table?

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


注意语言名称是一个标识符,不要用单引号括起来。您还应避免使用 row 等关键字作为变量名。

使用 dollar quoting(就像我所做的那样)也使编写函数体更容易

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

假设行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')