Insert rows based on array of UUIDs - sql

Looking for a way to insert a list of records based on an array of UUIDs. Here's my example code:
CREATE OR REPLACE FUNCTION "AddGroupUsers" (
"#OrganizationID" UUID,
"#GroupID" UUID,
"#UserIDs" UUID[]
)
RETURNS viud AS
$func$
BEGIN
FOR index IN "#UserIDs" LOOP
INSERT INTO
"UserGroups" (
"UserID",
"GroupID",
"OrganizationID"
)
VALUES (
"#UserID"[index],
"#GroupID",
"#OrganizationID"
);
END LOOP;
END;
$func$ LANGUAGE PLPGSQL;
Obviously doesn't work, lol.
I want to be able to call:
SELECT "AddGroupUsers"(
'cb6e96db-73db-4b07-811f-c54b61d09fa4',
'451a9ab7-02f6-4f63-bb87-80ad531ab490'
array(
'451a9ab7-02f6-4f63-bb87-80ad531ab490',
'451a9ab7-02f6-4f63-bb87-80ad531ab491',
'451a9ab7-02f6-4f63-bb87-80ad531ab492',
'451a9ab7-02f6-4f63-bb87-80ad531ab493',
'451a9ab7-02f6-4f63-bb87-80ad531ab494'
)::uuid[]
);
As a side note I have a unique key constraint that ensures only one record for a UserID and GroupID every exist. If the second array value breaks that rule will the whole query fail and how can I ignore it to ensure the rest of the values get inserted?

Use unnest and plain sql in instead of plpgsql. With this table:
create table user_groups (
org_id uuid, grp_id uuid, use_id uuid,
unique (grp_id, use_id)
);
This function will insert non existent:
create or replace function AddGroupUsers(
_org_id uuid, _grp_id uuid, _use_id uuid[]
) returns setof user_groups as $$
insert into user_groups (org_id, grp_id, use_id)
select s.org_id, grp_id, use_id
from
(
select
_org_id as org_id,
_grp_id as grp_id,
unnest(_use_id) as use_id
) s
left join
user_groups ug using (grp_id, use_id)
where ug.grp_id is null
returning *
;
$$ language sql;
Usage:
select *
from AddGroupUsers(
'cb6e96db-73db-4b07-811f-c54b61d09fa4'::uuid,
'451a9ab7-02f6-4f63-bb87-80ad531ab490'::uuid,
array[
'451a9ab7-02f6-4f63-bb87-80ad531ab490',
'451a9ab7-02f6-4f63-bb87-80ad531ab491',
'451a9ab7-02f6-4f63-bb87-80ad531ab492',
'451a9ab7-02f6-4f63-bb87-80ad531ab493',
'451a9ab7-02f6-4f63-bb87-80ad531ab494'
]::uuid[]
);
org_id | grp_id | use_id
--------------------------------------+--------------------------------------+--------------------------------------
cb6e96db-73db-4b07-811f-c54b61d09fa4 | 451a9ab7-02f6-4f63-bb87-80ad531ab490 | 451a9ab7-02f6-4f63-bb87-80ad531ab490
cb6e96db-73db-4b07-811f-c54b61d09fa4 | 451a9ab7-02f6-4f63-bb87-80ad531ab490 | 451a9ab7-02f6-4f63-bb87-80ad531ab491
cb6e96db-73db-4b07-811f-c54b61d09fa4 | 451a9ab7-02f6-4f63-bb87-80ad531ab490 | 451a9ab7-02f6-4f63-bb87-80ad531ab492
cb6e96db-73db-4b07-811f-c54b61d09fa4 | 451a9ab7-02f6-4f63-bb87-80ad531ab490 | 451a9ab7-02f6-4f63-bb87-80ad531ab493
cb6e96db-73db-4b07-811f-c54b61d09fa4 | 451a9ab7-02f6-4f63-bb87-80ad531ab490 | 451a9ab7-02f6-4f63-bb87-80ad531ab494

