How to select from one table non existing data in second table? - sql

I have 2 tables :
tab1
tab2
Here's the rows included in those table
tab1
item| num
a | 2
a | 3
b | 1
b | 3
tab2
num
1
2
3
4
5
There is a reference between the num in tab1 and num in tab2.
How to select nums from tab2 that dont exist in tab1 with the item column?
Here is the desired result:
item | num
a | 1
a | 4
a | 5
b | 2
b | 4
b | 5

One approach would be anti-join, using a "calendar" table containing all possible num/item values:
SELECT DISTINCT
t1a.item,
t2.num
FROM tab2 t2
CROSS JOIN tab1 t1a
LEFT JOIN tab1 t1b
ON t1b.item = t1a.item AND
t1b.num = t2.num
WHERE
t1b.item IS NULL
ORDER BY
t1a.item,
t2.num;
Demo

Related

How to populate a table based on a value from a different table

I have two tables of data which I can join using a left join linked on the ID in both tables. Where the course and the person are the same, I need to populate the RegNumber as the same as the RegNumber which is already there for 1 row:
How it is currently: if I join table 1 and table 2 with a left join.
Table 1
ID | Course| Person
67705 | A | 1
68521 | A | 1
85742 | A | 1
89625 | A | 1
67857 | B | 2
86694 | B | 2
88075 | B | 2
88710 | C | 3
47924 | C | 3
66981 | C | 3
12311 | B | 1
12312 | B | 1
12313 | B | 1
Table 2
ID | RegNumber
67705 | N712316
NULL | NULL
NULL | NULL
NULL | NULL
67857 | N712338
NULL | NULL
NULL | NULL
NULL | NULL
47924 | M481035
NULL | NULL
12311 | N645525
NULL | NULL
NULL | NULL
I need table 2 to look like this:
ID | RegNumber
67705 | N712316
68521 | N712316
85742 | N712316
89625 | N712316
67857 | N712338
86694 | N712338
88075 | N712338
88710 | N712338
47924 | M481035
66981 | M481035
12311 | N645525
12312 | N645525
12313 | N645525
That is, I need to insert new rows into Table 2
Can anyone help me please? This is Totally beyond my capability!
insert into table2 (ID,RegNumber)
select t1.ID,reg.regNumber
from table1 t1
cross join (select top 1 regNumber from table2 r2 join table1 r1
on r1.Id = r2.Id
and r1.Course = t1.Course
and r1.Person = t1.person
order by id) reg
where not exists (select 1 from table2 t2 where t1.ID = t2.ID)
you can improve performance a little bit by loading data into temp table first :
select t1.ID , Course,Person,regNumber
into #LoadedData
from table1 t1
join table2 t2 on t1.Id = t2.ID
insert into table2 (ID,RegNumber)
select t1.ID,reg.regNumber
from table1 t1
cross join (select top 1 regNumber from #LoadedData l
where l.Course = t1.Course
and l.Person = t1.person
order by id) reg
where not exists (select 1 from #LoadedData l where t1.ID = l.ID)
in either case having an index on (ID, Course, Person) will help with performance
Assuming:
You are missing items in table 2 that inherit data from other records in table 1.
What makes two different IDs share the same Regnumber is to have BOTH course and person number in common.
You really need to join table 1 to itself to create the mapping that associates ID 67705 with ID 68521, then you can join in table 2 to pick up the Regnumber.
Try this:
Insert into table2 (ID,RegNumber)
Select right1.ID, left2.RegNumber
From (
(table2 left2 INNER JOIN
table1 left1 On (left1.ID=left2.ID)
INNER JOIN table1 right1 On (left1.Course=right1.Course AND left1.Person=right1.Person)
) LEFT OUTER JOIN table2 right2 On (right1.ID=right2.ID)
WHERE right2.ID Is Null
The 4th table join (alias right2) is purely defensive, to handle two records in table2 having identical Person & Course in table1.
I have solved this myself.
I concatenated the person and course columns and then joined them using that new concatenated field
insert into table 2 (ID,RegNumber)
select X1.ID,X2.Regnumber
from (select concat(course,person) as X,ID from table1) X1
join (select concat(t1.course,t1.person) as X, t2.RegNumber
from table1 t1
join table2 t2 on t1.ID = t2.ID) X2
on X1.X = X2.X
where X1.ID not in (select ID from table2)

How to do an outer join with full result between two tables

I have two tables:
TABLE1
id_attr
-------
1
2
3
TABLE2
id | id_attr | val
----------------------
10 | 1 | A
10 | 2 | B
As a result I want a table that show:
RESULT
id | id_attr | val
----------------------
10 | 1 | A
10 | 2 | B
10 | 3 | NULL
So I want the row with id=10 and id_attr=3 also when id_Attr=3 is missing in TABLE2 (and I know that because I have a NULL value (or something else) in the val column of RESULT.
NB: I could have others ids in table2. For example, after insert this row on table2: {11,1,A}, as RESULT I want:
id | id_attr | val
----------------------
10 | 1 | A
10 | 2 | B
10 | 3 | NULL
11 | 1 | A
11 | 2 | NULL
11 | 3 | NULL
So, for every id, I want always the match with all id_attr.
Your specific example only has one id, so you can use the following:
select t2.id, t2.id_attr, t2.val
from table2 t2
union all
select 10, t1.id_attr, NULL
from table1 t1
where not exists (select 1 from table2 t2 where t2.id_attr = t1.id_attr);
EDIT:
You can get all combinations of attributes and ids in the following way. Use a cross join to create all the rows you want and then a left join to bring in the data you want:
select i.id, t1.id_attr, t2.val
from (select distinct id from table2) i cross join
table1 t1 left join
table2 t2
on t2.id = i.id and t2.id_attr = t1.id_attr;
It sounds like you want to do just an outer join on id_attr instead of id.
select * from table2 t2
left outer join table1 t1 on t2.id_attr = t1.id_attr;

select all rows by distinct values with limit on distinct values

Let's say we have 2 tables:
Table1: Table2:
id | t2id id | col
---------- ----------
1 | 1 1 | a
2 | 2 2 | b
3 | 2 3 | c
4 | 1 4 | d
5 | 3 5 | e
6 | 3 6 | f
7 | 4 7 | g
8 | 5 8 | h
9 | 1 9 | i
10 | 4 10 | j
My question is:
Is there any short way to put limit for distinct results of Table1.t2id column?
For example: if limit = 2 then all rows with t2id from 1 to 2 (or any other values) are selected.
Expected result (with limit = 2):
Res:
id | t2id
----------
1 | 1
2 | 2
3 | 2
4 | 1
9 | 1
Note:
Any information or suggestion are accepted
You could use just the where clause
Select id,t2id
from table1
where t2id<=2
Or you can use where .. between
Select id,t2id
from table1
where t2id between 1 and 2
I believe you want to:
Create a subquery with all the columns you need + this one: DENSE_RANK() OVER (ORDER BY Table1.t2id) AS MyRank
outside of the sub-query, add a where on MyRank
Complete solution:
SELECT id, tb2id
FROM (
SELECT id, tb2id, DENSE_RANK() OVER (ORDER BY Table1.t2id) AS MyRank
FROM table1
) MySubQuery
WHERE MyRank <= 2
This will adapt to JOINs with table2 (with potential multiplicity increase) and non-consecutive values in tb2id.
You can also use in:
select t1.*
from table1 t1
where t1.t2_id in (select t2.id from table2 t2 limit 2);
The advantage of this approach is that it is easy to make it random:
select t1.*
from table1 t1
where t1.t2_id in (select t2.id from table2 t2 order by random() limit 2);

Alter Table based on duplicate values in postgresql

I would like to change the type to be the same for the same item. For example my table looks like this:
item | type
A | 1
B | 2
A | 3
C | 4
B | 5
A | 6
Desired output:
item | type
A | 1
B | 2
A | 1
C | 3
B | 2
A | 1
You can set the value to the smallest:
update t
set t.type = t2.min_type
from (select t2.item, min(type) as min_type
from t t2
group by t2.item
) t2;
If you have a column that specifies the ordering, you can phrase this as:
update t
set t.type = t2.type
from (select distinct on (t2.item) t2.item, t2.type
from t t2
order by t2.item, t2.?
) t2;
SQL tables represent unordered sets. There is no "first" row unless a column specifically contains this information.

How to add a column to sql result from other row in same table

For example I have a table:
num | value | info
1 | a | test2
2 | a | test1
1 | b | specialinfo
3 | a | test3
And I have a query
select *
from t
where value='a'
It will result
1 a test2
2 a test1
3 a test3
But I want to add info for value b as not a new row but as an additional column, so it will be like:
1 a test2 specialinfo
2 a test1 null
3 a test3 null
so value specialinfo goes as additional column and not as additional row.
For this case select * from t where value IN ('a','b') will not work
is it possible to do that?
In order to get the details in a separate column one way to do this would be to join on the table multiple times to get the result:
select t1.num, t1.value, t1.info, t2.info as t2_info
from yt t1
left join yt t2
on t1.num = t2.num
and t1.value <> t2.value
where t1.value = 'a';
See SQL Fiddle with Demo