outer join more than 1 table - sql

let's say I have 5 tables, as follows:
CREATE TABLE T1 (
FIRST_NAME VARCHAR2(100),
LAST_NAME VARCHAR2(100),
CITY NUMERIC,
SALARY NUMERIC);
CREATE TABLE T2 (
CITY NUMERIC,
DISTRICT NUMERIC);
CREATE TABLE T3 (
DISTRICT NUMERIC,
DOMAIN NUMERIC);
CREATE TABLE T4 (
DOMAIN NUMERIC,
DETAILS_BOOK NUMERIC);
CREATE TABLE T5 (
DETAILS_BOOK NUMERIC,
FIRST_NAME VARCHAR2(100),
LAST_NAME VARCHAR2(100),
EMAIL VARCHAR2(100));
INSERT INTO T1 VALUES ('john', 'doe',1001,1000);
INSERT INTO T1 VALUES ('jack', 'jill',1001,2000);
INSERT INTO T1 VALUES ('jeff', 'bush',1001,1500);
INSERT INTO T2 VALUES (1001,1);
INSERT INTO T3 VALUES (1,543);
INSERT INTO T4 VALUES (543,22);
INSERT INTO T5 VALUES (22,'john', 'doe','john#22.com');
INSERT INTO T5 VALUES (44,'john', 'doe','john#44.com');
INSERT INTO T5 VALUES (22,'jeff', 'bush','jeff#22.com');
INSERT INTO T5 VALUES (44,'jeff', 'bush','jeff#44.com');
now, I want all records from t1, with their salaries and emails, corresponding to tables t2, t3, and t4, such that the reuslt should be:
FIRST_NAME | LAST_NAME | SALARY | EMAIL
--------------------------------------------------
john | doe | 1000 | john#22.com
jeff | bush | 1500 | jeff#22.com
jack | jill | 2000 | (NULL)
what I got so far is:
SELECT T1.FIRST_NAME, T1.LAST_NAME,T1.SALARY,T5.EMAIL
FROM T1,T2,T3,T4,T5
WHERE T1.FIRST_NAME = T5.FIRST_NAME (+)
and T1.LAST_NAME = T5.LAST_NAME(+)
AND T1.CITY = T2.CITY
AND T2.DISTRICT = T3.DISTRICT
AND T3.DOMAIN = T4.DOMAIN
AND T4.DETAILS_BOOK = T5.DETAILS_BOOK
which returns only the first two rows.

Try this instead:
SELECT
T1.FIRST_NAME,
T1.LAST_NAME,
T1.SALARY,
T5.EMAIL
FROM T1
LEFT JOIN T2 ON T1.CITY = T2.CITY
LEFT JOIN T3 ON T2.DISTRICT = T3.DISTRICT
LEFT JOIN T4 ON T3.DOMAIN = T4.DOMAIN
LEFT JOIN T5 ON T4.DETAILS_BOOK = T5.DETAILS_BOOK
AND T1.FIRST_NAME = T5.FIRST_NAME
AND T1.LAST_NAME = T5.LAST_NAME;
SQL Fiddle Demo
This will give you:
| FIRST_NAME | LAST_NAME | SALARY | EMAIL |
-------------------------------------------------
| john | doe | 1000 | john#22.com |
| jeff | bush | 1500 | jeff#22.com |
| jack | jill | 2000 | (null) |
The problem is that the INNER JOIN after the OUTER JOIN makes your joins works like an INNER , because, the inner joins eliminate those unmatched rows coming from the outer joins.
Note that: I used the ANSI SQL-92 explicit LEFT OUTER JOIN syntax, instead of the old implicit OUTER and INNER join syntax that you sued in your query.
Please try to use the LEFT OUTER JOIN instead of the old outer join syntax, and avoid INNER JOIN after OUTER JOINs.
For more details, see these:
Bad habits to kick : using old-style JOINs..
Bad habits to kick : using table aliases like (a, b, c) or (t1, t2, t3)
Update:
When you have many tables references in the FROM clause with the JOIN between them, each table is joined with the next table begging from the FROM clause1, results a temporary result set, then this temporary result set is joined with the next table and so on. In case of the OUTER JOIN, there are left or right:
LEFT JOIN will include those unmatched rows from the left table, where as,
RIGHT JOIN will include those unmatched rows from the right table.
Depending on the data you want to select, you have to watch out those tables in the two sides of the JOIN operator and the order of them.
1:This is just the logical query processing order, but in the actual order is always up to the query optimizer.

