0

I have the following query:

SELECT s.id, s.service_measure from service s, user_service_hist ush 
WHERE s.id <> ush.service_id 

My s table looks as follows:

id | service_measure
--------------------
7  | a
8  | b
9  | c

My ush table looks as follws:

id | service_id
--------------------
1  | 7
2  | 8

When I run the above query I expect and want the result to be

id | service_measure
--------------------
9  | c

Instead my result is:

id | service_measure
--------------------
8  | b
9  | c
7  | a
9  | c

It looks like it is running the query 2 times. Once for each row of the ush table. Can anyone tell me why this occurs?

Stefan
  • 301
  • 3
  • 14
  • 2
    You're getting the cross-product of two tables here. If you wanted to limit how the tables are linked together, set a `JOIN` condition. – tadman Nov 07 '16 at 19:38
  • You should remove ", user_service_hist ush " for get the result you expect – ScaisEdge Nov 07 '16 at 19:40
  • All I want is for the output to be anything BUT the the id's in the ush table. How do I achieve this. I did not set foreign keys in the ush table. – Stefan Nov 07 '16 at 19:40
  • Use `not in` or a left join. `select * from s where id not in (select service_id from ush)` – shawnt00 Nov 07 '16 at 19:41

4 Answers4

3

Lets break down your query:

SELECT s.id, s.service_measure 
FROM service s, user_service_hist ush 
WHERE s.id <> ush.service_id 

First the engine does the cross join you've requested by using the , notation in the from clause.

So a,b,c is joined to 1,2 resulting in

1. 7 a
2. 8 b
3. 9 c
4. 7 a
5. 8 b
6. 9 c

A cross join joins every record in a table to every record in another table. these are terribly slow and are seldom are needed. Think about it you have to first take every record in table 1 and pair it with every record from table 2. A join only has to match On like values negating all the work in generating a Cartesian that a cross join does. (I use them Rarely in the work I do; usually when I need to generate test data)

Then the where clause executes which eliminates those where the S.ID and ush.serivce_ID match.

This eliminates lines 1 and 5 above which is why you get only 4 records.

To get your expected results you would need to either to use a not exists or a an outer join instead of a cross join

SELECT s.id, s.service_measure 
FROM service s
LEFT JOIN user_service_hist ush 
 on S.ID = USH.Service_ID
WHERE ush.Service_ID is null

or

SELECT s.id, s.service_measure 
FROM service s
WHERE not exists (Select 1 
                  from user_Service ush 
                  where ush.Service_ID = s.ID)

or (since you seem to be learning about joins )

SELECT s.id, s.service_measure 
FROM user_service_hist ush 
RIGHT JOIN service s
 on S.ID = USH.Service_ID
WHERE ush.Service_ID is null

(notice all we did there was change the order of the joins and the word left to right) right join says include all records from the table on the right and only those matching to the right from the left table. (most people just re-arrange the tables so they are all left joins)

I suppose if the ush table is small an in would work but as the table isn't a set size, increases in records to this table would cause in to degrade in performance over time. The not exists should be the most efficient long term with proper indexes, though the left join offers the ability to get additional data off the ush table if needed; but at a slight hit to performance.

xQbert
  • 34,733
  • 2
  • 41
  • 62
2

You need a left excluding join! A left excluding join happens when you want all elements from table A where they do not exist on table B. Take a look in the picture!

SELECT 
    s.id, s.service_measure
FROM service s 
    LEFT JOIN user_service_hist ush 
        on ush.service_id = s.id 
WHERE ush.service_id = NULL

SQL Joins

Bruno
  • 2,889
  • 1
  • 18
  • 25
  • 1
    That's a good one, but I prefer this: https://blog.codinghorror.com/a-visual-explanation-of-sql-joins/ – xQbert Nov 07 '16 at 19:48
  • Awesome answers guys! Thank you. This is really helpful and already printed out and hanging over my desk. the query that worked was: `SELECT s.id, s.service_measure FROM service s LEFT JOIN user_service_hist ush ON s.id = ush.service_id WHERE ush.id IS NULL ` – Stefan Nov 07 '16 at 20:02
  • What is the difference between a JOIN and an OUTER JOIN? They both seem to work. – Stefan Nov 07 '16 at 20:03
  • INNER JOIN = JOIN. OUTER JOIN must be (LEFT, RIGHT or FULL) Also if you need NO data back from the ush table, `exists` would be slightly faster. – xQbert Nov 07 '16 at 20:04
1

Your query performs a cartesian product between the service and the user_service_history tables (that's the from part of the query). The result of this cartesian product is this intermediate table :

s.id | s.service_measure | ush.id | ush.service_id
--------------------------------------------------
7    | a                 | 1      | 7
7    | a                 | 2      | 8
8    | b                 | 1      | 7
8    | b                 | 2      | 8
9    | c                 | 1      | 7
9    | c                 | 2      | 8

This intermediary table is then filtered out by the restriction (where clause) s.id <> ush.service_id, which leaves :

s.id | s.service_measure | ush.id | ush.service_id
--------------------------------------------------
7    | a                 | 2      | 8
8    | b                 | 1      | 7
9    | c                 | 1      | 7
9    | c                 | 2      | 8

Then you keep the s.id and service_measure column and you get your results.

The query you want is (from my head) :

 select s.id, s.service_measure 
 from service s
 where s.id not in (
   select ush.id 
   from user_service_history ush
 )
gregfqt
  • 242
  • 3
  • 14
  • 1
    Good explanation! While not in would work; in the long run assuming the ush table can grow in size, the not in will degrade in performance in the long run. Exists would be the most efficient and a left join 2nd most and offers the ability to get data from 2nd table that an exist and not in are unable to do. – xQbert Nov 07 '16 at 20:09
  • @xQbert I agree with you. I chose `not in` over `not exists` because it seemed easier to understand. – gregfqt Nov 07 '16 at 20:16
0

Try this:

SELECT s.id, s.service_measure 
FROM service s
LEFT JOIN user_service_hist ush ON s.id = ush.service_id
WHERE ush.service_id IS NULL