Reputation: 413
Given the tables person
and car
and a relationship based on car.id
between the two tables, I could join them using the following code:
SELECT * FROM person
JOIN car
ON person.car_id = car.id;
However, I fail to figure out how to do this if I am working with two unnamed selections. Specifically, I would like to join the below selections based on id
.
SELECT id, phone_number
FROM student
WHERE id IN
(SELECT account_id
FROM loan
WHERE id IN
(SELECT loan_id
FROM fine
WHERE paid_amount < fine_amount))
UNION
SELECT id, phone_number
FROM administrator
WHERE id IN
(SELECT account_id
FROM loan
WHERE id IN
(SELECT loan_id
FROM fine
WHERE paid_amount < fine_amount))
SELECT
account_id,
EXTRACT ('day' FROM (NOW()::timestamp - expiry_date::timestamp))
FROM loan
WHERE id IN
(SELECT loan_id
FROM fine
WHERE paid_amount < fine_amount)
To clarify, if the first selection would be called table1
and the second selection would be called table2
, I would like to join the selections according to the below code
SELECT * FROM table1
JOIN table2
ON table1.id = table2.account_id;
Upvotes: 0
Views: 529
Reputation: 74670
You can give them a name each by putting the queries inline:
SELECT * FROM
(
--begin your first query
SELECT id, phone_number
FROM student
WHERE id IN
(SELECT account_id
FROM loan
WHERE id IN
(SELECT loan_id
FROM fine
WHERE paid_amount < fine_amount))
UNION
SELECT id, phone_number
FROM administrator
WHERE id IN
(SELECT account_id
FROM loan
WHERE id IN
(SELECT loan_id
FROM fine
WHERE paid_amount < fine_amount))
--end your first query
) t1
INNER JOIN
(
--begin your second query
SELECT
account_id,
EXTRACT ('day' FROM (NOW()::timestamp - expiry_date::timestamp))
FROM loan
WHERE id IN
(SELECT loan_id
FROM fine
WHERE paid_amount < fine_amount)
--end your second query
) t2
ON
t1.id = t2.account_id
You can also turn each query into a named CTE:
WITH table1 AS (
SELECT id, phone_number
FROM student
WHERE id IN
(SELECT account_id
FROM loan
WHERE id IN
(SELECT loan_id
FROM fine
WHERE paid_amount < fine_amount))
UNION
SELECT id, phone_number
FROM administrator
WHERE id IN
(SELECT account_id
FROM loan
WHERE id IN
(SELECT loan_id
FROM fine
WHERE paid_amount < fine_amount))
),
table2 as (
SELECT
account_id,
EXTRACT ('day' FROM (NOW()::timestamp - expiry_date::timestamp))
FROM loan
WHERE id IN
(SELECT loan_id
FROM fine
WHERE paid_amount < fine_amount)
)
--begin the query that joins the CTEs
SELECT * FROM table1
JOIN table2
ON table1.id = table2.account_id;
I'm struck though that you could simplify this query, something like this:
SELECT
people.*,
EXTRACT ('day' FROM (NOW()::timestamp - l.expiry_date::timestamp))
FROM
(
SELECT 'student' as typ, id, phone_number FROM student
UNION ALL
SELECT 'admin', id, phone_number FROM administrator
) people
INNER JOIN loan l ON people.id = l.account_id
INNER JOIN fine f ON l.id = f.loan_id
WHERE f.paid_amount < f.fine_amount
You should get into the habit of aliasing everything you put in a query (unless it's a subquery that filters one table only) and then use the aliases:
SELECT s.id as studentid, l.id as loanid FROM
student s
INNER JOIN loan l ON s.id = l.account_id
The reasons are many, but by using an alias and fully qualifying all your column references you prevent your query from breaking if someone adds a new column to one of the tables in future, where the name clashes with an existing column. By aliasing tables it allows you to join tables in multiple times. Example a student has a term_address and a home address:
SELECT * FROM
student s
INNER JOIN address ahome on s.home_address_id = ahome.id
INNER JOIN address aterm on s.term_address_id = aterm.id
This way you don't need a table for term addresses and a table for home addresses - the address table stores all addresses and you alias it differently. A different subset of records participates in each join
Anything that represents a block of data can be aliased, be it a table or a subquery, or possibly some other things.
SELECT * FROM
(subquery here) aliasForSubquery
INNER JOIN
(anther subquery) aliasForANotherSubquery
ON
aliasForSubquery.column = aliasForAnotherSubquery.column
The only thing you don't need to(can't) alias is the subquery that creates your IN(...)
list
Upvotes: 1
Reputation: 164164
You can use ctes:
with
cte1 as (
SELECT id, phone_number
FROM student
WHERE id IN
(SELECT account_id
FROM loan
WHERE id IN
(SELECT loan_id
FROM fine
WHERE paid_amount < fine_amount))
UNION
SELECT id, phone_number
FROM administrator
WHERE id IN
(SELECT account_id
FROM loan
WHERE id IN
(SELECT loan_id
FROM fine
WHERE paid_amount < fine_amount)
),
cte2 as (
SELECT
account_id,
EXTRACT ('day' FROM (NOW()::timestamp - expiry_date::timestamp))
FROM loan
WHERE id IN
(SELECT loan_id
FROM fine
WHERE paid_amount < fine_amount)
)
SELECT *
FROM cte1 JOIN cte2
ON cte1.id = cte2.account_id;
Upvotes: 1