I have one target table (already populated with data) and another one (source table) from wich 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 allways use 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.
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');
Related
I am building chatroom using Go,PostgreSQL.
I have this table -> messages
id | user | message_id | message
I want for each user, message_id Start from 1 and And increase automatically.
for example :
id | user | message_id | message
1 | one | 1 | Hello
2 | two | 1 | Hi!
3 | one | 2 | How are you?
4 | one | 3 | :)
Thanks in advance.
Without duplicate values and to safely increase message_id field we use SELECT FOR UPDATE command.
FOR UPDATE causes the rows retrieved by the SELECT statement to be locked as though for update. This prevents them from being locked, modified or deleted by other transactions until the current transaction ends. That is, other transactions that attempt UPDATE, DELETE, SELECT FOR UPDATE, SELECT FOR NO KEY UPDATE, SELECT FOR SHARE or SELECT FOR KEY SHARE of these rows will be blocked until the current transaction ends; conversely, SELECT FOR UPDATE will wait for a concurrent transaction that has run any of those commands on the same row, and will then lock and return the updated row.
Now let's write a simple function for inserting messages:
CREATE OR REPLACE FUNCTION insert_message(p_user character varying, p_message text)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
v_new_id integer;
v_id integer;
begin
-- selecting user last inserting message and get last message_id, and blocking this record for update command
select mm.id, mm.message_id into v_id, v_new_id
from messages mm
where
mm."user" = p_user
order by
mm.message_id desc
limit 1
for update;
if (v_new_id is null) then
v_new_id = 0;
end if;
insert into messages
(
"user",
message_id,
message_text
) values
(
p_user,
v_new_id + 1,
p_message
);
-- after inserting message we must unblock record
if (v_new_id > 0) then
update messages
set
updated_date = now()
where id = v_id;
end if;
END;
$function$
;
As you can see from the code for increasing message_id we must filter records by user and then the ordered message_id field descending. Given that in future this table will have million records then performance will be bad. For good performancing let's write new function:
CREATE OR REPLACE FUNCTION insert_message(p_user character varying, p_message text)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
v_new_id integer;
v_id integer;
begin
-- in users table add new last_id field, for storing last message_id value by user
-- and then block this record
select last_id into v_new_id from users
where "user" = p_user
for update;
insert into messages
(
"user",
message_id,
message_text
) values
(
p_user,
v_new_id + 1,
p_message
);
-- unblocking user record
update users
set
last_id = v_new_id + 1
where "user" = p_user;
END;
$function$
;
This code was simple and high-performance.
I need a stored procedure where I do two things. First I want to get all schema names that is prefixed with 'myschema_', so I've done this:
SELECT schema_name
FROM information_schema.schemata
WHERE schema_name LIKE 'myschema_%'
Next, I want a while loop that loops through each schema and gets 'name' from the 'person' table. This means that I somehow have to feed the result from the first select schema statement in as a parameter in the next call. And all of this should be one stored procedure.
This is what I want the stored procedure to return:
| schema | name |
-------------------
| schema_1 | Mike |
| schema_1 | Jane |
| schema_2 | Rich |
| schema_3 | Fred |
| schema_4 | Chris|
How do I do this?
You would need plpgsql block or procedure and dynamic SQL to do this.
create or replace function my_function()
returns table (sname text, pname text) as
$$
DECLARE
running_schema text;
running_name text;
DYN_SQL constant text default 'select "name" from %I.person';
BEGIN
for running_schema in --your query
SELECT schema_name FROM information_schema.schemata WHERE schema_name LIKE 'myschema_%'
loop
for running_name in execute format(DYN_SQL, running_schema) loop
sname := running_schema;
pname := running_name;
return next;
end loop;
end loop;
END;
$$ language plpgsql;
-- Unit test
SELECT sname, pname from my_function();
I have a table with partially consecutive integer ids, i.e. there are blocks such as 1,2,3, 6,7,8, 10, 23,24,25,26.
the gap size is dynamic
the length of the blocks is dynamic
I am breaking my head about a simple solution that selects from the table
and includes a column where the value corresponds to the first id of the respective block.
I.e. something like this
select id, first(id) over <what goes here?> first from table;
The result should look as following
| id | first |
|----|-------|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 6 | 6 |
| 7 | 6 |
| 8 | 6 |
| 10 | 10 |
| 23 | 23 |
| 24 | 23 |
| 25 | 23 |
| 26 | 23 |
Afterwards i could use this column nicely with the partition by window function clause.
What I came up with so far always looked similar to this and didn't succeed:
WITH foo AS (
SELECT LAG(id) OVER (ORDER BY id) AS previous_id,
id AS id,
id - LAG(id, 1, id) OVER (ORDER BY id) AS first_in_sequence
FROM table)
SELECT *,
FIRST_VALUE(id) OVER (ORDER BY id) AS first
FROM foo
ORDER BY id;
Defining a custom postgres function would also be an acceptable solution.
Thanks for any advice,
Marti
In Postgres you can create a custom aggregate. Example:
create or replace function first_in_series_func(int[], int)
returns int[] language sql immutable
as $$
select case
when $1[2] is distinct from $2- 1 then array[$2, $2]
else array[$1[1], $2] end;
$$;
create or replace function first_in_series_final(int[])
returns int language sql immutable
as $$
select $1[1]
$$;
create aggregate first_in_series(int) (
sfunc = first_in_series_func,
finalfunc = first_in_series_final,
stype = int[]
);
Db<>fiddle.
Read in the docs: User-Defined Aggregates
Here is an idea how this could be done. An implicit cursor is not horribly efficient though.
create or replace function ff()
returns table (r_id integer, r_first integer)
language plpgsql as
$$
declare
running_previous integer;
running_id integer;
running_first integer := null;
begin
for running_id in select id from _table order by id loop
if running_previous is distinct from running_id - 1 then
running_first := running_id;
end if;
r_id := running_id;
r_first := running_first;
running_previous := running_id;
return next;
end loop;
end
$$;
-- test
select * from ff() as t(id, first);
Getting unexpected result from function. I just need two result sets from the code that I have written in the function but instead getting some unnamed portal issue.
I have tried same using cursor.Which is as follows.
CREATE OR REPLACE FUNCTION User(param_state CHAR(10)) RETURNS SETOF refcursor AS $$
DECLARE
ref1 refcursor; -- Declare cursor variables
ref2 refcursor;
BEGIN
OPEN ref1 FOR select * from Table1
WHERE code = param_state;
RETURN NEXT ref1;
OPEN ref2 FOR select * from Table2
WHERE code= param_state;
RETURN NEXT ref2;
END;
$$ LANGUAGE plpgsql;
Expected output should be to 2 result set of 2 column each
-------------------
|party_code | limit|
|------------------|
|T001 | 120 |
-------------------
-------------------
|party_code | Sal |
|------------------|
|T001 | 1000 |
-------------------
But the output is
---------------------
|<unnamed portal 34>|
---------------------
|<unnamed portal 35>|
Have you tried to name your cursors ..
...
DECLARE
ref1 refcursor := 'mycursor1' ;
ref2 refcursor := 'mycursor2' ;
...
.. and fetch the results using their names ..
SELECT * FROM "User"('T001');
BEGIN;
FETCH ALL FROM mycursor2;
FETCH ALL FROM mycursor1;
END;
FETCH ALL FROM mycursor2;
code | Sal
------+------
T001 | 1000
(1 row)
postgres=# FETCH ALL FROM mycursor1;
code | limit
------+-------
T001 | 120
(1 row)
I just finished writing my first PLSQL function. Here what it does.
The SQL function attempt to reset the duplicate timestamp to NULL.
From table call_records find all timestamp that are duplicated.(using group by)
loop through each timestamp.Find all record with same timestamp (times-1, so that only 1 record for a given times is present)
From all the records found in step 2 update the timestamp to NULL
Here how the SQL function looks like.
CREATE OR REPLACE FUNCTION nullify() RETURNS INTEGER AS $$
DECLARE
T call_records.timestamp%TYPE;
-- Not sure why row_type does not work
-- R call_records%ROWTYPE;
S integer;
CRNS bigint[];
TMPS bigint[];
sql_stmt varchar = '';
BEGIN
FOR T,S IN (select timestamp,count(timestamp) as times from call_records where timestamp IS NOT NULL group by timestamp having count(timestamp) > 1)
LOOP
sql_stmt := format('SELECT ARRAY(select plain_crn from call_records where timestamp=%s limit %s)',T,S-1);
EXECUTE sql_stmt INTO TMPS;
CRNS := array_cat(CRNS,TMPS);
END LOOP;
sql_stmt = format('update call_records set timestamp=null where plain_crn in (%s)',array_to_string(CRNS,','));
RAISE NOTICE '%',sql_stmt;
EXECUTE sql_stmt ;
RETURN 1;
END
$$ LANGUAGE plpgsql;
Help me understand more PL/pgSQL language my suggesting me how it can be done better.
#a_horse_with_no_name: Here how the DB structure looks like
\d+ call_records;
id integer primary key
plain_crn bigint
timestamp bigint
efd integer default 0
id | efd | plain_crn | timestamp
----------+------------+------------+-----------
1 | 2016062936 | 8777444059 | 14688250050095
2 | 2016062940 | 8777444080 | 14688250050095
3 | 2016063012 | 8880000000 | 14688250050020
4 | 2016043011 | 8000000000 | 14688240012012
5 | 2016013011 | 8000000001 | 14688250050020
6 | 2016022011 | 8440000001 |
Now,
select timestamp,count(timestamp) as times from call_records where timestamp IS NOT NULL group by timestamp having count(timestamp) > 1
timestamp | count
-----------------+-----------
14688250050095 | 2
14688250050020 | 2
All that I want is to update the duplicate timestamp to null so that only one of them record has the given timestamp.
In short the above query should return result like this
select timestamp,count(timestamp) as times from call_records where timestamp IS NOT NULL group by timestamp;
timestamp | count
-----------------+-----------
14688250050095 | 1
14688250050020 | 1
You can use array variables directly (filter with predicate =ANY() - using dynamic SQL is wrong for this purpose:
postgres=# DO $$
DECLARE x int[] = '{1,2,3}';
result int[];
BEGIN
SELECT array_agg(v)
FROM generate_series(1,10) g(v)
WHERE v = ANY(x)
INTO result;
RAISE NOTICE 'result is: %', result;
END;
$$;
NOTICE: result is: {1,2,3}
DO
Next - this is typical void function - it doesn't return any interesting. Usually these functions returns nothing when all is ok or raises exception. The returning 1 RETURN 1 is useless.
CREATE OR REPLACE FUNCTION foo(par int)
RETURNS void AS $$
BEGIN
IF EXISTS(SELECT * FROM footab WHERE id = par)
THEN
...
ELSE
RAISE EXCEPTION 'Missing data for parameter: %', par;
END IF;
END;
$$ LANGUAGE plpgsql;