2016-03-08 494 views
2

我想通過說我是SQL的新手,但我的工作現在需要我在其中工作。PGSQL:遍歷整個表,在每一行上執行計算

我有一個包含地形點數據(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; 

此代碼執行和返回成功,但是當我檢查表,不改變已經作出。我的錯誤是什麼?

+0

你必須執行'UPDATE'查詢更改表數據。 –

+0

因此,如果我在循環完成後執行UPDATE查詢,是否會將更改提交到表中? –

回答

0

你不需要這個功能。 所有你需要的是運行此查詢:如果你想用一個函數來做到這一點,你可以使用SQL功能

UPDATE table SET resid = 1.0; 

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功能可與完成的任務3210功能。

+0

同樣,我意識到對於這個特定的功能我不需要for循環。我所做的只是試圖確定用於遍歷表中每一行的適當語法,因爲最終目標是對每一行執行一次計算。 –

1

我不確定概念驗證示例是否符合您的要求。一般來說,使用SQL,你幾乎不需要需要一個FOR循環。雖然您可以使用函數,但如果您有PostgreSQL 9.3或更高版本,則可以使用LATERAL subquery爲每行執行子查詢。

例如,創建萬個隨機3D點與隨機value柱:

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; 
4

做更新,在一個循環行由行幾乎總是一個壞主意和是極其緩慢和W不規模。你應該找到一種避免這種情況的方法。

之後話說:

您的所有功能正在做的是改變內存中的列值的價值 - 你只是修改變量的內容。如果要更新數據,您需要一個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聲明,否則您將更新所有行爲每個迭代的循環。

一個稍微更有效的解決方案是使用遊標,然後執行使用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(像我一樣)也使得編寫函數體更容易

+0

非常好。這是我正在尋找的答案。非常感謝你花時間陪伴。 –