postgresql

PostgreSQL, bulk update using data from another table


I have one target table (already populated with data) and another one (source table) from which I need to retrieve data into first one.

target_table

postgres=# select id,id_user from ttasks;
 id | id_user
----+---------
  1 |
  2 |
  3 |
  4 |
  5 |
(5 rows)

source_table

postgres=# select id from tusers where active;
  id
------
 1011
 1012
 1013
 1014
(4 rows)

I need to update id_user column of ttasks table using id's from tusers table, so final result on ttasks should be:

# expected result after update [select id, id_user from ttasks;]
 id | id_user
----+---------
  1 |    1011
  2 |    1012
  3 |    1013
  4 |    1014
  5 |    1011
(5 rows)

What I have tried (similar to INSERT ... FROM ... statement):

postgres=# update ttasks t1 set id_user = q1.id from (select id from tusers where active) q1 returning t1.id,t1.id_user;
 id | id_user
----+---------
  1 |    1011  
  2 |    1011
  3 |    1011
  4 |    1011  
  5 |    1011  
(5 rows)

but this query always uses the first id from my q1 subquery.

Any idea, help or even solution on how can I accomplish this task? Thank You very much!

p.s. This is my first post on this community so please be gentle with me if something in my question is not conforming with your rules.


Solution

  • Finally, after one of my friends told me that not everything could be coded in a "keep it stupid simple" manner, I wrote a plpqsql (PL/PGSQL) function that does the job for me and more than, allow to use some advanced filters inside.

    CREATE OR REPLACE FUNCTION assign_workers_to_tasks(i_workers_table regclass, i_workers_table_tc text, i_tasks_table regclass, i_tasks_table_tc text, i_workers_filter text DEFAULT ''::text, i_tasks_filter text DEFAULT ''::text)
      RETURNS void AS
    $BODY$
      DECLARE workers int[]; i integer; total_workers integer; r record; get_tasks text;
    begin
        i_workers_filter := 'where '||nullif(i_workers_filter,'');
        i_tasks_filter := 'where '||nullif(i_tasks_filter,'');
        EXECUTE format('select array_agg(%s) from (select %s from %s %s order by %s) q', i_workers_table_tc, i_workers_table_tc,i_workers_table, i_workers_filter,i_workers_table_tc)
        INTO workers; --available [filtered] workers
        total_workers := coalesce(array_length(workers,1),0); --total of available [filtered] workers
        IF total_workers = 0 THEN
          EXECUTE format('update %s set %s=null %s', i_tasks_table, i_tasks_table_tc, i_tasks_filter);
          RETURN;
        END IF;
        i :=1;
        get_tasks := format('select * from %s %s',i_tasks_table,i_tasks_filter); --[filtered] tasks
        FOR r IN EXECUTE (get_tasks) LOOP
          EXECUTE format('update %s set %s=%s where id = %s', i_tasks_table, i_tasks_table_tc, workers[i],r.id);
          i := i+1;
          IF i>total_workers THEN i := 1; END IF;
        END LOOP;
        RETURN;
    end;
      $BODY$
      LANGUAGE plpgsql VOLATILE
      COST 100;
    ALTER FUNCTION assign_workers_to_tasks(regclass, text, regclass, text, text, text)
      OWNER TO postgres;
    

    and to fulfil my own question:

    select assign_workers_to_tasks('tusers','id','ttasks','id_user','active');