PGSQL:遍历表,对每一行执行计算

时间:2016-03-08 00:51:34

标签: sql postgresql postgis

我想在前言中说我对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; 

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

4 个答案:

答案 0 :(得分:8)

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

说完之后:

您所做的所有功能都是更改内存中列值的值 - 您只是修改变量的内容。如果要更新数据,则需要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条件,否则您将更新所有行< strong>每次循环迭代。

稍微更有效的解决方案是使用游标,然后使用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客户端中禁用了自动提交,则在运行commit后需要SELECT LoopThroughTable()

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

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

答案 1 :(得分:1)

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

答案 2 :(得分:0)

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

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

答案 3 :(得分:-1)

你不需要一个功能。 您只需运行此查询:

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

请注意,建议不要将plpgsql函数用于可以使用Sql函数执行的任务。