0

I have been trying to insert data into a self referencing table while the data has been sourced from other different tables. This row is kind of a root record and needs to have its own reference as a foreign key. For easier understanding, providing the table DDL here -

CREATE TABLE self_refer (
    id        SERIAL  PRIMARY KEY,
    parent_id INTEGER      NOT NULL,
    FOREIGN KEY (parent_id) REFERENCES self_refer(id)
);

If we want to insert data using direct values, it has been a discussed topic here and the below statement works well.

INSERT INTO self_refer
            (parent_id)
VALUES      ((SELECT last_value
              FROM   self_refer_id_seq))

In my case, I need to insert data from other tables and thus the statement would be of INSERT INTO SELECT type. Below are my attempts and they don't work as expected.

INSERT INTO self_refer
            (parent_id)
SELECT CURRVAL('self_refer_id_seq')

and

INSERT INTO self_refer
            (parent_id)
SELECT last_value
FROM   self_refer_id_seq 

The first statement with VALUES inserts data appropriately, but the second and third ones pull the pre-latest data for last_value or currval. First query works well even when used with currval function.

Below are the results where first two records show the insertion via VALUES and the next two are with INSERT INTO SELECT statements.

id|parent_id|
--|---------|
47|       47|
48|       48|
49|       48|
50|       49|

Looking for any help on how to achieve the results like first two rows while keeping the SQL statements similar to the second two.

Update: Accepted answer works, but might not be an ideal solution, read through all the comments on how a better solution was arrived.

Added the statements below for quicker access. Feel free to check out db fiddle if preferred to tweak and try other possibilities.

CREATE TABLE self_refer (
    id        SERIAL  PRIMARY KEY,
    parent_id INTEGER      NOT NULL,
    FOREIGN KEY (parent_id) REFERENCES self_refer(id)
);
INSERT INTO self_refer
            (parent_id)
VALUES      ((SELECT last_value
              FROM   self_refer_id_seq))
1 rows affected
INSERT INTO self_refer
            (parent_id)
SELECT CURRVAL('self_refer_id_seq')+1;
1 rows affected
select * from self_refer;
id | parent_id
-: | --------:
 1 |         1
 2 |         2
INSERT INTO self_refer
            (parent_id)
VALUES      ((SELECT last_value
              FROM   self_refer_id_seq))
1 rows affected
select * from self_refer;
id | parent_id
-: | --------:
 1 |         1
 2 |         2
 3 |         3
INSERT INTO self_refer
            (parent_id)
SELECT CURRVAL('self_refer_id_seq')+1;
1 rows affected
select * from self_refer;
id | parent_id
-: | --------:
 1 |         1
 2 |         2
 3 |         3
 4 |         4
INSERT INTO self_refer
            (parent_id)
SELECT last_value +1
FROM   self_refer_id_seq 
1 rows affected
select * from self_refer;
id | parent_id
-: | --------:
 1 |         1
 2 |         2
 3 |         3
 4 |         4
 5 |         5
insert into self_refer values(default, (currval('self_refer_id_seq')));
1 rows affected
select * from self_refer;
id | parent_id
-: | --------:
 1 |         1
 2 |         2
 3 |         3
 4 |         4
 5 |         5
 6 |         6
CREATE FUNCTION my_trigger_function()
RETURNS trigger AS '
BEGIN
  IF NEW.parent_id = -1 THEN
    NEW.parent_id := NEW.id;
  END IF;
  return new;
END ' LANGUAGE 'plpgsql'
create trigger test_t
before insert on self_refer
for each row
EXECUTE PROCEDURE my_trigger_function()
INSERT INTO self_refer
            (parent_id)
SELECT -1
1 rows affected
select * from self_refer;
id | parent_id
-: | --------:
 1 |         1
 2 |         2
 3 |         3
 4 |         4
 5 |         5
 6 |         6
 7 |         7
INSERT INTO self_refer
            (parent_id)
SELECT 5
1 rows affected
select * from self_refer;
id | parent_id
-: | --------:
 1 |         1
 2 |         2
 3 |         3
 4 |         4
 5 |         5
 6 |         6
 7 |         7
 8 |         5

db<>fiddle here

2
  • So, you are entering data in both columns but you are showing us here only one ? How is column id beeing populated ? Commented Nov 5, 2019 at 14:50
  • 1
    id is of SERIAL type, it gets auto populated on every insertion. ref: postgresql.org/docs/9.1/datatype-numeric.html#DATATYPE-SERIAL Commented Nov 5, 2019 at 15:04

2 Answers 2

1

Would something like this be an option:

INSERT INTO self_refer
            (parent_id)
SELECT CURRVAL('self_refer_id_seq')+1;

INSERT INTO self_refer
            (parent_id)
SELECT last_value +1
FROM   self_refer_id_seq 

It does work: DEMO

Sign up to request clarification or add additional context in comments.

4 Comments

I had observed that it does work, but was unsure if that would be a right approach. The reason I was skeptic was the case when multiple threads are involved. I am not very sure about the behavior of currval or last_value when a different transaction invokes nextval while this transaction is still in progress. Do you have deeper insights on this?
Well I would test it if anything(with a loop maybe or something like that...). Sorry, do not have any deeper insights on this. Maybe if you create trigger like in this demo that I made here : dbfiddle.uk/… then you will see anything will work - even this two selects of yours that did not work... Hope this was helpful. Cheers.
Thanks for the effort here, but I find a problem with this. It was not explicitly explained, but table would even hold rows which are non self referencing. This trigger will ensure that all the rows inserted will be self referencing which defeats the requirement of keeping a parent id. And yes, we can tweak the trigger function to check for a predefined value - say -1 rather than checking for not null and make it self referencing if -1 is encountered.
@PavanKumar you are welcome. I understand. Well I tried. I believe that the answer was at least useful if not even correct... The first answer. Cheers!
0

A much more common design would allow the column column parent_id to be null in the case of a top level parent. It's also a superior design; at least IMHO. But

insert into self_refer values(default, (currval('self_refer_id_seq')));

Does the trick.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.