Search code examples
sqlpostgresqlpostgis

Iterate through table, perform calculation on each row


I would like to preface this by saying I am VERY new to SQL, but my work now requires that I work in it.

I have a dataset containing topographical point data (x,y,z). I am trying to build a KNN model based on this data. For every point 'P', I search for the 100 points in the data set nearest P (nearest meaning geographically nearest). I then average the values of these points (this average is known as a residual), and add this value to the table in the 'resid' column.

As a proof of concept, I am trying to simply iterate over the table, and set the value of the 'resid' column to 1.0 in every row.

My query is this:

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; 

This code executes and returns successfully, but when I check the table, no alterations have been made. What is my error?


Solution

  • Doing updates row-by-row in a loop is almost always a bad idea and will be extremely slow and won't scale. You should really find a way to avoid that.

    After having said that:

    All your function is doing is to change the value of the column value in memory - you are just modifying the contents of a variable. If you want to update the data you need an update statement:

    You need to use an UPDATE inside the loop:

    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;
    

    Note that you have to add a where condition on the primary key to the update statement otherwise you would update all rows for each iteration of the loop.

    A slightly more efficient solution is to use a cursor, and then do the update using 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?

    No. The call to the function runs in the context of the calling transaction. So you need to commit after running SELECT LoopThroughTable() if you have disabled auto commit in your SQL client.


    Note that the language name is an identifier, do not use single quotes around it. You should also avoid using keywords like row as variable names.

    Using dollar quoting (as I did) also makes writing the function body easier