Join 3 tables with different FKs - sql

I'm trying to join 3 tables together in a SELECT query with some WHERE clauses.
Table 1 is linked to Table 2, and Table 2 is linked to Table 3.
The tables are as follows:
Author
PK: Author_ID
FK: Location_ID
Author_First_Name
Location
PK: Location_ID
City
Articles
PK: Article_ID
FK: Author_ID
Article_Name
So far I've put together this Query, but am having trouble determining how to to Join the second Foreign Key 'Location_ID'
SELECT
Articles.Article_Name
FROM
Articles
INNER JOIN Author
ON Articles.Author_ID
INNER JOIN Location
ON
WHERE Author.Author_First_Name='Sam'
AND Location.City<>'Detroit'

The proper syntax is something like this:
SELECT a.Article_Name
FROM Articles a INNER JOIN
Author au
ON a.Author_ID = au.Author_ID INNER JOIN
Location l
ON l.Location_ID = au.Location_Id
WHERE au.First_Name = 'Sam' AND l.City <> 'Detroit';
Note the use of table aliases make the query easier to write and to read. Also, you need conditions connecting the two tables for the ON clauses.

You forgot to compare table fields
SELECT * FROM articles ar
LEFT JOIN author a ON ar.author_id = a.author_id
LEFT JOIN location l ON a.location_id = l.location_id
WHERE a.author_first_name = 'Sam' AND l.city <> 'Detroit'

Related

How to inner join of array type in postgres?

employees table with columns:
id (pk) BIGINT, name TEXT, email TEXT, work_locations BIGINT[].
work_locations columns contains the location table ids.
location table with columns:
id (pk) BIGINT, lat DECIMAL(12,9), long DECIMAL(12,9).
I want a result like
id name email lat, long
1 name email#email.com 23.345 54.3678
I am not able to join two table on work_locations and ids.
How to join these two tables?
You can join using the ANY operator:
select e.*,
l.lat,
l.long
from employees e
join locations l on l.id = any(e.work_locations)
order by e.id;
In general I would recommend to not store foreign keys in arrays like that. You should think about a properly normalized model with a many-to-many link table between employees and locations
You can unnest():
select e.*, l.*
from employees e cross join lateral
unnest(work_locations) wl(work_location) left join
locations l
on wl.work_location = l.id;

joining a table on 2 fields

I want to pull a person and their supervisor names from a table. The persons table has the supervisor_id and the person_id. The names table has name_id and a Full Name field. If I join Person ON either supervisor_id or person_id, how do I get the other to display as well?
You need to join twice, one for each relationship you have:
SELECT
-- Persons' columns
P.*,
-- Superviser name columns
SN.*,
-- Person name columns
PN.*
FROM
persons AS P
LEFT JOIN names AS SN ON P.supervisor_id = SN.name_id
LEFT JOIN names AS PN ON P.person_id = PN.name_id
Or you can join with an OR clause, but you won't be able to know which record did you join with unless you check with a CASE.
SELECT
-- Persons' columns
P.*,
-- name columns
N.*,
IsSupervisor = CASE WHEN P.supervisor_id = N.name_id THEN 'Yes' ELSE 'No' END
FROM
persons AS P
LEFT JOIN names AS N ON
P.supervisor_id = N.name_id OR
P.person_id = N.name_id
This last approach will display 2 rows as it will match either one or the other on different occasions, not both with the same persons row (as the first example).
A (self)join is what you need:
select p.*, supervisor=ps.name
from Person p join person ps on p.supervisor_id=ps.id

How to work in case in join condition

