PostgreSQL 9.3 - How to insert using json_populate_recordset but still get auto ids by sequence - sql

This is a followup question to "Populate multiple tables from a single JSON object with json_populate_recordset".
Say my tables (table_a, table_b) have sequences for a_id and b_id,
so they look like this:
CREATE TABLE table_a (a_id serial, b_id integer, c_id integer);
CREATE TABLE table_b (b_id serial, name text, z_id integer);
...
CREATE TABLE table_z (z_id serial, some_text_entry text);
A regular insert looking like this
INSERT INTO table_a (b_id, c_id) values (1, 2);
INSERT INTO table_b (name, z_id) values ("whatever", 3);
...
INSERT INTO table_z (some_text_entry) values ("Some text");
lets PostgreSQL increment the ids for a_id (table_a), b_id(table_b), etc., so the new dataset can be created and stored.
But how could I insert data using the more complex approach following and still get automatically created ids?
WITH input AS (
SELECT '{
"tablename_a":[{"a_id":1,"b_id":2,"c_id":3},
{"a_id":2,"b_id":51,"c_id":3}],
"tablename_b":[{"b_id":2,"name":"John Doe", "z_id":123},
{"b_id":51,"name":"Mary Ann", "z_id":412}],
"tablename_z":[{"z_id":123, "some_text_entry":"Something"},
{"z_id":123, "some_text_entry":"Something else"}]
}'::json AS j
)
, a AS (
INSERT INTO tablename_a
SELECT t.*
FROM input i
, json_populate_recordset(NULL::tablename_a, i.j->'tablename_a') t
)
, b AS (
INSERT INTO table_b
SELECT t.*
FROM input i
, json_populate_recordset(NULL::tablename_b, i.j->'tablename_b') t
)
-- ... more ...
INSERT INTO tablename_z
SELECT t.*
FROM input i
, json_populate_recordset(NULL::tablename_z, i.j->'tablename_z') t
;
If I just set the id to null
"tablename_a":[{"a_id": null,"b_id":2,"c_id":3},
{"a_id": null,"b_id":51,"c_id":3}]
or leave out the respective ids like this:
"tablename_a":[{"b_id":2,"c_id":3},
{"b_id":51,"c_id":3}]
I get the error 'null value in column "a_id" violates not-null constraint'.
In addition:
Would be really great if there was a way to deal with this, without specifically listing all column names (except the serial-ones).
In MySQL if you take my table_a, I know you could do something like
INSERT INTO table_a VALUES (1,2)
meaning that MySQL would fill the a_id field with an autoincrement id and the rest with the data given by the insert command. Is there no way to accomplish that with json, too?

Usually, when you want to insert into a table that has a column of type serial or bigserial, you omit that column from the list of columns. I tried to change your original syntax as little as possible to show how this works.
-- I made no changes to the CTE, except to cut some lines.
WITH input AS (
SELECT '{
"tablename_a":[{"a_id":1,"b_id":2,"c_id":3}, {"a_id":2,"b_id":51,"c_id":3}]
}'::json AS j
)
-- Ignore a_id. It's set automatically by PostgreSQL.
-- Think about cutting it from input.
INSERT INTO table_a (b_id, c_id)
SELECT b_id, c_id
FROM input i
, json_populate_recordset(NULL::table_a, i.j->'tablename_a') t

Related

Insert into 2 tables with single SQL statement instead of loop

I have to insert data in provonance of several table which itself comes from csv (COPY).
Before I used a LOOP in a function to enter the data. I want to simplify the thing for the sake of maintainability and speed.
I need to insert data into a description table, which serves as both the title and description (and multi language).
Previously my code was as follows (extract from the loop):
insert into description (label, lang_id, poi_id,date_dernier_update, date_enregistrementbdd, date_derniere_lecture) values (label, lang_id, poi_id, now(), now(), now()) RETURNING id INTO _retour_id_titre;
insert into poi_titre_poi (poi_id, titre_poi_id, titre_poi_key) values (poi_id, _retour_id_titre, label_lang);
But now I can't:
with rows as (
insert into description (label, lang_id, poi_id)
select rdfslabelfrs, '1', (select id from poi where uri_id = csv_poi_rdf_fr.poi) as toto from csv_poi_rdf_fr RETURNING id
)
insert into poi_titre_poi (poi_id, titre_poi_id, titre_poi_key)
select description.poi_id, id , 'fr'
FROM description;
In fact, I cannot insert the 'poi_id' in the 'poi_titre_poi' table which corresponds to the one which was inserted in the description table.
I get this error message:
ERROR: more than one row returned by a subquery used as an expression
État SQL : 21000
Can I make this work, or do I need to loop?
Filling in missing bits with assumptions, it could work like this:
WITH description_insert AS (
INSERT INTO description
(label , lang_id, poi_id)
SELECT c.rdfslabelfrs, 1 , p.id
FROM csv_poi_rdf_fr c
JOIN poi p ON p.uri_id = c.poi
RETURNING poi_id, id
)
INSERT INTO poi_titre_poi (poi_id, titre_poi_id, titre_poi_key)
SELECT d.poi_id, d.id , 'fr'
FROM description_insert d;
Related:
PostgreSQL multi INSERT...RETURNING with multiple columns
Insert data in 3 tables at a time using Postgres
Get Id from a conditional INSERT

DB2 Using with statement to keep last id