Based on this answer and the official documentation, you could declare a variable to store each user ID, like this:
CREATE OR REPLACE FUNCTION AddGroupUsers (
"#OrganizationID" UUID,
"#GroupID" UUID,
"#UserIDs" UUID[]
)
RETURNS void AS
$func$
DECLARE uID UUID;
BEGIN
FOREACH uID IN ARRAY "#UserIDs" LOOP
INSERT INTO
UserGroups (
UserID,
GroupID,
OrganizationID
)
VALUES (
uID,
"#GroupID",
"#OrganizationID"
);
END LOOP;
END;
$func$ LANGUAGE PLPGSQL;
And to actually call it:
SELECT AddGroupUsers(
'cb6e96db-73db-4b07-811f-c54b61d09fa4'::uuid,
'451a9ab7-02f6-4f63-bb87-80ad531ab490'::uuid,
array[
'451a9ab7-02f6-4f63-bb87-80ad531ab490',
'451a9ab7-02f6-4f63-bb87-80ad531ab491',
'451a9ab7-02f6-4f63-bb87-80ad531ab492',
'451a9ab7-02f6-4f63-bb87-80ad531ab493',
'451a9ab7-02f6-4f63-bb87-80ad531ab494'
]::uuid[]
);
(Note the square brackets instead of parenthesis)

Related

PostgreSQL: Trigger INSERT INTO SELECT from other table