Related

Replace one row with mupltiple rows form a different table

I have two tables:Table 1:
Number| Code | Value
-------+------+-----
Garden |A0,C2 | 100
Garden |rest | 500
House |A0,C2 | 100
House |rest | 500
Table2:
|Code|
+-----+
|A0 |
|B1 |
|C2 |
|D3 |
|E4 |
I would like to get a Table that looks something like this:
Number| Code | Value
-------+------+-----
Garden |A0 | 100
Garden |B1 | 500
Garden |C2 | 100
Garden |D3 | 500
Garden |E4 | 500
House |A0 | 100
House |B1 | 500
House |C2 | 100
House |D3 | 500
House |E4 | 500
Does anyone know a SQL Statement on how I can get to this table
Changing either table 1 or table 2 is not possible
I'm not a fan of this solution, as the JOIN with an EXISTS is ugly. I strongly suggest fixing your data model here. You shouldn't be using delimited data in your table, and you shouldn't be using a value like 'rest' to denote you want all other values other than those previously defined. The result set you get here is a normalised dataset, and that is exactly how you should be storing your data:
CREATE TABLE Table1 (Number varchar(6), --A number is a varchar?
Code varchar(50),
[Value] int);
INSERT INTO dbo.Table1 (Number,
Code,
[Value])
VALUES ('Garden','A0,C2',100),
('Garden','rest',500),
('House','A0,C2',100),
('House','rest',500);
CREATE TABLE Table2 (Code char(2));
INSERT INTO dbo.Table2 (Code)
VALUES ('A0'),
('B1'),
('C2'),
('D3'),
('E4');
GO
WITH CTE AS(
SELECT T1.Number,
SS.[value] AS Code,
T1.[Value]
FROM dbo.Table1 T1
CROSS APPLY STRING_SPLIT(T1.Code, ',') SS)
SELECT C.Number,
T2.Code,
C.[Value]
FROM CTE C
JOIN dbo.Table2 T2 ON C.Code = T2.Code
OR (C.Code = 'rest'
AND NOT EXISTS (SELECT 1
FROM CTE e
WHERE e.Number = C.Number
AND e.Code = T2.Code))
GO
DROP TABLE dbo.Table1;
DROP TABLE dbo.Table2;
db<>fiddle
If you aren't using SQL Server 2016+, you won't be able to use STRING_SPLIT. As a result I suggest looking up an "XML Splitter" or delimitedsplit8k(_lead).
You should fix the table1 so it is not using lists represented as comma-delimited strings. You should have a separate table for this. There are many good reasons for this.
Assuming that you are stuck with someone-else's really, really bad data model, you can use a join. There are several approaches. SQL Server 2017+ has built-in string_split() function.
Then, you want to generate the rows with a cross join and bring in the values you want using left joins:
select n.number, t2.code, coalesce(t1.value, t1rest.value) as value
from (select distinct number from table1) n cross join
table2 t2 left join
table1 t1
on t1.number = n.number and ',' + t1.codes + ',' like '%,' + t2.code + ',%' left join
table1 t1rest
on t1rest.number = n.number and t1rest.codes = 'rest';
Here is a db<>fiddle

How can I join two tables with a many-to-one relationship ordered by date?