In my project I need to create a script that insert data with auto generate value for the primary key and then to reuse this number for foreign on other tables.
I'm trying to use the WITH statement in order to keep that value.
For instance, I'm trying to do this:
WITH tmp as (SELECT ID FROM (INSERT INTO A ... VALUES ...))
INSERT INTO B ... VALUES tmp.ID ...
But I can't make it work.
Is it at least possible to do it or am I completely wrong???
Thank you
Yes, it is possible, if your DB2-server version supports the syntax.
For example:
create table xemp(id bigint generated always as identity, other_stuff varchar(20));
create table othertab(xemp_id bigint);
SELECT id FROM FINAL TABLE
(INSERT INTO xemp(other_stuff)
values ('a'), ('b'), ('c'), ('d')
) ;
The above snippet of code gives the result below:
ID
--------------------
1
2
3
4
4 record(s) selected.
If you want to re-use the ID to populate another table:
with tmp1(id) as ( SELECT id FROM new TABLE (INSERT INTO xemp(other_stuff) values ('a1'), ('b1'), ('c1'), ('d1') ) tmp3 )
, tmp2 as (select * from new table (insert into othertab(xemp_id) select id from tmp1 ) tmp4 )
select * from othertab;
As per my understanding
You will have to create an auto-increment field with the sequence object (this object generates a number sequence).
You can CREATE SEQUENCE to achieve the auto increment value :
CREATE SEQUENCE seq_person
MINVALUE 1
START WITH 1
INCREMENT BY 1
CACHE 10

Insert a new record with a field containing total record in the table

I wonder if it exists a function that allows me to get total rec count of a table to for new record insertion into the table?
I have this table:
CREATE TABLE "TEST"
( "NAME" VARCHAR2(20 BYTE),
"ID" NUMBER,
"FLAG" NUMBER
) ;
Insert into TEST (NAME,ID,FLAG) values ('Ahlahslfh',1,1);
Insert into TEST (NAME,ID, FLAG) values ('Buoiuop',2,1);
Insert into TEST (NAME,ID, FLAG) values ('UOIP',12,0);
My intention is to issue a statement that is equivalent to this:
INSERT INTO TEST( NAME, ID, FLAG )
VALUES( 'TST', 3,1 );
The statement I used below generated error:
INSERT INTO TEST ( NAME, ID, FLAG )
VALUES ( 'TST', SELECT COUNT(*)+1 FROM TEST WHERE FLAG=1,1 );
Below is the final result I am expecting:
Is there a way around it? Of course, I can put them in a script, count the records into a variable and insert that variable into the field. I just wonder if there is more elegant solution and do this in 1 statement.
Thanks!
This is likely to be a very bad way to set an id. In general, I think you should use sequences/identity/auto_increment and not worry about gaps.
But, you can do what you want using parentheses -- these are needed for subqueries:
INSERT INTO TEST(NAME, ID, FLAG)
VALUES ('TST',
(SELECT COUNT(*)+1 FROM TEST WHERE FLAG = 1),
1
);
Or, alternatively:
INSERT INTO TEST(NAME, ID, FLAG)
SELECT 'TST', COUNT(*) + 1, 1
FROM TEST
WHERE FLAG = 1;
I must emphasize that this seems dangerous. It is quite possible that you will get duplicate ids. You should really let the database insert a new value and not worry about gaps.

Insert some variable columns with constants

What I'm looking to do with my code is insert some variable number of rows into test_table where 'policy' in source_table matches 'bond_ser' in policy_custsgt and 'SNL_ID' in source_table matches 'inst_key' in raw_table.
I want it to insert serial_number and ID along with some other constants. I can get it to insert serial_number and ID just fine, but how do I get it to add some constants in other fields of the table every time it inserts the variables from the other tables?
This is the code I currently have, if I remove the "'122812', '999999', 'myname'" from the first line it works fine but will only populate the serial_number and ID columns of my table with each insert.
INSERT INTO test_table(serial_number, ID, '122812', '999999', 'myname')
SELECT policy, SNL_ID
FROM source_table
WHERE (policy IN (SELECT bond_ser from policy_custsgt)) AND
(SNL_ID in (select inst_key from raw_table))
Thanks!
Of course, I over looked the simple solution. All I needed to do was move those constants I wanted down from the INSERT statement to the SELECT statment, and add the column names in the INSERT so:
INSERT INTO test_table(serial_number, ID, starting_date, ending_date, user_id)
SELECT policy, SNL_ID, '122812', '999999', 'myname'
FROM source_table
WHERE (policy IN (SELECT bond_ser from policy_custsgt)) AND
(SNL_ID in (select inst_key from raw_table))

insert one column data from a table into other table, but the other column data will be specified dynamically

i have 2 tables.
TABLE A COLUMNS - aid , aname
TABLE B COLUMNS - bid , bname
from table A, i will pick up data from column 'aid',
AND insert it in 'bid' column of table B , but in bname column of table B, i will insert a new value. how do i do this?
create table A(aid int,aname char)
insert into A values(111, 'e')
create table B(bid int, bname char)
insert into B (bid,bname)
bid will take value from the query : select aid from a
bname will get a new value -m
expected result should be : THE TABLE B WILL HAVE :
bid bname
--- -----
111 m
Try this:
insert into b (bid, bname) select aid, 'm' as bname_fixed_val from a
Two facts enabled the solution above:
The insert .. select clause allows you to insert the values returned with any select.
You can return constant values as fields with select, like for instance:
SELECT 0 as id, 'John' as name
Combining these two points together, I used an insert..select clause to select the field value from the first table (aid), along with a constant value for the second field (m). The AS bname_fixed_val clause is simply a field alias, and can be omitted.
For more information on SQL, here 's a link: http://www8.silversand.net/techdoc/teachsql/index.htm, although googling it wouldn't hurt also.