How to join and union at the same time - sql

I'm sure this has a simple solution but I'm struggling to find it.
I have two tables
CREATE TABLE t1 (
"name" VARCHAR(1),
"id" INTEGER,
"data1" VARCHAR(2)
);
INSERT INTO t1
("name", "id", "data1")
VALUES
('a', '1', 'a1'),
('d', '1', 'd1');
CREATE TABLE t2 (
"name" VARCHAR(1),
"id" INTEGER,
"data2" VARCHAR(2)
);
INSERT INTO t2
("name", "id", "data2")
VALUES
('d', '1', 'd2'),
('k', '1', 'k2');
I want this final combined table:
| name | id | data1 | data2 |
| ---- | --- | ----- | ----- |
| a | 1 | a1 | |
| d | 1 | d1 | d2 |
| k | 1 | | k2 |
Things I've tried:
Do a union
select
t1.name,
t1.id,
t1.data1,
NULL as data2
from t1
union
select
t2.name,
t2.id,
NULL as data1,
t2.data2
from t2
| name | id | data1 | data2 |
| ---- | --- | ----- | ----- |
| a | 1 | a1 | |
| d | 1 | d1 | |
| d | 1 | | d2 |
| k | 1 | | k2 |
Do a full join
select * from t1
full join t2 on t2.id = t1.id
and t2.name = t1.name;
| name | id | data1 | name | id | data2 |
| ---- | --- | ----- | ---- | --- | ----- |
| a | 1 | a1 | | | |
| d | 1 | d1 | d | 1 | d2 |
| | | | k | 1 | k2 |
The answer is somewhere in between 😅

You're looking for a FULL OUTER JOIN:
SELECT COALESCE(t1.name, t2.name) AS name,
COALESCE(t1.id, t2.id) AS id,
t1.data1,
t2.data2
FROM t1
FULL OUTER JOIN t2 ON t2.name = t1.name
Output:
name id data1 data2
a 1 a1 null
d 1 d1 d2
k 1 null k2
Demo on db-fiddle

I think you just want a full join:
select *
from t1 full join
t2
using (name, id);

Thanks, Gordon and Nick for your answers.
I checked the documentation and found an even shorter variation:
select *
from t1
natural full join t2;
From the documentation
Furthermore, the output of JOIN USING suppresses redundant columns: there is no need to print both of the matched columns, since they must have equal values.
While JOIN ON produces all columns from T1 followed by all columns from T2, JOIN USING produces one output column for each of the listed column pairs (in the listed order), followed by any remaining columns from T1, followed by any remaining columns from T2.

Related

Take the row after the specific row

I have the table, where I need to take the next row after the row which has course 'TA' and flag = 1. For this I created the column rnum (OVER DATE) which may help for finding it
| student | date | course | flag | rnum |
| ------- | ----- | ----------- | ---- | ---- |
| 1 | 17:00 | Math | null | 1 |
| 1 | 17:10 | Python | null | 2 |
| 1 | 17:15 | TA | 1 | 3 |
| 1 | 17:20 | English | null | 4 |
| 1 | 17:35 | Geography | null | 5 |
| 2 | 16:10 | English | null | 1 |
| 2 | 16:20 | TA | 1 | 2 |
| 2 | 16:30 | SQL | null | 3 |
| 2 | 16:40 | Python | null | 4 |
| 3 | 19:05 | English | null | 1 |
| 3 | 19:20 | Literachure | null | 2 |
| 3 | 19:30 | TA | null | 3 |
| 3 | 19:40 | Python | null | 4 |
| 3 | 19:50 | Python | null | 5 |
As a result I should have:
| student | date | course | flag | rnum |
| ------- | ----- | ------- | ---- | ---- |
| 1 | 17:20 | English | null | 4 |
| 2 | 16:30 | SQL | null | 3 |
There are many ways to get your desired result, let's see some of them.
1) EXISTS
You can use the EXISTS clause, specifying a subquery to match for the condition.
SELECT T2.*
FROM #MyTable T2
WHERE EXISTS (
SELECT 'x' x
FROM #MyTable T1
WHERE T1.course = 'TA' AND T1.flag = 1
AND T1.student = T2.student AND T2.rnum = T1.rnum + 1
)
2) LAG
You ca use window function LAG to access previous row for a given order and then filter your resultset with your conditions.
SELECT w.student, w.date, w.course, w.flag, w.rnum
FROM (
SELECT T1.*
, LAG(course, 1) OVER (PARTITION BY student ORDER BY rnum) prevCourse
, LAG(flag, 1) OVER (PARTITION BY student ORDER BY rnum) prevFlag
FROM #MyTable T1
) w
WHERE prevCourse = 'TA' AND prevFlag = 1
3) JOIN
You can self-JOIN your table on the next rnum and keep only the rows who match the right condition.
SELECT T2.*
FROM MyTable T1
JOIN MyTable T2 ON T1.student = T2.student AND T2.rnum = T1.rnum + 1
WHERE T1.course = 'TA' AND T1.flag = 1
4) CROSS APPLY
You can use CROSS APPLY to specify a subquery with the matching condition. It is pretty similar to EXISTS clause, but you will also get in your resultset the columns from the subquery.
SELECT T2.*
FROM #MyTable T2
CROSS APPLY (
SELECT 'x' x
FROM #MyTable T1
WHERE T1.course = 'TA' AND T1.flag = 1
AND T1.student = T2.student AND T2.rnum = T1.rnum + 1
) x
5) CTE
You can use common table expression (CTE) to extract matching rows and then use it to filter your table with a JOIN.
;WITH
T1 AS (
SELECT student, rnum
FROM #MyTable T1
WHERE T1.course = 'TA' AND T1.flag = 1
)
SELECT T2.*
FROM #MyTable T2
JOIN T1 ON T1.student = T2.student AND T2.rnum = T1.rnum + 1
Adding the rownumber was a good start, you can use it to join the table with itself:
WITH matches AS (
SELECT
student,
rnum
FROM table
WHERE flag = 1
AND course = 'TA'
)
SELECT t.*
FROM table t
JOIN matches m
on t.student = m.student
and t.rnum = m.rnum + 1