I need to join two tables and get the most recent record only. Here is the basic form:
table1.id | table1.region | table1.important_col1
1 | NORTH AMERICA | abc
2 | CHINA | def
2 | NORTH AMERICA | hij
table2.id | table2.region | table2.transaction_date | table2.important_col2
1 | NORTH AMERICA | 2/13/2019 | xyz
1 | NORTH AMERICA | 1/13/2019 | zzz
1 | NORTH AMERICA | 12/13/2018 | xxx
desired result:
1 | NORTH AMERICA | 2/13/2019 | abc | xyz
I wanted to use this answer but it seems like I can't use it if I need to group by and then order by descending date. I will need information in multiple columns on the right hand side, but do not want duplicate rows on the left hand side.
The right hand side may have up to 100s of records per id, but I just need something that works for now. Thanks in advance.
edit: I also need to filter the right hand side on other criteria so a simple MAX(table2.transaction_date) won't work.
You can filter your table using internal window function, I used LAG for this example, but you can use ROW_NUMBER and filter several records. Using sliding windows does not change the number of records or counted as SQL aggregation, i.e. you filter using where rather than with having.
SELECT
t1.id
,t2.transaction_date
,t1.region
,t1.col1
,t2.important_col2
FROM table1 AS t1
OUTER APPLY (
SELECT
id
,transaction_date
,LAG(transaction_date,1) over (partition by id order by transaction_date desc) as prev_td
,important_col2
FROM table2
-- WHERE filter_by_col=1 -- additonal "right side" filtering
) as t2
where t1.id = t2.id
and t2.prev_td is null
Output:
1 2019-02-13 00:00:00.000 NORTH AMERICA abc xyz
I used this to test the above query:
create table table1
(id int,
region varchar(30),
col1 varchar(100));
insert into table1
values (1 ,'NORTH AMERICA' ,'abc'),
(2,'CHINA','def'),
(2,'NORTH AMERICA','hij');
create table table2
(id int,
region varchar(30),
transaction_date datetime,
important_col2 varchar(100))
insert into table2
values
(1 ,'NORTH AMERICA',convert(datetime, '02/13/19', 1),'xyz'),
(1 ,'NORTH AMERICA',convert(datetime, '01/13/19',1),'zzz'),
(1 ,'NORTH AMERICA',convert(datetime, '12/13/18',1),'xxx')
Try in this way:
select table11.id, table1.region, max(table2.transaction_date) transaction_date
from table1
inner join table2
on table1.id = table2.id
group by table1.id, table1.region
If there are more columns in table2 (other than transaction date) that you want to display as well, then aggregation alone cannot solve your question.
In MySQL 8.0 you can use window function ROW_NUMBER() to identify the most recent transaction record, as follows :
SELECT x.*
FROM (
SELECT
t1.*,
t2.*,
ROW_NUMBER() OVER(PARTITION BY t2.region ORDER BY t2.transaction_date DESC) rn
FROM table1 t1
INNER JOIN table2 t2 ON t1.region = t2.region
) x
WHERE x.rn = 1
In earlier versions of MySQL, one solution is to add a NOT EXISTS with a correlated subquery that ensures that we are joining with the most recent transaction for the current region :
SELECT t1.*, t2.*
FROM table1 t1
INNER JOIN table2 t2
ON t1.region = t2.region
AND NOT EXISTS (
SELECT 1
FROM table2
WHERE region = t2.region AND transaction_date > t2.transaction_date
)

SQL combining two tables without duplication

I want to combine two tables based on a column. Here's an example:
These are my two tables:
Table1 Table2
Person | salary Person | Age
Tim | 22.50 Larry | 32
Tony | 49.00 Tim | 22
John | 32.67 Tony | 44
Kim | 23.42 John | 31
And my resulting table should be:
Person | salary | Age
Larry | | 32
Tim | 22.50 | 22
Tony | 49.00 | 44
John | 32.67 | 31
Kim | 23.42 |
Everyone is added to the resulting table only once even if they don't have a value for both salary and age
Thanks in advance!
Since you have tagged this as MS-ACCESS I will provide SQL code for MS ACCESS
Because Microsoft does not have the capability to do a FULL OUTER JOIN, you have to think of a clever way to use existing SQL commands to create a FULL OUTER JOIN. The following code should do the trick using your tables above:
SELECT Table1.Person, Salary, Age
FROM Table1 INNER JOIN Table2 ON Table1.Person = Table2.Person
UNION ALL
SELECT Table1.Person, Salary, Age
FROM Table1 LEFT JOIN Table2 ON Table1.Person = Table2.Person
WHERE Table2.Person Is Null
UNION ALL
SELECT Table2.Person, Salary, Age
FROM Table1 RIGHT JOIN Table2 ON Table1.Person = Table2.Person
WHERE Table1.Person Is Null
This could have been done by full outer join but since you are using ms access you will have to use union all in the below manner.
Try this out and let me know in case you face any difficulties.
SELECT * FROM table1 t1
LEFT JOIN
table2 t2
ON t1.person = t2.person
UNION all
SELECT * FROM table1 t1
RIGHT JOIN
table2 t2
ON t1.person = t2.person
I don't see what's your problem exactly but here is a SQL query that will do just what you have requested:
SELECT Person, salary, Age
FROM Table1 FULL OUTER JOIN Table2
WHERE Table1.Person = Table2.Person
Edit: This won't work on MS-ACCESS for its lack of support for FULL OUTER JOINS, the other two answers have explained the alternative.

