Reputation: 33
If I have a table t1 that looks this where records are unique on a combination of item, size and date;
item size date
1234 S 2013-02-11
1234 M 2013-02-11
1234 L 2013-02-11
9999 S 2013-02-11
9999 M 2013-02-11
9999 L 2013-02-11
1234 S 2013-02-13
1234 M 2013-02-13
1234 L 2013-02-13
9999 S 2013-02-13
9999 M 2013-02-13
How can I retrieve the item and size that is missing from 2013-02-13 when compared to 2013-02-11?
The return I am looking for is,
item size
9999 L
I have tried;
SELECT ta.item,
ta.size
FROM t1 ta
LEFT JOIN t1 tb
ON ta.item = tb.item
AND ta.size = tb.size
WHERE ta.date = '2013-02-11'
AND tb.date = '2013-02-13'
AND tb.size IS NULL
and
SELECT item, size
FROM t1
WHERE t1.date = '2013-02-11'
AND NOT EXISTS (
SELECT item, size
FROM t1
WHERE t1.date = '2013-02-13'
)
both return empty sets.
The only way I have managed to get this to work is by using temporary tables;
CREATE temporary table temp1
SELECT * FROM t1 WHERE date = '2013-02-11';
CREATE temporary table temp2
SELECT * FROM t1 WHERE date = '2013-02-13';
SELECT temp1.item, temp1.size FROM temp1
LEFT JOIN temp2
ON temp1.item = temp2.item AND temp1.size = temp2.size
WHERE temp2.size IS NULL
Temporary tables are not going to be possible. How can I achieve this without using temp tables?
Many thanks,
(please be gentle, i am just starting out!)
Upvotes: 3
Views: 866
Reputation: 77657
Others have provided valid solutions for you, and this post merely addresses your failed attempts, explaining what was missing and how it could be fixed.
This query
SELECT ta.item,
ta.size
FROM t1 ta
LEFT JOIN t1 tb
ON ta.item = tb.item
AND ta.size = tb.size
WHERE ta.date = '2013-02-11'
AND tb.date = '2013-02-13'
AND tb.size IS NULL
fails because there's a contradiction in the WHERE clause. The query is trying to find a row where tb.size
is NULL, which would mean that the tb
instance didn't have a match with the ta
one, and tb.date
is a particular value. Now, if there's no match in tb
, all its columns will be returned as NULLs, which includes tb.date
, meaning it cannot be equal to a value and be null at the same time.
Obviously, your intention was different. You wanted to left-join one subset of a table to another subset (of the same table, as it happens). In that case, the condition defining the subset you are joining should be put into the ON clause. That is, you are not joining on the sole condition that both item
and size
columns of both instances match, but on the combined condition of those columns matching and the other instance's date
being a particular value. So, this is what you should have instead:
SELECT ta.item,
ta.size
FROM t1 ta
LEFT JOIN t1 tb
ON ta.item = tb.item
AND ta.size = tb.size
AND tb.date = '2013-02-13'
WHERE ta.date = '2013-02-11'
AND tb.date = '2013-02-13'
AND tb.size IS NULL
The other query
SELECT item, size
FROM t1
WHERE t1.date = '2013-02-11'
AND NOT EXISTS (
SELECT item, size
FROM t1
WHERE t1.date = '2013-02-13'
)
seems to be a result of confusing EXISTS
with IN
.
First, how EXISTS works. It returns true
when its subquery returns a non-empty row set. It doesn't matter which columns, only the presence of rows counts. If none is returned, the result is false
. This way, your query reads like this:
Return every row whose
date
is'2013-02-11'
AND there aren't any rows fordate = '2013-02-11'
.
While you probably meant to say by that query something like this:
Return every row whose
date
is'2013-02-11'
AND there are no corresponding row with the sameitem
andsize
and with thedate
of'2013-02-11'
.
That is, you need to correlate the EXISTS subquery with the main query, which means including the current row's size
and item
into the EXISTS test. So, this is what the fix might look like:
SELECT item, size
FROM t1 ta
WHERE ta.date = '2013-02-11'
AND NOT EXISTS (
SELECT item, size
FROM t1 tb
WHERE tb.date = '2013-02-13'
AND ta.item = tb.item
AND ta.size = tb.size
)
On the other hand, you could rewrite the same query to use NOT IN
instead of NOT EXISTS
. Here:
SELECT item, size
FROM t1
WHERE date = '2013-02-11'
AND (item, size) NOT IN (
SELECT item, size
FROM t1
WHERE date = '2013-02-13'
)
which basically says:
Return every row whose
(item, size)
pair fordate = '2013-02-11'
is not in the(item, size)
pair set fordate = '2013-02-11'
.
An IN
/NOT IN
query may often look clearer than its EXISTS
/NOT EXISTS
counterpart. Whether the two would result in identical query plans is another matter, and you should probably test before deciding which method to use (but that applies to the LEFT JOIN + WHERE IS NULL
method too, of course).
Upvotes: 0
Reputation: 1269623
Use an aggregation query:
select item, size
from t1
group by item, size
having sum(case when date = '2013-02-11' then 1 else 0 end) = 1 and
sum(case when date = '2013-02-13' then 1 else 0 end) = 0
This is an example of a "groups within groups" query. By varying the having
clause, you have a lot of flexibility in the conditions you want to meet. For instance:
having sum(case when date = '2013-02-11' then 1 else 0 end) = 0 or
sum(case when date = '2013-02-13' then 1 else 0 end) = 0
would return combinations that are present in the data, but missing on either day.
Upvotes: 2
Reputation: 1774
You can also try this simple query
SELECT item, size FROM dbo.Table_1 WHERE date = '2013-02-11'
EXCEPT
SELECT item, size FROM dbo.Table_1 WHERE date = '2013-02-13'
Upvotes: 0
Reputation: 263703
SELECT a.*
FROM
(
SELECT item, size
FROM tableName
WHERE date = '2013-02-11'
) a
LEFT JOIN
(
SELECT item, size
FROM tableName
WHERE date = '2013-02-13'
) b ON a.item = b.item AND
a.size = b.size
WHERE b.size IS NULL
Upvotes: 4