I'm trying to create a trigger that will add a new row processed entry to a destination table each time a new row is created in the source table.
Step 1 Create destination table:
CREATE TABLE public.destination_table (
id serial PRIMARY KEY,
created_at TIMESTAMP NOT NULL,
sale_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
product_name VARCHAR NOT NULL,
url VARCHAR NOT NULL,
shop_id VARCHAR NOT NULL,
user_id VARCHAR)
Step 2 Create trigger function:
CREATE OR REPLACE FUNCTION triger_function() RETURNS TRIGGER AS
$BODY$
BEGIN
INSERT INTO public.destination_table ( created_at, sale_id, product_id, product_name, url, shop_id, user_id)
SELECT created_at,
sale_id,
product_id,
product_name,
split_part(url::text, '?'::text, 1) AS url,
shop_id,
((((((((data #>> '{}'::text[])::jsonb) #>> '{}'::text[])::jsonb) -> 'local_storage'::text) -> 'data'::text) #>> '{}'::text[])::jsonb) ->> 'user_id'::varchar AS user_id
FROM source_table;
RETURN new;
END;
$BODY$
language plpgsql;
** The Select query inside function work normally when single running.
Step 3 Create trigger:
CREATE TRIGGER trigger_records
AFTER INSERT ON public.source_table
FOR EACH ROW
EXECUTE PROCEDURE triger_function();
The problem is that Trigger does not work, which means it does not record new entries in the target table. Can't figure out where the error is.
You should be using the NEW record in the trigger function to reference the newly inserted data instead of a select, i.e.:
CREATE OR REPLACE FUNCTION triger_function() RETURNS TRIGGER AS
$BODY$
BEGIN
INSERT INTO public.destination_table ( created_at, sale_id, product_id, product_name, url, shop_id, user_id)
VALUES(NEW.created_at,
NEW.sale_id,
NEW.product_id,
NEW.product_name,
split_part(NEW.url::text, '?'::text, 1),
NEW.shop_id,
((((((((NEW.data #>> '{}'::text[])::jsonb) #>> '{}'::text[])::jsonb) -> 'local_storage'::text) -> 'data'::text) #>> '{}'::text[])::jsonb) ->> 'user_id'::varchar)
RETURN new;
END;
$BODY$
language plpgsql;

How to make an insert with a combination of a select and passed parameters in a stored function

I have two tables, e.g.:
CREATE TABLE table_1
(
one_column INTEGER,
two_column INTEGER,
three_column INTEGER
);
CREATE TABLE table_2
(
id SERIAL,
column_1 INTEGER,
column_2 INTEGER,
column_3 INTEGER,
name TEXT,
step INTEGER
);
I have a stored function which receives a number of parameters. Within the function, I need to INSERT a row into a table using a (dynamic?) combination of the results from a SELECT and two of the function parameters. Currently I've got something similar to the following pseudo-code...
CREATE OR REPLACE FUNCTION function_p (
p_id INTEGER,
p_name TEXT DEFAULT '',
p_step INTEGER DEFAULT NULL
)
RETURNS VOID AS $$
BEGIN
INSERT INTO table_2 (
column_1,
column_2,
column_3,
p_name,
p_step
)
SELECT
one_column,
two_column,
three_column
FROM table_1
WHERE id = p_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Should the INSERT be more like this?
INSERT INTO table_2 (
column_1,
column_2,
column_3,
name,
step
)
(SELECT
one_column,
two_column,
three_column
FROM table_1
WHERE id = p_id),
p_name,
p_step;
Use format to create the insert statement. Also, you problably want a procedure, e.g.
CREATE OR REPLACE PROCEDURE function_p (
p_name TEXT DEFAULT '',
p_step INT DEFAULT NULL
)
LANGUAGE plpgsql AS $$
BEGIN
EXECUTE format('
INSERT INTO table_2 (column_1,column_2,column_3,name,step)
SELECT one_column, two_column,three_column,%L,%s
FROM table_1',p_name,p_step);
END;
$$
Keep in mind that the columns number (and types) used in the INSERT statement has to match with those coming from the SELECT.
Demo: db<>fiddle

Table Variable and Table-Valued Function equivalent in PostgreSQL

I need to create a function in PostgreSQL for the following :
Query multiple tables based on a business logic (all result sets return the same type of data)
Compile all result sets into one table and return that table
Is it possible to accomplish this without using the temp tables in PostgreSQL?
I currently do this in Microsoft SQL server using Table Variables, below is a sample function:
create FUNCTION test(#search_in nvarchar(500))
RETURNS #data_table TABLE
(
item_id int,
item_type nvarchar(1),
first_name nvarchar(100),
last_name nvarchar(100))
) AS
BEGIN
-- from first table
if charindex('search_in_authors', #search_in) > 0
insert into #data_table
select item_id, 'a', first_name, last_name
from authors
where first_name = 'james'
-- from second table
if charindex('search_in_editors', #search_in) > 0
insert into #data_table
select item_id, 'e', first_name, last_name
from editors
where first_name = 'james'
-- from third table
if charindex('search_in_publishers', #search_in) > 0
insert into #data_table
select item_id, 'p', first_name, last_name
from publishes
where first_name = 'james'
-- there could be more like these based on the business logic...
(...)
-- finally return the records compiled in #data_table
RETURN
END
Sample calls to the function:
select * from dbo.test('search_in_authors')
select * from dbo.test('search_in_authors, search_in_editors')
select * from dbo.test('search_in_authors, search_in_editors,search_in_publishers ')
Are there any options in PostgreSQL to achieve this other than using a temp table ?
Thanks,
San
You can use RETURN QUERY to add the result of various queries to the output.
CREATE OR REPLACE FUNCTION public.testf()
RETURNS TABLE(id INTEGER, name text)
STABLE
AS $$
DECLARE
BEGIN
RETURN QUERY select 1 as id, 'abc' as name;
RETURN QUERY select 2 as id, 'def' as name;
RETURN QUERY select 3 as id, 'xyz' as name;
-- Final return as set is now complete.
return;
END;
$$ LANGUAGE plpgsql;
select * from public.testf();
id | name
----+------
1 | abc
2 | def
3 | xyz
(3 rows)

How correctly create multiple entries by arrays in PostgreSQL?

In PostgreSQL database I have table which looks like this:
| question_id | question_text | widget | required | position |
|-------------|---------------|--------|----------|----------|
| int | text | int | boolean | int |
Second table which called factors_questions_relationship looks like this:
| factor_id | question_id |
|-------------|---------------|
| int | text |
I am trying to create function which would create multiple rows and return array of ids of new created entries. How correctly to make such function?
CREATE OR REPLACE FUNCTION factorio(
FACTOR_IDENTIFIER INT,
TEXT_ARR VARCHAR[],
WIDGET_ARR INT[],
REQUIRED_ARR BOOLEAN[],
POSITION_ARR INT[]
) RETURNS SETOF INT AS $$
BEGIN
RETURN QUERY
WITH RESULT_SET AS (
INSERT INTO QUESTIONS (TEXT, WIDGET, REQUIRED, POSITION)
SELECT
UNNEST(ARRAY[TEXT_ARR]) AS TEXT,
UNNEST(ARRAY[WIDGET_ARR]) AS WIDGET,
UNNEST(ARRAY[REQUIRED_ARR]) AS REQUIRED,
UNNEST(ARRAY[POSITION_ARR]) AS POSITION
RETURNING ID
)
--
INSERT INTO factors_questions_relationship (FACTOR_ID, QUESTION_ID)
SELECT FACTOR_IDENTIFIER FACTOR_ID, QUESTION_ID FROM UNNEST(ARRAY[array_agg(SELECT ID FROM RESULT_SET)]) QUESTION_ID
--
SELECT ID FROM RESULT_SET;
END;
$$ LANGUAGE plpgsql;
You can simply unnest them in columns
select
unnest(array['quick','brown','fox']) as question_text,
unnest(array[1,2,3]) as widget_id
Whereas putting them in FROM clause, would result to cartesian product:
select question_text, widget_id
from
unnest(array['quick','brown','fox']) as question_text,
unnest(array[1,2,3]) as widget_id
Output:
To obtain the Ids generated from newly-inserted records, use return query + returning id combo. Sample test:
create table z
(
id int generated by default as identity primary key,
question_text text
);
create or replace function insert_multiple_test()
returns table (someid int) as
$$
begin
return query
with resulting_rows as
(
insert into z(question_text) values
('hello'),
('你好'),
('hola')
returning id
)
select id from resulting_rows;
end;
$$ language 'plpgsql';
select * from insert_multiple_test();
Output:
Here's for SETOF:
create table z(id int generated by default as identity primary key, question_text text);
create or replace function insert_multiple_test()
returns setof int
as
$$
begin
return query
with resulting_rows as
(
insert into z(question_text) values
('hello'),
('你好'),
('hola')
returning id
)
select id from resulting_rows;
end;
$$ language 'plpgsql';
select x.the_id from insert_multiple_test() as x(the_id);
Output:
If you don't need to run multiple queries, you can just use LANGUAGE 'sql', it's simpler:
create table z
(
id int generated by default as identity primary key,
question_text text
);
create or replace function insert_multiple_test()
returns setof int as
$$
insert into z(question_text) values
('hello'),
('你好'),
('hola')
returning id;
$$ language 'sql';
select x.id_here from insert_multiple_test() as x(id_here);
Output:

PL/pgSQL: finding all groups person belongs to (also indirectly)

Simple intro:
I have a database with users and groups.
Every user might be a member of one or more groups.
Every group might have one or more parent groups.
Schema:
CREATE TABLE users(
username VARCHAR(64) NOT NULL PRIMARY KEY,
password VARCHAR(64) NOT NULL,
enabled BOOLEAN NOT NULL);
CREATE TABLE groups (
id bigserial NOT NULL PRIMARY KEY,
group_name VARCHAR(64) NOT NULL);
CREATE TABLE groups_inheritance (
group_id bigint NOT NULL,
parent_group_id bigint NOT NULL,
CONSTRAINT fk_group_inheritance_group FOREIGN KEY(group_id) REFERENCES groups(id),
CONSTRAINT fk_group_inheritance_group_2 FOREIGN KEY(parent_group_id) REFERENCES groups(id),
CONSTRAINT unique_uk_groups_inheritance UNIQUE(group_id, parent_group_id));
CREATE TABLE group_members (
id bigint PRIMARY KEY,
username VARCHAR(64) NOT NULL,
group_id bigint NOT NULL,
CONSTRAINT fk_group_members_username FOREIGN KEY(username) REFERENCES users(username),
CONSTRAINT fk_group_members_group FOREIGN KEY(group_id) REFERENCES groups(id));
I'm looking for a PL/pgSQL function which finds all groups (their names) particular user belongs to.
Example:
group name: People,
group parent: null
group name: Students,
group parent: People
group name: Football_players,
group parent: People
group name: Basketball_players,
group parent: People
user name: Maciej,
groups : Students, Football_players
f("Maciej") = {"Students", "People", "Football_players"}
He belongs to "People" just because he belongs to "Students" or "Football_players". He is not a direct member of "People" group.
Thanks in advance!
WITH RECURSIVE group_ancestry AS (
SELECT group_id, username
FROM group_members
UNION
SELECT groups_inheritance.parent_group_id, username
FROM group_ancestry
JOIN groups_inheritance ON groups_inheritance.group_id = group_ancestry.group_id
)
SELECT username, group_id
FROM group_ancestry
If you have just one level of inheritance (as in example), then you could use such query:
WITH group_ids AS
(
SELECT group_id
FROM group_members
WHERE username LIKE 'Maciej'
)
SELECT group_name
FROM
(SELECT group_id FROM group_ids
UNION
SELECT DISTINCT parent_group_id
FROM groups_inheritance INNER JOIN group_ids USING(group_id)) g
INNER JOIN groups ON id = group_id;
Result:
group_name
------------------
People
Students
Football_players
(3 rows)
PL/pgSQL function:
DROP FUNCTION IF EXISTS f(varchar(64));
CREATE FUNCTION f(username varchar(64))
RETURNS text[] AS $$
DECLARE
gId bigint;
pgId bigint;
gName text;
result text[] = '{}';
BEGIN
FOR gId IN SELECT group_id FROM group_members WHERE username LIKE username
LOOP
SELECT INTO gName group_name FROM groupS WHERE id = gId;
result := result || gName;
FOR pgId IN SELECT parent_group_id FROM groups_inheritance WHERE group_id = gId
LOOP
SELECT INTO gName group_name FROM groups WHERE id = pgId;
IF NOT (result #> ARRAY[gName]) THEN
result := result || gName;
END IF;
END LOOP;
END LOOP;
RETURN result;
END $$
LANGUAGE 'plpgsql';
Result:
SELECT f('Maciej');
f
------------------------------------
{Students,People,Football_players}
(1 row)
However for nested parent groups I think that recursion should be suitable.
EDIT:
Here is recursion-based variant for nested parent groups:
CREATE OR REPLACE FUNCTION f_recursive(gIdParam bigint, resultArrayParam bigint[])
RETURNS bigint[] AS $$
DECLARE
pgId bigint;
resultArray bigint[];
BEGIN
FOR pgId IN SELECT parent_group_id FROM groups_inheritance WHERE group_id = gIdParam
LOOP
IF NOT (resultArrayParam #> ARRAY[pgId]) THEN
resultArray := resultArray || pgId;
resultArray := resultArray || f_recursive(pgId, resultArray);
END IF;
END LOOP;
RETURN resultArray;
END $$
LANGUAGE 'plpgsql';
CREATE OR REPLACE FUNCTION f(usernameParam varchar(64))
RETURNS text[] AS $$
DECLARE
gId bigint;
resultArray bigint[];
BEGIN
FOR gId IN SELECT group_id FROM group_members WHERE username LIKE usernameParam
LOOP
resultArray := resultArray || gId;
resultArray := resultArray || f_recursive(gId, resultArray);
END LOOP;
RETURN array_agg(group_name)
FROM groups INNER JOIN (SELECT unnest(resultArray)) u ON unnest = id;
END $$
LANGUAGE 'plpgsql';
Example insert:
INSERT INTO groups (id, group_name) VALUES
(1, 'People'), (2, 'Workers'), (3, 'Programmers'),
(4, 'AI-Programmers'), (5, 'Administators'), (6, 'Managers');
INSERT INTO groups_inheritance (group_id, parent_group_id) VALUES
(2, 1), (3, 2), (4, 3), (5, 2), (6, 2);
INSERT INTO users (username, password, enabled) VALUES
('Maciej', '12345', true);
INSERT INTO group_members (id, username, group_id) VALUES
(1, 'Maciej', 4), (2, 'Maciej', 5);
Result:
SELECT f('Maciej');
f
-----------------------------------------------------------
{AI-Programmers,Programmers,Workers,People,Administators}
(1 row)
Another way is to use WITH query along with RECURSIVE modifier as #araqnid shown.