Using EXCEPT with NOT EXISTS [closed]
-
20-01-2021 - |
Domanda
SELECT DISTINCT cname
FROM account AS cust_account
WHERE NOT EXISTS ( SELECT type
FROM account
EXCEPT
SELECT type
FROM account
WHERE account.cname = cust_account.cname )
Can someone please explain step-by-step how the query produces the result required by the question? I am just learning about NOT EXISTS and am not sure what values it takes from the parenthesis and where the WHERE clause belongs with.
Soluzione
There are three separate parts to the query. I'll label them so I can reference each more clearly:
SELECT DISTINCT cname -- Part A
FROM account AS cust_account
WHERE NOT EXISTS ( SELECT type -- Part B
FROM account
EXCEPT
SELECT type -- Part C
FROM account
WHERE account.cname = cust_account.cname )
First the inner query. Part C lists all type
values for account
rows owned by the customer currently being considered by the outer query (Part A). Conceptually you can think of the DBMS reading all rows that match the outer query then stepping through them one at a time. For each outer row it executes the inner query. (In reality likely something more time-efficient happens but it will be logically equivalent to this.)
Part C is sometimes called a correlated sub-query because it is matched (correlated) with a value coming from the outer query. In this case it is matching on cname
. It is the WHERE clause that tells the DBMS to match Part C to Part A. cust_account
is an alias defined in the outer query. Because a single table - account
- is used three times in the statement the alias allows us to specify exactly which of these three we want to reference. It is the alias which allows us to say "match Part C to Part A."
Part B lists all type
values from table account
. This part has no WHERE so it lists all accounts for all users, not just the one currently being considered by the outer query. This part is not correlated.
The EXCEPT takes the results of the query above it (Part B) and removes those rows which occur in the query below it (Part C). Since Part B is "all types that are in the database" and Part C is "all types the current cname owns" then "B except C" is "all types except those which the current cname owns." If we have a list of all types, and we remove those which the cname owns, what is left is those types which the cname does not own. So if the inner query has any rows (for the current cname) we know there is at least one type of account that cname does not own.
EXISTS() returns true if the inner query has row(s). So NOT EXISTS() returns true if the inner query is empty. As we have just shown, it will be empty if the current cname owns all types of accounts. So the outer query lists all cname
which have all types of account - as requested in the problem.
Altri suggerimenti
You have two tables in this
customers(cname)
accounts(cname,type)
I think you want this. You want to SELECT
all cusotmers that have a matching account. It doesn't make sense to negate everything. And it doesn't make sense to say "every type of account." Because types here are values, and
- it doesn't make sense to have a customer with more than one value.
- it the question means any account, it's easier to select on the join condition itself in the exists,
I think it's a bad question.
SELECT cname
FROM customers AS c
WHERE EXISTS (
SELECT 1
FROM accounts AS a
WHERE a.cname = c.cname
);
It also doesn't make sense to use DISTINCT cname
there unless you know that there can be duplicate names AND if there were duplicate names and you used DISTINCT
then you're no longer talking about customers -- just the values of their names in a (unique) set.