views:

158

answers:

6

Hi, I wrote a query

select u.user, g.group, u2g.something 
from   users, groups, u2g 
where  users.u = u2g.u and groups.g = u2g.g

that returns data like this:

user, group, something
----------------------
1   , 3,     a
1   , 5,     b
2   , 3,     c
3   , 3,     d
4   , 5,     e

now I would like to limit this query in such a way that it only shows users which are both in groups 3 and 5 - so it would only return {1,3, a} , {1,5, b} for my example data.

edit: I added another column to the data because there may be an incorrect solution using a group by.

edit2: sorry, I was misled by documentation. MySQL 4.0 does not support subqueries :(

edit3: This SQL will be generated programatically for any number of groups (well, up to 20 in current specification) so I would like to avoid solutions that give me too much additional coding to do. If a solution will not be found, I will just modify the resulting .Net 1.1 DataTable, but I would like to avoid that if possible.

edit4: any new idea? Perhaps one without subqueries that includes IN (3,5)?

A: 

Its not and answer to your question but why don't you use Joins?

Megacan
I mostly use = (95%) (which i think is the same as an inner join) and sometimes a left/right join (5%), so I'm a bit rusty there.
Spikolynn
The effect is the same as a join, but I find that joins are more readable. With joins the where clause is more clean.
Megacan
Now that I see examples here I see it is somewhat more readable, yes.
Spikolynn
+1  A: 
select u.user, g.group, u2g.something 
from users u, groups g, u2g 
where u.user = u2g.user and g.group = u2g.group 
    where exists 
     (select 1 
         from u2g u2g2 
        where u2g2.user=u.user and u2g2.group in(3,5))
pablito
I dont think thats what he's after. that covers the casae where it's 3 adn 5 but not the general case.
Omar Kooheji
i could generalize it, but sadly i think my db does not support EXISTS
Spikolynn
+1  A: 

Something along these lines?

select u.[user], g.group
from     u
    inner join ug on ug.userid = u.id 
    inner join g on g.id = ug.groupid
    inner join 
    (
     select ug.userid
     from ug
     where ug.groupid in (1,2)
     group by ug.userid
     having count(*) = 2
    ) sub on sub.userid = u.id

-Edoode

edosoft
sorry, mysql 4.0 actually does NOT support subqueries as I thought, but +1 for effort :)
Spikolynn
+1  A: 

Using double join with groups-table will give you the correct result:

  select u.user, u2g.something 
  from   users 
  INNER JOIN u2g ON users.u = u2g.u
  INNER JOIN groups g1 ON u2g.g = g1.g AND g1.group = 3
  INNER JOIN groups g2 ON u2g.g = g2.g AND g2.group = 5
  /* try this for two rows, one for each group */
  INNER JOIN groups ON u2g.g = groups.g

However, this doesn't exactly match your request, that you want two rows, one for each group. This will only give you one row, you might be able to join it once more with groups therefor rendering two rows.

Another example (if you're selecting using the same groupID that you map against ):

SELECT u.uID, gm.something 
FROM cdcms_users u 
inner join cdcms_group_memberships gm1 on gm1.uID = u.uID AND gm1.gID = 32
inner join cdcms_group_memberships gm2 on gm2.uID = u.uID AND gm2.gID = 33
jishi
nope. returns no results even without the two rows fix.SELECT u.uID, gm.something FROM cdcms_users uinner join cdcms_group_memberships gm on (gm.uID = u.uID)inner join cdcms_groups g1 on (g1.gID = gm.gID) and (g1.gID = 32)inner join cdcms_groups g2 on (g2.gID = gm.gID) and (g2.gID = 33)
Spikolynn
Could you try it without the last two joins? So just check if it works correctly when searching for users in group 3 without any checking for group 5? I look at this and think that it should surely be the correct approach at least.
Ray Hidayat
Yes, it returns results then. But with the additional joins it does not.
Spikolynn
Just to be sure... you do have 2 rows for that user in your cdcms_group_memberships table, right? If it's the same ID you will be using in u2g and groups, you don't need to join in groups. I edited the post with another approach then
jishi
your second solution works: SELECT u.uID, u.uEmail, g.gName, gm1.dateStart, gm1.dateEndFROM cdcms_users u inner join cdcms_groups g on gm1.gID = g.gID OR gm2.gID = g.gIDinner join u2g gm1 on gm1.uID = u.uID AND gm1.gID = 32inner join u2g gm2 on gm2.uID = u.uID AND gm2.gID = 33Thanks!
Spikolynn
+1  A: 

Quite hideous non-general solution that results in two rows in Oracle:

  select users.u, groups.g
    from   users , groups, u2g, groups g2, u2g u2g2
    where  users.u = u2g.u 
           and users.u = u2g2.u
           and groups.g = u2g.g
           and g2.g = u2g2.g
           and (groups.g in (3,5) and g2.g in (3,5) and groups.g <> g2.g)
           ;
Aleksi
yup, this one finally works, thanks :-)I'll wait a bit more since it's quite hard to make it work for X groups with my query generator
Spikolynn
+1  A: 

Why is groups used in the query? Its only accessed field (g) exists in u2g. I imagine you probably want to bring back a boatload of stuff from there as well.

In order to get the sort of resultset you describe without the use of subqueries you end up with a real mess: a quadratic explosion of query text!

You'll need something of the following form:

select users.u, groups.g, u2g0.something
from users u, groups g, u2g u2g0, u2g u2g1
where groups.g = 3
     and users.u = u2g0.u
     and u2g0.g = 3
     and users.u = u2g1.u
     and u2g1.g = 5
union all
select users.u, groups.g, u2g1.something
from users u, groups g, u2g u2g0, u2g u2g1
where groups.g = 5
     and users.u = u2g0.u
     and u2g0.g = 3
     and users.u = u2g1.u
     and u2g1.g = 5

Since this is a programmatically generated query, I'll use a web-page-scripting-like notation here to describe the query construction. I will also make the rash and unwarranted simplifying assumption that the group identifiers are not a potential SQL-injection attack vector. :-)

<% for(int i = 0; i < requiredGroups.Length; i++) { %>
  <% if(i > 0) { %>
    union all
  <% } %>
select users.u, groups.g, u2g<%=i%>.something
from users u, groups g
  <% for(int j = 0; j < requiredGroups.Length; j++) { %>
     , u2g u2g<%=j%>
  <% } %>
where groups.g = <%=requiredGroups[i]%>
  <% for(int j = 0; j < requiredGroups.Length; j++) { %>
     and users.u = u2g<%=j%>.u
     and u2g<%=j>.g = <%=requiredGroups[j]%>
  <% } %>
<% } %>
Jeffrey Hantin
man, thanks for your time :) I better close the question before I waste any other good programmer's time, and modify my approach...
Spikolynn