10
+-------------------+   +-------------------+   +---------------------+
|      Service      |   |       Asset       |   |     AssetService    |
+-------------------+   +-------------------+   +---------------------+
| Id   |    Name    |   | Id   |    Name    |   | AssetId | ServiceId |
|-------------------|   |-------------------|   |---------------------|
| 1    |  Service 1 |   | 1    |   Asset 1  |   |     1   |     1     |
| 2    |  Service 2 |   | 2    |   Asset 2  |   |     1   |     2     |
| 3    |  Service 3 |   | 3    |   Asset 3  |   |     2   |     2     |
+-------------------+   +-------------------+   |     2   |     3     |
                                                +---------------------+

So I have these tables. I want to get the Services that is not on AssetService where AssetId = 1 Like this:

+-------------------+
|      Service      |
| Id   |    Name    |
+-------------------+
| 3    |  Service 3 |
+-------------------+

Is this possible with just inner/left/right join? because I already tried different combinations of inner join but it's not working, like this inner join Asset a on a.Id != as.AssetId. I event tried left and right join.

Can somebody help me?

Thanks.

akoxi
  • 225
  • 1
  • 3
  • 10
  • 2
    This isn't possible to do with just inner joins. If a Service has no rows in AssetService then the inner join will remove it from the result but it must be returned. – Martin Smith Feb 06 '18 at 15:52

3 Answers3

30

You can you use an intelligent left join to return non-matching rows only from left table(Service)

SELECT S.Id, S.Name FROM [Service] S
LEFT JOIN ServiceAsset SA
ON S.Id = SA.ServiceId
WHERE SA.ServiceId IS NULL

enter image description here

Note: INNER JOIN returns the matching rows whereas you want the non matching rows then use LEFT JOIN instead

Elham Kohestani
  • 3,013
  • 3
  • 20
  • 29
10

The simplest I can think of:

select * from Service
where Id not in (
    select ServiceId
    from AssetService 
    where AssetId = 1);

SQLFiddle link

I don't think it's possible using inner join, because that would only retrieve records that match some criteria and you are looking for records that do not match.

It is, however, possible to do it with left join as Ctznkane525 shows in his answer.

Edit

As jarlh pointed out in the comments, not in might lead to surprising results when there are nulls in the subquery. So, here is the not exists version:

select Id, Name
from Service s
where not exists (
    select *
    from AssetService a
    where AssetId = 1
    and ServiceId = s.Id);

SQLFiddle link

Community
  • 1
  • 1
Cristian Lupascu
  • 39,078
  • 16
  • 100
  • 137
  • The NOT IN behavior surprise many people when null values are returned from the subquery. That's why I usually do NOT EXISTS instead. (I didn't downvote.) – jarlh Feb 06 '18 at 15:39
  • @jarlh That's a new one for me. I just realized I never used `not in` with data sets that contained nulls. So, thanks for teaching me something new today! :) – Cristian Lupascu Feb 06 '18 at 15:52
  • 1
    Change the "where not in" to "where not exists" as jarlh suggested an it will produce what generally will be a superior query plan to the 'outer join where something is null.' solution. Specifically, it will produce semi-antijoin, which has been shown generally to outperform outer joins. – user3444696 Feb 06 '18 at 21:28
3

Try this:

select * from Service where Id not in (
    select ServiceId from AssetService where AssetId = 1 
    -- we have to filter out NULLs, in case of NULL values query result will be empty
    and ServiceId not null
)

It doesn't require any join.

Here is solution with join:

select Id, Name from Service
except
select S.Id, S.Name from Service S join AssetService [AS] on S.Id = [AS].ServiceId
where [AS].AssetId = 1
Michał Turczyn
  • 32,028
  • 14
  • 47
  • 69