SQL - Select records without duplicate on just one field in SQL?

Table 1
| Customer_ID | Template_ID
---------------------
| C1 | T1 |
| C1 | T2 |
---------------------
Table 2
---------------------
| Template_ID | Product_ID
---------------------
| T1 | P1 |
| T1 | P5 |
| T1 | P5 |
| T2 | P10 |
| T2 | P45 |
Expected Join query result:
------------------------------------------
| Customer_ID | Template_ID | Product_ID
------------------------------------------
| C1 | T1 | P1
| C1 | T1 | P5
| C1 | T2 | P10
| C1 | T2 | P45
.
.
For a template, I want to get only the unique Product_ID like above. Currently my query returns P5 twice like,
.
.
| C1 | T1 | P5
| C1 | T1 | P5
.
.
How can I handle this at the query level?
use distinct
select distinct t1.*,t2.productid
from table1 t1 join table2 t2 on t1.Template_ID =t2.Template_ID
Use DISTINCT to eliminates duplicates. It does not apply to the first column only, but to the whole row.
For example:
select distinct t1.customer_id, t1.template_id, t2.product_id
from t1
join t2 on t2.template_id = t1.template_id
You just have to GROUP BY the field you want to be unique, so Product_ID:
SELECT Customer_ID, Template_ID, Product_ID
FROM table1
JOIN table2 using ( Template_ID )
GROUP BY Product_ID;
Please try this.
SELECT
DISTINCT A.Customer_ID ,A.Template_ID ,B.Product_ID
FROM
table1 AS A
INNER JOIN table2 AS B
ON A.Template_ID = B.Template_ID

How to find out if 2 values in the same row exist in another table

I want to find out if 2 values in the same row exist in another table.
Only return the row if both values exist at the same time.
I can probably do 2 joins but is there an efficient way to do this?
Is there something like below?
table_1
+---------+---------------+------+
| id | other_id | key |
+---------+---------------+------+
| 1 | 2 | A |
| 2 | 1 | B |
| 1 | 3 | C |
| 4 | 2 | D |
+---------+---------------+------+
table_2
+---------+
| id |
+---------+
| 2 |
| 3 |
| 4 |
+---------+
SELECT
*
from
table_1
where
(id, other_id) in (
SELECT
id
from
table_2
)
output_table
+---------+---------------+------+
| id | other_id | key |
+---------+---------------+------+
| 4 | 2 | D |
+---------+---------------+------+
You can try as
SELECT *
FROM table1 t1
WHERE exists (select 1 from table2 t2 where t1.id=t2.id)
and exists (select 1 from table2 t3 where t1.anotherid=t3.id) ;
select * from table_1
where table_1.id in (select table_2.id from table_2)
try this , hope this help you!
Use exists:
SELECT
*
from
table_1 t1
where exists
(
select id from table_2 t2
where t1.id = t2.id or t1.other_id = t2.id;
)

SQL query join- unrelated tables

