tags:

views:

45

answers:

2

I have two queries who's results should be mutually exclusive, but they are not.

This one properly finds all accounts that have an email with a matching name and order_id:

SELECT * FROM `accounts` 
LEFT OUTER JOIN `emails` ON emails.account_id = accounts.id 
WHERE (emails.type_name = 'name' AND emails.order_id = 1)

This one should should find all accounts that don't have an email with a matching name and order_id, or don't have an email at all:

SELECT * FROM `accounts` 
LEFT OUTER JOIN `emails` ON emails.account_id = accounts.id 
WHERE (!(emails.type_name = 'name' AND emails.order_id = 1) OR emails.id IS NULL)

However, this latter query is returning accounts that have a matching email if they also have a non-matching email, thus it's returning accounts from the first query. Any help would be greatly appreciated.

+1  A: 

Consider the following test case:

CREATE TABLE accounts (id int);
CREATE TABLE emails (id int, account_id int, type_name varchar(10), order_id int);

INSERT INTO accounts VALUES (1), (2), (3), (4);

INSERT INTO emails VALUES (1, 1, 'name', 1);
INSERT INTO emails VALUES (2, 1, 'no-name', 1);
INSERT INTO emails VALUES (3, 2, 'name', 1);
INSERT INTO emails VALUES (4, 2, 'no-name', 1);
INSERT INTO emails VALUES (5, 3, 'name', 2);

Then this works as expected:

SELECT * FROM `accounts` 
LEFT OUTER JOIN `emails` ON emails.account_id = accounts.id 
WHERE (emails.type_name = 'name' AND emails.order_id = 1);

+------+------+------------+-----------+----------+
| id   | id   | account_id | type_name | order_id |
+------+------+------------+-----------+----------+
|    1 |    1 |          1 | name      |        1 |
|    2 |    3 |          2 | name      |        1 |
+------+------+------------+-----------+----------+
2 rows in set (0.00 sec)

The problem with your second query is that it can return a NULL row if there is an account with no email, as is the case of account number 4:

SELECT * FROM `accounts` 
LEFT OUTER JOIN `emails` ON emails.account_id = accounts.id 
WHERE (!(emails.type_name = 'name' AND emails.order_id = 1) OR emails.id IS NULL);

+------+------+------------+-----------+----------+
| id   | id   | account_id | type_name | order_id |
+------+------+------------+-----------+----------+
|    1 |    2 |          1 | no-name   |        1 |
|    2 |    4 |          2 | no-name   |        1 |
|    3 |    5 |          3 | name      |        2 |
|    4 | NULL |       NULL | NULL      |     NULL |
+------+------+------------+-----------+----------+
4 rows in set (0.01 sec)

Why wouldn't this be enough for a mutually exclusive result set with no NULL rows?:

SELECT * FROM `accounts` 
LEFT OUTER JOIN `emails` ON emails.account_id = accounts.id 
WHERE NOT (emails.type_name = 'name' AND emails.order_id = 1)

+------+------+------------+-----------+----------+
| id   | id   | account_id | type_name | order_id |
+------+------+------------+-----------+----------+
|    1 |    2 |          1 | no-name   |        1 |
|    2 |    4 |          2 | no-name   |        1 |
|    3 |    5 |          3 | name      |        2 |
+------+------+------------+-----------+----------+
3 rows in set (0.00 sec)
Daniel Vassallo
Thanks for the detailed response! For the second query, I'm looking for accounts that do not have a certain kind of email record (type_name = 'name' AND order_id = 1). In your example, accounts 1 and 2 have this kind of email record, so I don't want those returned. Account 3 has an email record that doesn't match, so I want that returned. Account 4 has no email records, so I want that returned because it can't match. To summarize: My query currently returns accounts 1, 2, 3 and 4. I only want it to return 3 and 4.
tassock
A: 

If your target is to match a pair of (type_name,order_id) then it should work -

SELECT * FROM `accounts` 
LEFT OUTER JOIN `emails` ON emails.account_id = accounts.id 
WHERE (emails.type_name != 'name' OR emails.order_id != 1)
Sadat
Thanks for your response. Like my second original query though, this returns accounts that have a matching email record if they also have a non-matching email record. For this second query, I don't want accounts if they have a matching email record.
tassock