how to "deepcopy" rows - sql

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 :)

Related

Need sql help: For each record in table A (has more columns than table B), insert into Table B

I know my subject is a little sparse, but for the life of me I cannot figure out how to do this. I could accomplish this in C# but I am getting confused by the SQL syntax. I searched and searched and I can't seem to find what I am looking for probably because I don't understand some of the SQL that I am looking at.
TABLE 1
-----------
| CustNo | Catalog1 | Catalog2 | Catalog3 | Catalog4 |
| 1 | A | B | C | NULL |
| 2 | B | C | NULL | D |
| 3 | A | C | E | F |
TABLE 2 (empty)
COLUMNS: CustNo|Catalog
So Basically for each record in Table 1, I want to insert the catalogs into table 2.
So the desired output would look like the following.
TABLE 2
CustNo|Catalog
| 1 | A
| 1 | B
| 1 | C
| 2 | B
| 2 | C
| 2 | D
| 3 | A
| 3 | C
| 3 | E
| 3 | F
Thank you all for any help!
Just unpivot. I like to do this using apply;
insert into table2 (CustNo, Catalog)
select t1.CustNo, v.Catalog
from table1 t1 cross apply
(values (t1.Catalog1), (t1.Catalog2), (t1.Catalog3), (t1.Catalog4)
) v(catalog)
where v.Catalog is not null;

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.

ORACLE Not In on Multiple Columns

Kindly have a look at the following structure.
Table: A
+---------+----------+
| Col1A | Col2A |
+---------+----------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 1 | 2 |
| 2 | 2 |
+---------+----------+
Table: B
+---------+----------+
| Col1B | Col2B |
+---------+----------+
| 2 | 1 |
| 3 | 1 |
+---------+----------+
Here is what I am trying to achieve that is to get the result as following:
Result
+---------+----------+
| Col1A | Col2A |
+---------+----------+
| 1 | 1 |
| 4 | 1 |
| 1 | 2 |
| 2 | 2 |
+---------+----------+
What I want :
I want to get the result of table A records which are not exists in Table B. It should be on the basis of both column. If the combination exist in first table then don't no show it.
What I have tried so far :
The first approach that I have tried was to use Not In statement. Following is my statement.
SELECT A.COL1A, A.COL2A
FROM A
WHERE A.COL1A NOT IN (
SELECT B.COL1B FROM B
);
But the issue with this approach didn't consider the second column. It will give me the following result.
+---------+----------+
| 1 | 1 |
| 4 | 1 |
+---------+----------+
While it will not show following as it should be sub-traced because of we didn't checked for other column.
+---------+----------+
| 1 | 2 |
| 2 | 2 |
+---------+----------+
Then I tried Not Exists but I didn't worked too. Here is my query.
SELECT A.COL1A, A.COL2A
FROM A
WHERE NOT EXISTS(
SELECT B.COL1B,B.COL2B FROM B
);
Edit
Sorry I forget to include the fiddle link.
Here it is : Fiddle Demo
For using Exists I think you should use it like this:
SELECT *
FROM A
WHERE
NOT EXISTS(SELECT 1
FROM B
WHERE A.Col1A = B.Col1B AND A.Col2A = B.Col2B)
One way to do this which is conceptually simple is to JOIN tables A and B on the two columns, and then SELECT only those rows in A which do not exist in B:
SELECT *
FROM A
LEFT OUTER JOIN B
ON (A.COL1A = B.COL1B AND A.COL2A = B.COL2B)
WHERE B.COL1B IS NULL
SELECT *
FROM a
WHERE (col1a, col2a) NOT IN (SELECT col1b, col2b FROM b);
But be aware that this will fail if either col1b or col2b contains NULL values.
SQLFiddle DEMO
SELECT A.COL1A, A.COL2A
FROM A
WHERE (Col1A, Col2A) NOT IN (
SELECT Col1B, Col2B FROM B
);
You can even use:
SELECT A.COL1A, A.COL2A
FROM A
WHERE (Col1A, Col2A) NOT IN (
(2, 1),
(3, 1)
);

find set of row, duplicate list, before insert

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