Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQL: join with OR in condition

I have 2 tables:

Devices (id (PK))
Links (id (PK), device_id_1 (FK), device_id_2 (FK))

Which represents devices connected by links.

I need to select all devices connected with a given one (which can be device_id_1 or device_id_2). I tried to do it with the following query:

select d2.*
from Devices as d1
left outer join Links as l on d1.id in (l.device_id_1, l.device_id_2)
left outer join Devices as d2 on d2.id in (l.device_id_1, l.device_id_2)
where d1.id = 398 and d2.id <> 398;

But as soon as I added second JOIN the query returns zero rows. What am I doing wrong?

like image 614
mike_grinin Avatar asked Aug 29 '16 12:08

mike_grinin


People also ask

Can we use join IN WITH clause in SQL?

SQL handles queries across more than one table through the use of JOINs. JOINs are clauses in SQL statements that link two tables together, usually based on the keys that define the relationship between those two tables.

Can you join with or condition?

If you have an OR condition in the JOIN - and there is no possibility that the values in the OR statement overlap...then you can convert it to a UNION ALL. If the values overlap it would require a UNION which may not improve performance over the JOIN.

How do I do a conditional join in SQL?

A conditional column join is a fancy way to let us join to a single column and to two (or more) columns in a single query. We can accomplish this by using a case statement in the on clause of our join. A case statement allows us to test multiple conditions (like an if/else if/else) to produce a single value.

Can we add a condition on join?

It is better to add the condition in the Join. Performance is more important than readability. For large datasets, it matters. Do you have some kind of proof, research how the placement of the mentioned predicates affects performance?


1 Answers

The where clause was effectively making your last left join an inner join.

To correct move the left join filter criteria to the join criteria

select d2.*
from Devices as d1
left outer join Links as l on d1.id in (l.device_id_1, l.device_id_2)
left outer join Devices as d2 on d2.id in (l.device_id_1, l.device_id_2)
and d2.id <> 398
where d1.id = 398;

A much less elegant although generally accepted approach would be...

select d2.*
from Devices as d1
left outer join Links as l on d1.id in (l.device_id_1, l.device_id_2)
left outer join Devices as d2 on d2.id in (l.device_id_1, l.device_id_2)
where d1.id = 398 
  and (d2.id <> 398 OR D2.ID is null)

I generally think of it this way..

When using outer joins I typically want to exclude the rows before the join occurs so the engine doesn't have to generate such a large Cartesian. In addition on outer joins, null records I want returned. However, if I apply the limit in the where clause, all the null records generated from the outer join will be removed unless I account for NULLS as well.

In this case since you're using a <>... <> can't compare to null thus it will exclude desired records as you can't use a equality check on a null value.

1 = NULL returns NULL and 1 <> NULL returns NULL; thus not true

like image 193
xQbert Avatar answered Oct 16 '22 14:10

xQbert