tags:

views:

354

answers:

4

I've always done this back asswards in PHP or ASAP, so I figure it's time to actually learn the proper way to do it in SQL. I have the following 4 tables in a database:

Category (Fields: CategoryNumber, Desc) (small table with 15 rows)

Media (Fields: MediaID, Desc, CategoryNumber, etc) (huge table with 15,000 rows)

Sales (Fields: Date, MediaID, EmployeeID etc) (huge table with 100,000 rows)

Employees (Fields: EmployeeID, Name, etc) (small table with only 20 rows)

Category only links to Media Media has links to both Category and Sales. Sales links to both the Media and Employee Employee only links to Sales

What I would like to do is to write a query which tells me what categories a given employee has never sold any media in.

I can write a simple query that looks for unmatched data between 2 tables, but I have no clue how to do it when I'm dealing with 4 tables.

Thanks for your time and help!

+2  A: 
SELECT c.CategoryNumber, c.Desc
FROM   Category c
WHERE NOT EXISTS
(
SELECT *
FROM       Employees e
INNER JOIN Sales     s on s.EmployeeID = e.EmployeeID
INNER JOIN Media     m on m.MediaID    = s.MediaID
WHERE e.Name = "Ryan"
AND   m.CategoryNumber = c.CategoryNumber
)

MS Access evidently needs a lot of parentheses (thanks, Ryan!):

select * 
from Category c 
where not exists 
( select * 
from (     Employee e 
inner join Sales    s on (s.EmployeeId = e.EmployeeId)) 
inner join Media    m on (m.MediaID    = s.MediaID) 
where (e.Name = 'Ryan' and m.CategoryNumber = c.CategoryNumber) )
Carl Manaster
Shouldn't those be single quotes, though?
Mike Burton
Lol, pretty much identical answer... +1 for being faster :) haha
Andomar
that's fine, but this just hard codes the result to Ryan. I think the OP wants a result set showing all the Employees and what categories they are not selling.
northpole
WOw, you guys are super fast. Working on it right now.
Ryan
Having problems implementing the suggestions here, which I'm sure is related to the fact I'm forced to use MS Access as the db in this problem (using mySQL on all new stuff). I've spent about 2 hours trying to make it work, to no avail. I believe the problem is that Access needs to have parentheses around the joins? Basically I'm trying to make the inner Select work first, and it is not working. Any addtional suggestions would be greatly apprecaited.
Ryan
I added the ms-access tag in hopes that it will attract some attention from someone with knowledge of its syntax; sorry, I don't.
Carl Manaster
Did some more digging around and found the solution using proper Access formating to your solution:select *from Category cwhere not exists ( select * from (Employee e inner join Sales s on (s.EmployeeId = e.EmployeeId)) inner join Media m on (m.MediaID = s.MediaID) where (e.Name = 'Ryan' and m.CategoryNumber = c.CategoryNumber))
Ryan
Updated with your code, so people have a formatted version to look at. Thanks.
Carl Manaster
Subqueries with NOT are not reliably optimized in Jet. Certain NOT IN and NOT EXISTS operations will fail to use the index on one side or the other of the operation and can be very slow. Where you can use an outer join, you'll get more reliable performance.
David-W-Fenton
I've read that NOT IN is to be avoided but not NOT EXISTS. It's well optimized in SQL Server and the fact it was the SQL Server team that wrote Jet 4.0 which introduced NOT EXISTS may not be a coincidence. That said, maybe the lack of knowledge on the subject can be attributed to the lack of documentation of the engine since Jet 3.0 and even the Jet 3.n documentation is being removed from the MS website. And I'm still looking for some documentation on ACE/Jet's CHECK constraints... :(
onedaywhen
+3  A: 

Here's my suggestion:

select *
from Category c
where not exists (
    select *
    from Employee e
    inner join Sales s on s.EmployeeId = e.EmployeeId
    inner join Media m on m.MediaID = s.MediaID
    where e.Name = 'Ryan' and m.CategoryNumber = c.CategoryNumber
)

To query all employes with the categories in which they didn't sell anything:

select e.EmployeeName, c.CategoryNumber
from Category c
cross join Employee e
where not exists (
    select *
    from Sales s
    inner join Media m on m.MediaID = s.MediaID
    where c.categoryNumber = m.CategoryNumber
    and s.EmployeeId = e.EmployeeId
)
Andomar
The inner query should be inner joins, as in Carl's case. Or you could filter for NULLs.
Mike Burton
@Mike Burton: Good suggestion, changed it. Though it didn't matter since "m.CategoryNumber = c.CategoryNumber" effectively enforces the inner join.
Andomar
+1 for cross join!
northpole
Ugh, both of you guys provided great help at the same time, is there any way to select both answers as correct?
Ryan
@Ryan: You can only accept one answer, and Carl Manaster was first. You can vote all answers up that you think are useful.
Andomar
+1  A: 
select c.desc
from category
left outer join (select s.employeeid,m.categorynumber
      from sales s 
      inner join media m on s.mediaid=m.mediaid
      inner join employee e on e.employeeid=s.employeeid
      where e.name = 'JOE'
      group by employeeid,categorynumber) t on t.categorynumber=c.categorynumber
where s.employeeid is null
brendan
A: 
Ryan