遍历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')
首先我想说我是 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')