• Here's another alternative. I haven't tested the performance as I don't have a setup to do that, and I don't know if it will be similar to Jeff's in query plan. It does avoid the COUNT(DISTINCT...) that I read a while back (can't find the linky) was sometimes bad (something about rewinds).

    EDIT: Eh, nevermind... you can ignore this one... Did a small test and it's between 5 and 10x slower than other methods with about 2x the reads.

    Second Edit: BUT, it's easier to adjust if you know that the situation is a random number of "Purchased these" with a single "but didn't purchase this". Most of the other methods rely on replicating a block of SQL for each item in the list of "Purchased". I think both Jeff's (and mine) work without that restriction.

    SELECT

    b.CustomerID

    FROM

    (

    SELECT

    a.CustomerID

    FROM

    (

    SELECT

    DISTINCT

    CustomerID,

    Product

    FROM

    Purchase

    WHERE

    Product IN ('A','B')) AS a

    GROUP BY

    a.CustomerID

    HAVING

    COUNT(Product) = 2

    ) AS b LEFT OUTER JOIN

    Purchase p ON b.CustomerID = p.CustomerID AND p.Product = 'C'

    WHERE

    p.Product IS NULL