tags:

views:

57

answers:

5

Hi everybody,

I have spent several hours with this SQL problem, which I thought would be easy - I still think it should be, but obviously not easy enough for me (not an SQL expert). I would be glad if you could help me with this.

I have stripped this down for this example. Imagine two tables:

PRODUCT
 -ID
 -NAME

REVIEW
 -ID
 -PRODUCT_ID
 -USER_ID
 -CONTENT

Where each user can review a product only once.

What I need now is an SQL query that returns all products together with the reviews of a given user. If a user has not reviewed a product, there should be NULL values returned for the review columns.

I thought something like the following would do, but obviously it does not, since only reviewed products are returned.

SELECT p.*, r.* 
from PRODUCT p join REVIEW r 
  on r.PRODUCT_ID = p.ID 
where r.USER_ID=:userId

I would be veeeeery happy if someone could help me out with this. I am pretty sure there has to be some kind of subselect involved, but I do not seem to be able to figure it out myself :(

BTW: I need to translate this to HQL, so the solution should not make use of features like UNION, which are not supported in HQL.

Thanks a lot & best regards, Peter

+3  A: 
SELECT p.*, r.* 
 from PRODUCT p LEFT JOIN REVIEW r 
  on r.PRODUCT_ID = p.ID 
 where r.USER_ID=:userId
Michael Pakhantsov
No, this does not make a difference, at least not on Oracle. AFAIK, join is left per default. As you can see, I have just figured out the solution, see my answer... Thanks for the reply, anyway :)
PeterP
@Peter - I'm pretty certain that is not the case. Are you absolutely certain the keyword `left` is nowhere to be seen in your query?
Martin Smith
Thanks for the hint, see my comment on my answer. Still, this solution does not work when having the user restriction in the WHERE clause. But thanks a lot for all your input on this :)
PeterP
+1  A: 

If you want to select products where a no users have created a review:

SELECT p.*, r.* from PRODUCT p join REVIEW r on r.PRODUCT_ID = p.ID
WHERE r.USER_ID IS NULL

If you want to select products where a specific user hasn't created a review:

SELECT p.*, r.* from PRODUCT p join REVIEW r on r.PRODUCT_ID = p.ID
WHERE r.USER_ID IS NULL or r.USER_ID!=:userId
nessence
Well, I cannot just drop a part of the query, since I need to restrict on a user.... Pls see my answer, which I have finally found out. Thanks anyway :)
PeterP
Hm, but this one does not return the products which have been only reviewed by the given user, no?
PeterP
I misread -- I generated a query pull all the products which the user has yet to review. Your posted answer below is correct.
nessence
+2  A: 

Ah, just after posting this, I have finally found the answer on another site. (I have searched before, I swear ;-)

The trick is not to have the userId restriction in a WHERE clause, but rather as part of the JOIN:

SELECT p.*, r.* 
from PRODUCT p LEFT JOIN REVIEW r 
  on r.PRODUCT_ID = p.ID AND r.USER_ID=:userId

Thanks for your thoughts!

(edited: also have to use a LEFT join, as pointed out by Martin Smith, thanks!)

PeterP
@Peter - Unless you are using an `outer` join this change will make zero difference.
Martin Smith
It does, at least in Oracle SQL, for me... but thanks for the hint, HQL might still be interpreting this differently
PeterP
@Peter: You can make your query clearer by explicitly stating whether the join is `inner` or `left outer`.
kbrimington
Argh... you got me. I am very sorry. Obviously I have confused different statements in my SQL developer window, I have been experimenting with. Thanks for pointing this out, I have fixed the statement with the missing LEFT.
PeterP
BOO YA! See, told you to drop the where ;)That said, depending on the RDBMS, you may need to put the condition (with regard to NULL) in the join and/or WHERE. Some MySQL versions will run faster w/added WHERE because it will exclude non-match rows on the first pass whereas with JOIN parameters, the reduction won't be performed until ALL rows are joined.
nessence
+1  A: 

To get the effect you described, you should use a left outer join, but constrain your right-hand side by either a matching user ID or null.

SELECT p.*, r.* 
 from PRODUCT p LEFT OUTER JOIN REVIEW r 
  on r.PRODUCT_ID = p.ID 
 where r.USER_ID=:userId or r.USER_ID is null
kbrimington
A: 

Another aproach using subqueries

SELECT P., R. FROM PRODUCT P LEFT JOIN (SELECT * FROM REVIEW WHERE UserId=:userId) UR ON UR.ProductId=P.ID

VdesmedT