How to optimize OR clauses in ON condition in PostgreSQL? - sql

I have a query like this:
SELECT a.*
FROM a
LEFT JOIN b
ON b.a_id = a.id
LEFT JOIN c
ON c.a_id = a.id
LEFT JOIN (/* some complex subquery */) AS q
ON q.id = a.id OR q.id = b.id OR q.id = c.id
Obviously it's not optimal because using ORs in the ON condition makes the planner unable to use indexes.
I tried to rewrite it like this:
WITH (/* some complex subquery */) AS q
SELECT a.*
FROM a
LEFT JOIN q AS qa
ON qa.id = a.id
LEFT JOIN b
ON b.a_id = a.id
LEFT JOIN q AS qb
ON qb.id = b.id
LEFT JOIN c
ON c.a_id = a.id
LEFT JOIN q AS qc
ON qc.id = c.id
WHERE COALESCE(qa.id, qb.id, qc.id) IS NOT NULL
but it didn't get much faster because the database still needs to evaluate the WHERE clause.
Is there any technique to optimize such a kind of joins? If not, then how can I redesign the schema?

Related

CTE vs Multiple joins in SQL Server

I'm wondering if I am using CTEs correctly. I have two versions of a query, the first one uses a CTE and the second one uses multiple joins, both accomplish the same result.
So, my question(s) is, am I using the CTE correctly? It is more readable, but was this the intended use/ is it more efficient than the joins?
CTE QUERY:
WITH ASSOCIATION AS (
SELECT PK_COLUMN AS ID,
ENTITY_COLUMN,
ANOTHER_ENTITY,
FROM A
LEFT OUTER JOIN B ON A.ID = B.ID
LEFT OUTER JOIN C ON C.ID = B.ID
LEFT OUTER JOIN D ON D.ID = C.ID
)
SELECT COLUMNS
FROM Z
LEFT JOIN Y ON Y.ID = Z.ID
LEFT JOIN ASSOCIATION AA ON AA.ID = Y.ID AND SOME_CONDITION
LEFT JOIN ASSOCIATION BB ON BB.ID = Y.ID AND SOME_DIFFERENT_CONDITION
vs
MULTIPLE JOINS QUERY:
SELECT COLUMNS
FROM Z
LEFT JOIN Y ON Y.ID = Z.ID
LEFT JOIN A ON A.ID = Y.ID AND SOME_CONDITION
LEFT JOIN B ON A.ID = B.ID
LEFT JOIN C ON C.ID = B.ID
LEFT JOIN D ON D.ID = C.ID
LEFT JOIN A A2 ON A2.ID ON Y.ID AND SOME_DIFFERENT_CONDITION
LEFT JOIN B B2 ON A2.ID = B2.ID
LEFT JOIN C C2 ON C2.ID = B2.ID
LEFT JOIN D D2 ON D2.ID = C2.ID

Right join vs left join, which table is left vs right?

Given
select *
from a
left join b
on a.id = b.id
is table a left and table b right?
Would that be equivalent to
Select *
from a
right join b
on b.id = a.id
because I switched left and right while flipping the ON clause? Or is a still left because it came first and b is right because it's the thing we're joining?
Thank you.
No. "left" and "right" refer to the ordering of the tables in the FROM clause. So these are equivalent:
select *
from a left join
b
on a.id = b.id
select *
from b right join
a
on a.id = b.id
These two on clauses do exactly the same thing:
on a.id = b.id
on b.id = a.id
They do not affect the results at all.

Simplifying code with nested JOINs in WHERE clause

I'm writing an SQL statement in PostgreSQL where I'm JOINing data from different tables that are each connected by foreign keys on their ids. Table b has a field a_id which relates to the id of table a and so on.
My problem is that I want to reuse a value from the joined table in a WHERE clause without having to do all the JOINs again, like this:
SELECT *
FROM a
INNER JOIN b ON b.a_id = a.id
INNER JOIN c ON c.b_id = b.id
WHERE a.id = 3
AND a.x =
(SELECT c.y
FROM a
INNER JOIN b ON b.a_id = a.id
INNER JOIN c ON c.b_id = b.id
WHERE a.id = 3
AND c.id = 5)
I bet there's a simpler solution for this snippet that I'm just not realising. I'll be glad if anybody can help me out.
I don't have a silver bullet answer which simplifies your query, but CTEs certainly could make it a bit easier on the eyes:
WITH cte AS (
SELECT *
FROM a
INNER JOIN b ON b.a_id = a.id
INNER JOIN c ON c.b_id = b.id
WHERE a.id = 3
)
SELECT *
FROM cte
WHERE x IN (SELECT y FROM cte WHERE c_id = 5);
My aliases or column names may be off, and you may need to tidy up the CTE a bit before it would actually work for you.
You can use window functions for this:
SELECT . . .
FROM (SELECT a.*, b.*, c.*, -- should list the columns explicitly
MAX(c.y) FILTER (WHERE c.id = 5) OVER () as y_5
FROM a INNER JOIN
b
ON b.a_id = a.id INNER JOIN
c
ON c.b_id = b.id
WHERE a.id = 3
) abc
WHERE abc.x = abc.y_5;
I hope the below-mentioned query will help you.
SELECT *
FROM a
INNER JOIN b ON b.a_id = a.id
INNER JOIN c ON c.b_id = b.id
WHERE a.id = 3 and c.id =5 and a.x = c.y

How to write a transitive sql join in DB2?

I want something like the following.
SELECT fewCols, aColFromNewTbl FROM TABLE_A AS A
LEFT OUTER JOIN TABLE_B AS B ON A.ID = B.ID
LEFT OUTER JOIN TABLE_C AS C ON A.ID = C.ID
INNER JOIN A_NEW_TABLE AS NEWTBL ON NEWTBL.ID = B.ID;
Somehow I'm not able to achieve this functionality. Actually above query is suppose to join A with NEWTBL, but I'm joining it with B, which is already joined with A. For my results I want them to come exclusively from the join of NEWTBL and B. I don't know how I can get desired results?
Probably you need this:
SELECT fewCols, aColFromNewTbl
FROM TABLE_A AS A
LEFT OUTER JOIN TABLE_B AS B
INNER JOIN A_NEW_TABLE AS NEWTBL
ON NEWTBL.ID = B.ID
ON A.ID = B.ID
LEFT OUTER JOIN TABLE_C AS C
ON A.ID = C.ID;

Right join with a where clause

I found a query like this in code:
SELECT *
FROM a
RIGHT JOIN b ON a.id = b.id
WHERE a.id = b.id
Is it basically the same as an inner join on a.id = b.id?
Yes, this is basically the same as an inner join.
The where clause will fail when there are no matches, because the value a.id will be NULL.