find set of row, duplicate list, before insert - sql

I have table (it's a list of struct with 4 integers, first id is list id)
id | idL | idA(null) | idB(null) | idC
1 | 1 | 2 | null | 1
2 | 1 | 4 | null | 1
3 | 1 | null | 1 | 1
4 | 2 | 2 | null | 1
5 | 2 | 4 | null | 1
6 | 3 | 6 | null | 1
7 | 3 | null | 4 | 1
Now I need to insert 4th list to this table
idA | idB | idC
2 | null | 1
4 | null | 1
null | 1 | 1
but, it's already exist (list id = 1)
idA | idB | idC
2 | null | 1
4 | null | 1
alse exist (idL = 2)
idA | idB | idC
2 | null | 1
4 | null | 1
null | 7 | 1
does not exist.
How to find duplicate before insert it to table

It appears to be just a matter of insert from (select not in).
Try this example:
SQLFiddle
Disclaimer: In the example data you provided rows 2 and 4 got a identical idA,idB,idC set.
If that columns cannot form a unique and you already got that tuple in copy table and you need one row in copy table for each row in original table that ill be a lot harder because for a such row in copy there's no way to tell the row in original it's related.

if values is in table temp and you know the list id.
you can use "Except"
eg:
insert into list (idL, idA, idB, idC)
select #list_id, t.idA, t.idB, t.idC
from
(
select idA, idB, idC
from #new_values
except
select idA, idB, idC
from list
) t

Related

how to "deepcopy" rows

My question is similar to this one but more involved. Suppose I have a table A with id idA, and another table B with idB and foreign key idA. I would like to duplicate all entries of A, including corresponding entries in B. For example, if I have the following tables at the start:
A
|---|
|idA|
|---|
| 1 |
| 2 |
| 3 |
|---|
B
|---|---|
|idB|idA|
|---|---|
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
|---|---|
Then the result should be:
A
|---|
|idA|
|---|
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
|---|
B
|---|---|
|idB|idA|
|---|---|
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
| 4 | 4 |
| 5 | 4 |
| 6 | 5 |
|---|---|
This is quite tricky. You need to insert the ids into the a -- but then be able to match them back to the existing ids to insert the right values into b.
A generic solution looks like this:
with i as (
insert into a
select . . . -- the other columns you want
from a
order by idA
returning *
),
a_mapping (
select a.idA, i.idA as new_idA
from (select a.*, row_number() over (order by idA) as seqnum
from a
) a join
(select i.*, row_number() over (order by idA) as seqnum
from i
) i
on a.seqnum = i.seqnum
)
insert into b (idA) (
select am.new_idA
from b join
a_mapping am
on b.idA = am.idA;
Note: If you have another unique column or columns in the row, then the mapping is a little easier to generate. Of course, if you are copying all the columns, then nothing else is unique, so you do need the row_number().
Of course, for your very simple example, you don't need a mapping table. You can just use:
with i as (
insert into a
select . . . -- the other columns you want
from a
order by idA
returning *
)
insert into b (idA) (
select i.idA
from i
I have an approach which may be equivalent to what Gordon Linoff suggests, I would be grateful if you could point out any flaws!
Let's set up the tables:
CREATE TABLE A(
idA SERIAL PRIMARY KEY,
txt varchar);
INSERT INTO A(txt)
VALUES ('A1'), ('A2'),('A3');
CREATE TABLE B(
idB SERIAL PRIMARY KEY,
idA int REFERENCES A(idA),
txt varchar);
INSERT INTO B(idA, txt)
VALUES (1, 'A1.B1'), (1, 'A1.B2'), (2, 'A2.B1');
so the initial data looks as follows:
SELECT * FROM (A LEFT JOIN B ON A.idA=B.idA) ORDER BY A.idA, B.idB;
ida | txt | idb | ida | txt
-----+-----+-----+-----+-------
1 | A1 | 1 | 1 | A1.B1
1 | A1 | 2 | 1 | A1.B2
2 | A2 | 3 | 2 | A2.B1
3 | A3 | | |
(4 rows)
Now, we can use the NEXTVAL function to generate the mappings directly:
CREATE TEMP TABLE tmp_A_new AS (
SELECT *, NEXTVAL('A_idA_seq') as newidA
FROM A ORDER BY idA -- order probably not needed
);
INSERT INTO A(idA, txt) (SELECT newidA, txt FROM tmp_A_new);
CREATE TEMP TABLE tmp_B_new AS (
SELECT B.idB, newidA, B.txt, NEXTVAL('B_idB_seq') as newidB
FROM B, tmp_A_new WHERE B.idA=tmp_A_new.idA ORDER BY idB
);
INSERT INTO B(idB, idA, txt) (SELECT newidB, newidA, txt FROM tmp_B_new);
The results look correct:
SELECT * FROM (A LEFT JOIN B ON A.idA=B.idA) ORDER BY A.idA, B.idB;
ida | txt | idb | ida | txt
-----+-----+-----+-----+-------
1 | A1 | 1 | 1 | A1.B1
1 | A1 | 2 | 1 | A1.B2
2 | A2 | 3 | 2 | A2.B1
3 | A3 | | |
4 | A1 | 4 | 4 | A1.B1
4 | A1 | 5 | 4 | A1.B2
5 | A2 | 6 | 5 | A2.B1
6 | A3 | | |
(8 rows)
Note that this could be continued further down to C, D, etc.
I would be glad for any comments :)

select empty object in jsonb_each in postgres

How to select a empty object Record with jsonb_each function.
because I select some extra field with jsonb_each key and value.
but when a all record jsonb column in empty result is empty.
create table newtest (id SERIAL PRIMARY KEY,foo jsonb);
insert into newtest (foo) values ('{"a":1, "c":2}'), ('{"b":1}'), ('{}');
select * from newtest
ID | foo
-----+----------------
1 | "{"a": 1, "c": 2}"
2 | "{"b": 1}"
3 | "{}"
select id,(jsonb_each(foo)).key AS KEY, (jsonb_each(foo)).value AS value from newtest
Result
ID | key | value
-----+----------------
1 | a | 1
1 | c | 2
2 | b | 1
I need a result like
ID | key | value
-----+----------------
1 | a | 1
1 | c | 2
2 | b | 1
3 |null | null
A lateral left outer join should be the right thing:
SELECT newtest.id, item.key, item.value
FROM newtest
LEFT JOIN LATERAL jsonb_each(newtest.foo) item ON TRUE;
id | key | value
----+-----+-------
1 | a | 1
1 | c | 2
2 | b | 1
3 | |
(4 rows)
This will supply a NULL for missing entries on the right side.

Select from cross-reference based on inclusion (column values being superset)

Given a cross-reference table t relating table a with b:
| id | a_id | b_id |
--------------------
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 7 |
| 5 | 2 | 3 |
| 6 | 3 | 2 |
| 7 | 3 | 3 |
What would be the conventional way of selecting all a_id whose b_id is a superset of a given set?
For example, for the set (2,3), I would expect the result:
| a_id |
--------
| 1 |
| 3 |
Since a_id 1 and 3 are the only set of b_id that is a superset of (2,3).
The best solution I've found so far (thanks to this answer):
select id
from a
where 2 = (select count(*)
from t
where t.a_id = a.id and t.b_id in (2,3)
);
But I'd prefer to avoid calculating stuff like cardinality before running the query.
You can simply adapt the query as:
select id
from a cross join
(select count(*) as cnt
from t
where . . .
) x
where x.cnt = (select count(*)
from t
where t.a_id = a.id and t.b_id in (2,3)
);

Select from cross-reference based on inclusion (column values being subset)

Suppose I have a cross-reference table t with the following data:
| id | a_id | b_id |
--------------------
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 7 |
| 5 | 2 | 3 |
| 6 | 3 | 2 |
| 7 | 3 | 3 |
What would be the conventional way of selecting all a_id whose b_id is a subset of a given set?
For example, for some set (1,2,3,4,5), I would expect the result:
| a_id |
--------
| 1 |
| 3 |
Since a_id 1 and 3 are the only set of b_id that is a subset of (1,2,3,4,5).
Hmmm . . . One way uses aggregation:
select a_id
from t
group by a_id
having sum(case when b_id not in (1, 2, 3, 4, 5) then 1 else 0 end) = 0;
However, assuming you have an a table, then I prefer this method:
select a_id
from a
where not exists (select 1
from t
where t.a_id = a.a_id and t.b_id not in (1, 2, 3, 4, 5)
);
This saves the expense of aggregation and the lookup can take advantage of an appropriate index (on t(a_id, b_id)) so this should have better performance.

Pl/PgSQL, insert into two tables, one with header rows

What would be the best way of going about doing the following insert. I've looked around, and I'm kind of stuck.
The table I Currently have (insert)
| id | order_id | item_id | type | group |
| 1 | 1 | 1 | 2 | 1 | <- type 2 represents a "header" item, or a "kit"
| 2 | 1 | 2 | 1 | 1 | <- type 1 represents a member of the "kit"
| 3 | 1 | 3 | 1 | 1 |
| 4 | 1 | 4 | 2 | 2 | <- New group means new kit
| 5 | 1 | 2 | 1 | 2 |
| 6 | 1 | 5 | 1 | 2 |
I need to insert these items into the following two tables:
1) item_entry
| id | mode | tmplt_id | item_id | parent_item_entry_id |
| 1 | 1 | 1 | NULL | NULL | <- This is a header line, mode 1
| 2 | 2 | NULL | 2 | 1 | <- This is a sub line, mode 2
| 3 | 2 | NULL | 3 | 1 | <- parent_item_entry_id references the header it belongs to
| 4 | 1 | 4 | NULL | NULL |
| 5 | 2 | NULL | 2 | 4 |
| 6 | 2 | NULL | 5 | 4 |
2) item_entry_details
| id | item_entry_id | order_id | group |
| 1 | 1 | 1 | 1 |
| 2 | 4 | 1 | 2 | <- only header information is necessary
Of course, item_entry.id is driven off of a sequence (item_entry_id_seq). Is there an elegent way of getting this to work? I currently loop through each group, first assigning a nextval() to a variable, and then I loop through each item in the group, writing to the table.
FOR recGroup IN SELECT DISTINCT group FROM insert LOOP
intParentItemEntryID := nextval('item_entry_id_seq');
FOR recLine IN SELECT * FROM insert LOOP
INSERT INTO item_entry VALUES (CASE intParentItemEntryID/DEFAULT, CASE 1/2, CASE recLine.item_id/NULL, CASE NULL/recLine.item_id, CASE NULL/intParentItemEntryID)
INSERT INTO item_entry_details VALUES (DEFAULT, intParentItemEntryID, recLine.order_id, recLine.group);
END LOOP;
END LOOP;
Is there a better way, or is the above the only way this type of insert can be done?
No need for procedural code "row-at-a-time" here, just plain old sql will suffice.
-- create the tables that the OP did not provide
DROP TABLE the_input;
CREATE TABLE the_input
( id INTEGER NOT NULL PRIMARY KEY
, order_id INTEGER NOT NULL
, item_id INTEGER NOT NULL
, ztype INTEGER NOT NULL
, zgroup INTEGER NOT NULL
);
DROP TABLE target1 ;
CREATE TABLE target1
( id INTEGER NOT NULL
, zmode INTEGER NOT NULL
, tmplt_id INTEGER
, item_id INTEGER
, parent_item_entry_id INTEGER
);
DROP TABLE target2 ;
CREATE TABLE target2
( id SERIAL NOT NULL
, item_entry_id INTEGER NOT NULL
, order_id INTEGER NOT NULL
, zgroup INTEGER NOT NULL
);
-- fil it up ...
INSERT INTO the_input
( id, order_id, item_id, ztype, zgroup ) VALUES
( 1 , 1 , 1 , 2 , 1 ) -- <- type 2 represents a "header" item, or a "kit"
,( 2 , 1 , 2 , 1 , 1 ) -- <- type 1 represents a member of the "kit"
,( 3 , 1 , 3 , 1 , 1 ) --
,( 4 , 1 , 4 , 2 , 2 ) -- <- New group means new kit
,( 5 , 1 , 2 , 1 , 2 ) --
,( 6 , 1 , 5 , 1 , 2 ) --
;
-- Do the inserts.
INSERT INTO target1(id,zmode,tmplt_id,item_id,parent_item_entry_id)
SELECT i1.id, 1, i1.id, NULL, NULL
FROM the_input i1
WHERE i1.ztype=2
UNION ALL
SELECT i2.id, 2, NULL, i2.id, ip.item_id
FROM the_input i2
JOIN the_input ip ON ip.zgroup = i2.zgroup AND ip.ztype=2
WHERE i2.ztype=1
;
INSERT INTO target2(item_entry_id,order_id,zgroup)
SELECT DISTINCT MIN(item_id),order_id, zgroup
FROM the_input i1
WHERE i1.ztype=2
GROUP BY order_id,zgroup
;
SELECT * FROM target1
ORDER BY id;
SELECT * FROM target2
ORDER BY id;
Result:
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "the_input_pkey" for table "the_input"
CREATE TABLE
INSERT 0 6
DROP TABLE
CREATE TABLE
INSERT 0 6
id | zmode | tmplt_id | item_id | parent_item_entry_id
----+-------+----------+---------+----------------------
1 | 1 | 1 | |
2 | 2 | | 2 | 1
3 | 2 | | 3 | 1
4 | 1 | 4 | |
5 | 2 | | 5 | 4
6 | 2 | | 6 | 4
(6 rows)
DROP TABLE
NOTICE: CREATE TABLE will create implicit sequence "target2_id_seq" for serial column "target2.id"
CREATE TABLE
INSERT 0 2
id | item_entry_id | order_id | zgroup
----+---------------+----------+--------
1 | 4 | 1 | 2
2 | 1 | 1 | 1
(2 rows)