Can someone help me to join the two tables without any primary or secondary keys. Sample table is
TABLE 1
| ID | NAME |
| 1 | x |
| 2 | Y |
| 3 | z |
TABLE 2
| Num | NAME | DATE |
| 52 | X | 12-aug-17 |
| 53 | X | 11-apr-17 |
| 62 | X | 10-aug-11 |
| 12 | y | 2-jan-16 |
| 23 | Y | 3-apr-18 |
I want retrieve data from X
select *
from table2
where name = 'x';
| Num | NAME | DATE |
| 52 | X | 12-aug-17 |
| 53 | X | 11-apr-17 |
| 62 | X | 10-aug-11 |
Now I will get three data from table2. I'm little stuck after this step. I want to get top of data the from table 2 and combine with table one.
I want final output should be
| ID | NAME | Num | DATE |
| 1 | x | 52 | 12-aug-17 |
Can someone suggest me how can I join this table? Its easy to join when we have any primary key but here not the case
Thanks
You can use this:
SELECT TOP(1) table1.ID, table2.Num, table2.Name, table2.DATE
FROM table2 INNER JOIN table1 ON table1.NAME = table2.NAME
WHERE table2.NAME = 'x'
ORDER BY table2.DATE ASC
OR
SELECT table1.ID, table2.Num, table2.Name, table2.DATE
FROM table1 INNER JOIN
(SELECT TOP(1) * FROM table2 WHERE NAME = 'x' ORDER BY DATE ASC) table2
ON table1.NAME = table2.NAME
You need to get the maximum DATE using a subquery, as in:
select t1.id, t2.*
from table1 t1
join table2 t2 on t2.name = t1.name
where t2.date = (
select max(date) from table2 where name = 'x'
);

Access SQL: Select from table if ID is in another

Say I have the table T1:
ID | PNo | MM | CP | Flag | Name |
---|-----|------|----|------|------|
1 | 13 | True | 4 | A | X |
1 | 92 | True | 3 | A | X |
2 | 1 | True | 3 | B | Y |
2 | 13 | False| 2 | A | Y |
3 | 13 | True | 3 | B | W |
4 | 1 | True | 3 | B | Z |
And T2:
ID | PNo | MM | CP |
---|-----|------|----|
1 | 13 | True | 4 |
2 | 92 | True | 3 |
3 | 1 | True | 3 |
4 | 13 | False| 2 |
5 | 13 | True | 3 |
1 | 1 | False| 3 |
What I want to do is to do a INSERT INTO where I take values of T1 and T2 but only if the ID of T2 is in T1 and if T1 has the flag with the value A.
I have tried two things:
1) INNER JOIN: Something like
INSERT INTO T3 (ID, PNo, MM, CP, Flag, Name)
SELECT T1.ID, T2.PNo, T2.MM, T2.CP, 'A', T1.Name
FROM T2 INNER JOIN T1 ON T1.ID = T2.ID
WHERE (T1.FLAG = 'A')
The problem here is that it literally takes every combination of all relevant rows from T1 and T2. What I want is, I only want to take those rows of T2 whose IDs are also in T1.
2) IN?
INSERT INTO T3 (ID, PNo, MM, CP, Flag, Name)
SELECT T1.ID, T2.PNo, T2.MM, T2.CP, 'A', T1.Name
FROM T2, T1
WHERE T2.ID IN
(SELECT ID FROM T1 WHERE Flag = 'X')
Problem here is, this takes foreeeeeeeeeeever!
Is there not a more sophisticated method for this?
edit:// Changed a value in T2 so that the example is more meaningful.
So what I want in the new table T3 is:
ID | PNo | MM | CP | Flag | Name |
---|-----|------|----|------|------|
1 | 13 | True | 4 | A | X |
1 | 1 | False| 3 | A | X |
2 | 1 | True | 3 | A | Y |
What I get instead is:
ID | PNo | MM | CP | Flag | Name |
---|-----|------|----|------|------|
1 | 13 | True | 4 | A | X |
1 | 1 | False| 3 | A | X |
1 | 13 | True | 4 | A | X |
1 | 1 | False| 3 | A | X |
2 | 1 | True | 3 | A | Y |
So basically for all T1 values that I select (ID, Name) and for all corresponding rows that I can match by ID in T2, I get every combination.
Your second solution is almost right, however, you perform cartesian product unnecessarily (I'm not surprised that it takes forever). Try this one and if it is slow then create an index on Flag.
INSERT INTO T3 (ID, PNo, MM, CP, Flag, Name)
SELECT *
FROM T2
WHERE T2.ID IN
(SELECT ID FROM T1 WHERE Flag = 'X')
This should do what you want:
INSERT INTO T3 (ID, PNo, MM, CP, Flag, Name)
SELECT T2.ID, T2.PNo, T2.MM, T2.CP, ?, "A"
FROM T2
WHERE T2.ID IN (SELECT ID FROM T1 WHERE Flag = "A");
You are selecting T1.ID in your first query. You can take either T1.ID or T2.ID, because you are requiring that they be equal.
You can do this with a JOIN:
INSERT INTO T3 (ID, PNo, MM, CP, Flag, Name)
SELECT T2.ID, T2.PNo, T2.MM, T2.CP, T1.Name, "A"
FROM T2 INNER JOIN
T1
ON T2.ID = T1.ID;
If this is generating duplicates, then you have multiple rows in T1 for a given name.