pewpewlasers
pewpewlasers

Reputation: 3215

Postgres merge two rows with common array elements

I have a postgres table with a column names "ids".

+----+--------------+
| id |     ids      |
+----+--------------+
|  1 | {1, 2, 3}    |
|  2 | {2, 7, 10}   |
|  3 | {14, 11, 1}  |
|  4 | {12, 13}     |
|  5 | {15, 16, 12} |
+----+--------------+

I want to merge rows with at least one common array element and create a new row from that (or merge into one existing row). So finally the table would look like the following:

+----+--------------------------+
| id |           ids            |
+----+--------------------------+
|  6 | {1, 2, 3, 7, 10, 14, 11} |
|  7 | {12, 13, 15, 16}         |
+----+--------------------------+

Order of array elements in the resulting table does not really matter but they must be unique.


The rows are added independently from another system. For example we could add a new row where ids are {16, 18, 1}.

Right now to make sure we combine all the rows with at least one common array element, I am doing the calculations in my server (Node.js).

So before I create a new row, I pull all the existing rows in the database that have at least one item in common using:

await t.any('SELECT * FROM arraytable WHERE $1 && ids', [16, 18, 1])

This gives me all the rows that have at least 16, 18 or 1. Then I merge the rows with [16, 18, 1] and remove duplicates.

With the availability of this new array, I delete all existing rows fetched above and insert this new row to the database. As you can see, most of the work is being done in Node.js.

Instead of this I am trying to create a trigger, which will do all these steps for me as soon as I add the new row. How do I go about doing this with a trigger. Also, are there better ways?

Upvotes: 1

Views: 811

Answers (2)

Robert Żyłan
Robert Żyłan

Reputation: 96

Can procedure suffice?

CREATE OR REPLACE PROCEDURE add_ids(new_ids INT[])
AS $$
    DECLARE sum_array INT[];
    BEGIN
        SELECT ARRAY (SELECT UNNEST(ids) FROM table1 WHERE table1.ids && new_ids) INTO sum_array;
        sum_array := sum_array || new_ids;
        SELECT ARRAY(SELECT DISTINCT UNNEST(sum_array)) INTO sum_array;
        DELETE FROM table1 WHERE table1.ids && sum_array;
        INSERT INTO table1(ids) SELECT sum_array;
    END;
$$
LANGUAGE plpgsql;

Unfortunately inserting row inside trigger calls another trigger causing infinitie loop. I do not know work around that.

PS. Sorry if creating another answer is bad practice. I want to leave it for now for reference. I will delete it when the problem is resolved.

Edit by pewpewlasers:

To prevent the loop another table is probably needed. I have created a new temporary table2. New arrays can be added to this table. This table will have a trigger which does the calculations and saves it to table1. It also deletes this temporarily created row.

CREATE OR REPLACE FUNCTION  on_insert_temp() RETURNS TRIGGER AS $f$
DECLARE sum_array BIGINT[];
BEGIN
    SELECT ARRAY (SELECT UNNEST(ids) FROM table1 WHERE table1.ids && NEW.ids) INTO sum_array;
    sum_array := sum_array || NEW.ids;
    SELECT ARRAY(SELECT DISTINCT UNNEST(sum_array)) INTO sum_array;
    DELETE FROM table1 WHERE table1.ids && sum_array;
    INSERT INTO table1(ids) SELECT sum_array;
    DELETE FROM table2 WHERE id = NEW.id;
    RETURN OLD;
END
$f$ LANGUAGE plpgsql;

CREATE TRIGGER on_insert_temp AFTER INSERT ON table2 FOR EACH ROW EXECUTE PROCEDURE  on_insert_temp();

Upvotes: 2

Robert Żyłan
Robert Żyłan

Reputation: 96

Given tables

CREATE TABLE table1(id serial, ids INT [] )
CREATE TABLE table2(id serial, ids INT [] )

the trigger can looks like that


CREATE OR REPLACE FUNCTION sum_tables_trigger() RETURNS TRIGGER AS $table1$
   BEGIN
      INSERT INTO table2(ids) SELECT ARRAY(SELECT DISTINCT UNNEST(table1.ids || new.ids) ORDER BY 1) FROM table1 WHERE table1.ids && new.ids;
      RETURN NEW;
   END;
$table1$ LANGUAGE plpgsql;

CREATE TRIGGER sum_tables_trigger_ BEFORE INSERT ON table1
FOR EACH ROW EXECUTE PROCEDURE sum_tables_trigger();

tableA.ids && tableB.ids returns true, if tables have common element.

tableA.ids || tableB.ids adds elements.

ARRAY(SELECT DISTINCT UNNEST(table1.ids || new.ids) ORDER BY 1) removes duplicates.

Upvotes: 1

Related Questions