How to find city when ContactID is provided and condition is if ContactID is coming as 123 then it will look whether it is P or C, If P then it will go to Person table and returns City(USA) as output and If C then it will go to Company table and gives City(AUS) as output.
NB: all tables contain thousands of record and City value comes from run time.
Unless you're dynamically generating the query (i.e. using some language other than SQL to execute it) then you need to join on both tables anyway. If you're joining on both tables then there's no need for a CASE statement:
select *
from contacts co
left outer join person p
on co.contactid = p.contactid
and co.person_company = 'P'
left outer join company c
on co.contactid = c.contactid
and co.person_company = 'C'
You'll start noting an issue here, for every column from PERSON and COMPANY you're going to have to add some business logic to work out which table you want the information from. This can get very tiresome
select co.contactid
, case when p.id is not null then p.name else c.name end as name
from contacts co
left outer join person p
on co.contactid = p.contactid
and co.person_company = 'P'
left outer join company c
on co.contactid = c.contactid
and co.person_company = 'C'
Your PERSON and COMPANY tables seem to have exactly the same information in them. If this is true in your actual data model then there's no need to split them up. You make the determination as to whether each entity is a person or a company in your CONTACTS table.
Creating additional tables to store data in this manner is only really helpful if you need to store additional data. Even then, I'd still put the data that means the same thing for a person or a companny (i.e. name or address) in a single table.
If there's a 1-2-1 relationship between CONTACTID and PID and CONTACTID and CID, which is what your sample data implies, then you have a number of additional IDs, which have no value.
Lastly, if you're not restricting that only companies can go in the COMPANY table and individuals in the PERSON table. You need the PERSON_COMPANY column to exist in both PERSON and COMPANY, though as a fixed string. It would be more normal to set up this data model as something like the following:
create table contacts (
id integer not null
, contact_type char(1) not null
, name varchar2(4000) not null
, city varchar2(3)
, constraint pk_contacts primary key (id)
, constraints uk_contacts unique (id, contact_type)
);
create table people (
id integer not null
, contact_type char(1) not null
, some_extra_info varchar2(4000)
, constraint pk_people primary key (id)
, constraint fk_people_contacts
foreign key (id, contact_type)
references contacts (id, contact_type)
, constraint chk_people_type check (contact_type = 'P')
);
etc.
you can LEFT JOIN all 3 tables and the using a CASE statement select the one that you need based on the P or C value
SELECT
CASE c.[Person/Company]
WHEN 'P' THEN p.NAME
WHEN 'C' THEN a.Name
END AS Name
FROM Contact c
LEFT JOIN Person p on p.ContactId = c.ContactId
LEFT JOIN Company a on a.ContachId = c.ContactId
Ben's answer is almost right. You might want to check that the first join has no match before doing the second one:
select c.*, coalesce(p.name, c.name) as p.name
from contacts c left outer join
person p
on c.contactid = p.contactid and
c.person_company = 'P' left join
company co
on c.contactid = co.contactid and
c.person_company = 'C' and
p.contactid is null;
This may not be important in your case. But in the event that the second join matches multiple rows and the first matches a single row, you might not want the additional rows in the output.

How can I use a join for two columns corresponding to the same primary key?

I have a table with five columns:
streetId, streetName, areaId, ISfSectionId1, ISFsectionId2
where areaId is a foreign key for table area and isfsectionid1 and isfsectionid2 are foreign keys for the same primary key isfsectionId (isfsectionId2 can have null value).
I am using this query to join them
SELECT
s.streetId, s.streetName, a.areaName, i.isfsectionName, d.ISFsectionName
FROM
area a
INNER JOIN
street s ON s.areaId = a.areaId
INNER JOIN
ISFsections i ON s.fasileone = i.ISFsectionId
JOIN
ISFsections d ON s.fasiletwo = d.ISFsectionId
Without that last join, it is working fine, but when adding the last join, it's returning records for the ISFsectionId2 only.
What is the problem?
SELECT s.streetId
,s.streetName
,a.areaName
,i.isfsectionName
,d.ISFsectionName
from area a
INNER join street s on s.areaId = a.areaId
LEFT join ISFsections i on s.fasileone = i.ISFsectionId
LEFT join ISFsections d on s.fasiletwo = d.ISFsectionId

Suggest optimized query using 4 tables ( RIGHT JOIN v/s INNER JOIN & HAVING )

I have following table structure
table_country ==> country_id (PK) | country | status
table_department ==> department_id (PK) | department | country_id (FK)
table_province ==> province_id (PK) | province | department_id (FK)
table_district ==> district_id (PK) | district | province_id (FK)
NOTE: all tables engine are innoDB
One country can have multiple department, one department can have multiple province and one province can have multiple district. Now I need to search only those country which have at least one district
I have written the below 2 SQL queries, in my case, both queries return the same results.... please describe the difference between those queries
Using a RIGHT JOIN:
SELECT c.country_id as id, c.country as name
FROM table_country c
RIGHT JOIN table_department d ON d.country_id=c.country_id
RIGHT JOIN table_province p ON p.department_id=d.department_id
RIGHT JOIN table_district ds ON ds.province_id=p.province_id
WHERE c.status='Active' GROUP BY (c.country_id)
Using INNER JOIN and HAVING clause:
SELECT COUNT(ds.district), c.country_id as id, c.country as name
FROM table_country c
INNER JOIN table_department d ON d.country_id = c.country_id
INNER JOIN table_province p ON p.department_id = d.department_id
INNER JOIN table_district ds ON ds.province_id = p.province_id
WHERE c.status='Active'
GROUP BY (c.country_id)
HAVING COUNT(ds.district)>0
Please tell me where these both query make the difference in results and which one I have to use or do I have to use a different query?
Thanks in advance
I would suggest using the second of your 2 queries (with inner joins), but without the HAVING clause as it isn't required because the inner joins require that any row in the final result MUST have a row in the district table.
The first of your queries, using a more exotic series of RIGHT OUTER JOINS, ultimately produces the same outcome - but because they are outer joins it is potentially less efficient. Another way of representing your first query would be to reverse the table sequence like this:
SELECT
c.country_id AS id
, c.country AS name
, COUNT(ds.district)
FROM table_district ds
INNER JOIN table_province p ON ds.province_id = p.province_id
INNER JOIN table_department d ON p.department_id = d.department_id
INNER JOIN table_country c ON d.country_id = c.country_id
WHERE c.status='Active'
GROUP BY
c.country_id
, c.country
and hopefully when inverted like that it is clear that no result row can exist unless there is a row from the district table.