views:

138

answers:

4

Hi,

I have this sample table:

CREATE TABLE `dummy` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userId` int(11) NOT NULL,
  `pet` varchar(50) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;


INSERT INTO `dummy` (`id`, `userId`, `pet`) VALUES(1, 1, 'dog');
INSERT INTO `dummy` (`id`, `userId`, `pet`) VALUES(2, 1, 'cat');
INSERT INTO `dummy` (`id`, `userId`, `pet`) VALUES(3, 2, 'dog');
INSERT INTO `dummy` (`id`, `userId`, `pet`) VALUES(4, 2, 'cat');
INSERT INTO `dummy` (`id`, `userId`, `pet`) VALUES(5, 3, 'cat');
INSERT INTO `dummy` (`id`, `userId`, `pet`) VALUES(6, 4, 'dog');

How can I write the statements below in mysql:

  • Retrieve all users who own both a dog and a cat
  • Retrieve all users who own a dog or a cat
  • Retrieve all users who own only a cat
  • Retrieve all users who doesn't own a cat

EDIT: With the help of dbemerlin, I have solutions for first two statements. Here they are:

  • Retrieve all users who own both a dog and a cat:

     SELECT * FROM dummy WHERE pet = 'cat' OR pet = 'dog' GROUP BY userId HAVING COUNT(*) = 2
    
  • Retrieve all users who own a dog or a cat:

     SELECT * FROM dummy WHERE pet = 'cat' OR pet = 'dog' GROUP BY userId
    

I have found a solution for 3:

  • Retrieve all users who own only a cat:

    SELECT * FROM dummy WHERE userId IN (SELECT userId FROM dummy WHERE pet = 'cat' GROUP BY userId) GROUP BY userId HAVING COUNT(*) = 1
    

    But Adriano have a better solution:

    SELECT * FROM dummy WHERE pet = 'cat' AND userId NOT IN (SELECT userId FROM dummy WHERE pet != 'cat');
    

But still having problems for the last statement:

  • Retrieve all users who doesn't own a cat:

     SELECT * FROM dummy WHERE pet != 'cat' GROUP BY userId
    

    This doesn't work either. What I exactly need is that to retrieve all user who doesn't own a cat but may have other pets.

Thanks!

EDIT: This is not a homework assignment. I tried to simplify the question for asking here and also to isolate the problem. The real situation is I am trying to retrieve users who have click 2 different links (stored as url strings) and etc. And if this was a homework assignment, what's the wrong in asking how to achieve this here? If I had a friend of mine who had MySQL knowledge, what's the difference asking him to tell me the solution and explain than asking here?

+3  A: 

Sounds like a homework assignment..what do you have so far? We can help from there.

Jeff Storey
Hi Jeff, well it seems easy but I couldn't came up with anything.
matte
Well, you should be able to come up with something, I would assume there's a textbook associated with the class, an instructor, or google mysql select and joins. If you expect to learn it, you may want to try to figure it out instead of getting all the answers from people.
gmcalab
By the way, this may seem like a homework but it is not. As I have said in the comment above, this is a simplified table structure of a more mixed problem.
matte
You could still google mysql select and joins instead of asking someone to write the entire query for you.
gmcalab
gmcalab, yes you are right. I can google, but wait. Look at the search results for any mysql related search, you will see stack overflow. Still I don't understand what's wrong with the question. If I had put my own not working sql query into the question, would it be ok?
matte
Yes, that's a good start. Put what you did try and we will help to show you why it doesn't work.
Jeff Storey
+1  A: 

1:

SELECT u.user_id
FROM   user u
INNER JOIN dummy d
USING (user_id)
WHERE d.pet = 'cat'
OR d.pet = 'dog'
GROUP BY user_id
HAVING COUNT(*) = 2

2:

SELECT u.user_id
FROM   user u
INNER JOIN dummy d
USING (user_id)
WHERE d.pet = 'cat'
OR d.pet = 'dog'
GROUP BY user_id

3:

SELECT u.user_id
FROM   user u
INNER JOIN dummy d
USING (user_id)
WHERE d.pet = 'cat'

4:

SELECT u.user_id
FROM   user u
INNER JOIN dummy d
USING (user_id)
WHERE d.pet = 'dog'
dbemerlin
Is there a way to do this without using INNER JOIN? I want to use only one table
matte
Maybe something like: SELECT * FROM dummy WHERE pet = 'cat' OR pet = 'dog' GROUP BY userId HAVING COUNT(*) = 2. But that didn't work.
matte
dbemerlin, ignore my last comment here. Actually it did work! :)
matte
dbemerlin, first two work but both last two don't work. There is a misunderstanding for the last two. 3: User must have only cat, no other pets (In your solution, it retrieves also users having dog and cat). 4: User must not have cat but may have other pets (There may be more than two pets in system)
matte
@matte The "group by" query with only one table will return all users that have both a dog and cat. It will also return users with two dogs. It will also return users with two cats. Is there a unique constraint that you haven't disclosed?
+1  A: 

One solution to your latter problems could be this:

SELECT * 
FROM dummy 
WHERE pet = 'cat' 
AND userId NOT IN (
    SELECT userId 
    FROM dummy 
    WHERE pet != 'cat'
);

for users who only have cats.

This lets you use a single variable to represent the type of pet you want selected.

The result here, with the data you posted:

mysql> select * from dummy where pet = 'cat' and userId not in \
    -> (select userId from dummy where pet != 'cat');
+----+--------+-----+
| id | userId | pet |
+----+--------+-----+
|  5 |      3 | cat |
+----+--------+-----+
1 row in set (0.00 sec)

EDIT: For your last problem, you just reverse the = and != in the selects. Do try to think about it for a second before asking.

EDIT: You want to know about performance. One tool offered by MySQL is EXPLAIN. Prefixing your query with the keyword EXPLAIN will give you an analysis of its performance, possible pathway of execution, keys and indexes involved, etc. In this case:

mysql> explain select * from dummy where pet = 'cat' and userId not in (select userId from dummy where pet != 'cat');
+----+--------------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type        | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+--------------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | PRIMARY            | dummy | ALL  | NULL          | NULL | NULL    | NULL |    6 | Using where |
|  2 | DEPENDENT SUBQUERY | dummy | ALL  | NULL          | NULL | NULL    | NULL |    6 | Using where |
+----+--------------------+-------+------+---------------+------+---------+------+------+-------------+
2 rows in set (0.00 sec)

mysql> explain SELECT * FROM dummy WHERE userId IN (SELECT userId FROM dummy WHERE pet = 'cat' GROUP BY userId) GROUP BY userId HAVING COUNT(*) = 1;
+----+--------------------+-------+------+---------------+------+---------+------+------+----------------------------------------------+
| id | select_type        | table | type | possible_keys | key  | key_len | ref  | rows | Extra                                        |
+----+--------------------+-------+------+---------------+------+---------+------+------+----------------------------------------------+
|  1 | PRIMARY            | dummy | ALL  | NULL          | NULL | NULL    | NULL |    6 | Using where; Using temporary; Using filesort |
|  2 | DEPENDENT SUBQUERY | dummy | ALL  | NULL          | NULL | NULL    | NULL |    6 | Using where; Using temporary; Using filesort |
+----+--------------------+-------+------+---------------+------+---------+------+------+----------------------------------------------+
2 rows in set (0.00 sec)

You'll notice that your query adds a "using temporary, using filesort" to the 'extra' column. That, in brief, means it is less efficient, because a temporary table must be created, and sorting must occur for your result to be calculated. You can read this manpage to know more.

Adriano Varoli Piazza
Hi Adriano, thank you for your question. It is working perfectly! But I have came up with a solution too: SELECT * FROM dummy WHERE userId IN (SELECT userId FROM dummy WHERE pet = 'cat' GROUP BY userId) GROUP BY userId HAVING COUNT(*) = 1. What's your opinion about my solution (Performance wise)?
matte
A: 

There are several solutions to the first problem, "users that have at least one dog and one cat."

  select * 
    from dummy
    where userId in (select userId from dummy where pet = 'dog')
     and  userId in (select userId from dummy where pet = 'cat');

Using a correlated sub query:

    select * 
    from dummy
    where exists (select 1 from dummy p where p.userId = d.userId and pet = 'dog')
     and  exists (select 1 from dummy p where p.userId = d.userId and pet = 'cat')

Performance depends on the optimizer. It's possible both have the same execution plan.

  select d.* 
    from dummy d
    join (select distinct userId, pet from dummy where pet = 'dog') as g
    using (userId)
    join (select distinct userId, pet from dummy where pet = 'cat') as c
    using (userId);

The last one uses what Oracle calls an "inline view." Other database vendors have a different name. If these queries don't work in mySql, let me know.