SELECT Table From Other Table

how to select table from select other table ?
Table1
ID| Name |
1 | Henry
2 | Dony
Table2
ID| Addres|
1 | London
2 | Texas
I have select ID table2 From Select table1, like below :
SELECT ID From Table2 Where Select ID From Table1
You could write a JOIN to do so:
SELECT ID FROM Table2 AS T2
INNER JOIN Table1 AS T1
ON T2.ID=T1.ID
Christos is totally right, you can just use an INNER JOIN to join the two tables. Basically, what this will do is combine both tables, returning only those values from where your specified match key exists. So if you have an ID from table 1, and it isn't in table 2, then you won't see that ID in the query Christos specified. You can use a LEFT JOIN on the command above to get ALL ID's from the T1 table (or a RIGHT JOIN to get all ID's from T2).
Good luck!

Proper Left join of three tables - SQL

Update - Ok the three answers all make sense, i'm going to try them each as I am curious if there is a performance +/- but i'm not sure I have enough test data in my tables to determine that.
I am trying to look at Table A and search to see if a user exists in Table B or Table C so as to find anyone form table A who does not exists in at least one of the other two tables (they do not need to exists in both, just B or C)
Something like this but without having to have to almost identical statements below
SELECT emp_id
FROM
tableA
LEFT JOIN
TableB
ON
tableA.emp_id = tableB.emp_id
WHERE
TableA.emp_id IS NULL
SELECT emp_id
FROM
tableA
LEFT JOIN
TableC
ON
tableA.emp_id = tableC.emp_id
WHERE
TableA.emp_id IS NULL
Table A
+---------+--------+-----------+
| Emp_ID | Status | hire_date |
+---------+--------+-----------+
| 12345 | happy | 10/10/2005|
| 54321 | sad | 12/01/2009|
+---------+--------+-----------+
Table B
+---------+--------+
| Emp_ID | Weight |
+---------+--------+
| 12345 | 185 |
| 54321 | 150 |
+---------+--------+
Table C
+---------+--------+
| Emp_ID | City |
+---------+--------+
| 12345 | Chicago|
| 54321 | Atlanta|
+---------+--------+
Thanks for any suggestions!
You can join all tables in a single query.
SELECT a.Emp_ID -- a.* <<== if you want to include all columns
FROM tbA a
LEFT JOIN tbB b
ON a.Emp_ID = b.Emp_ID
LEFT JOIN tbC c
ON a.Emp_ID = c.Emp_ID
WHERE b.Emp_ID IS NULL
AND c.Emp_ID IS NULL -- <<== AND should be use here
Why not just express the query using not in?
SELECT emp_id
FROM tableA
WHERE emp_id not in (select emp_id from TableB) and
emp_id not in (select emp_id from TableC);
you can join 3 table simply as below
Select emp_id from table1 a left join table2 b on a.emp_id=b.emp_id
left join table3 c on c.emp_id=a.emp_id
Your query can't work because you have a WHERE tableA.emp_id IS NULL, and TableA is the emp_id you want to test for, you should have tested with TableB.eemp_id IS NULL and TableC.emp_id IS NULL for the second query.
Since you want rows that do not exists in at least tableB or tableC, you can do a LEFT JOIN with both tableB and tableC and test if at least one of the emp_id in those tables IS NULL with a OR
SELECT emp_id
FROM tableA
LEFT JOIN TableB ON tableA.emp_id = tableB.emp_id
LEFT JOIN TableC ON tableA.emp_id = tableC.emp_id
WHERE
TableB.emp_id IS NULL
OR TableC.emp_id IS NULL