Problem with SQL query involving XOR-like condition
Let's say we have a table (EnsembleMembers) in an SQL database with the following data. It lists the musicians which are part of various ensembles, along with their instruments.
EnsembleID (FK) MusicianID (FK) Instrument
----------------------------------------------
'1' 开发者_如何学编程 '1' 'Clarinet'
'1' '4' 'Clarinet'
'1' '100' 'Saxophone'
'2' '200' 'Saxophone'
'2' '300' 'Saxophone'
'2' '320' 'Flute'
'99' '300' 'Clarinet'
I want to select the ensemble IDs where the ensemble has one or more saxophone or one or more clarinet players, but not both. I have tried the following SQL statement, but it is returning 1,2,2,99
, rather than the expected 2,99
.
SELECT e1.EnsembleID
FROM ensemblemembers e1
WHERE e1.Instrument = 'Saxophone'
OR e1.Instrument = 'Clarinet'
AND NOT EXISTS (SELECT *
FROM ensemblemembers e2
WHERE ( e1.Instrument = 'Saxophone'
AND e2.Instrument = 'Clarinet'
AND e1.EnsembleID = e2.EnsembleID)
OR ( e1.Instrument = 'Clarinet'
AND e2.Instrument = 'Saxophone'
AND e1.EnsembleID = e2.EnsembleID));
What am I doing wrong?
PS - I don't want to use DISTINCT for performance reasons.
SELECT EnsembleID
FROM EnsembleMembers
WHERE Instrument IN ('Saxophone', 'Clarinet')
GROUP BY EnsembleID
HAVING COUNT(DISTINCT Instrument) = 1
You can also use a FULL OUTER JOIN for this, but this type of join is not supported by MySQL and a few other minor databases.
SELECT COALESCE(e1.EnsembleID, e2.EnsembleID) AS EnsembleID
FROM EnsembleMembers e1 FULL OUTER JOIN EnsembleMembers e2
ON e1.EnsembleID = e2.EnsembleID
AND e1.Instrument = 'Saxophone'
AND e2.Instrument = 'Clarinet'
WHERE e1.EnsembleID IS NULL OR e2.EnsembleID IS NULL
If you need this to work without FULL OUTER JOIN, try this:
SELECT e1.EnsembleID, e1.Instrument
FROM EnsembleMembers e1 LEFT OUTER JOIN EnsembleMembers e2
ON e1.EnsembleID = e2.EnsembleID
AND e2.Instrument = 'Clarinet'
WHERE e1.Instrument = 'Saxophone' AND e2.EnsembleID IS NULL
UNION
SELECT e1.EnsembleID, e1.Instrument, e2.EnsembleID, e2.Instrument
FROM EnsembleMembers e1 LEFT OUTER JOIN EnsembleMembers e2
ON e1.EnsembleID = e2.EnsembleID
AND e2.Instrument = 'Saxophone'
WHERE e1.Instrument = 'Clarinet' AND e2.EnsembleID IS NULL;
In the future, please tag your question with the brand of RDBMS you use.
I assume you have a table called ENSEMBLES:
select E.id from ensembles E where exists (
select 1 from ensemblemembers M where E.id = M.ensembleid
and M.instrument in ('clarinet', 'saxophone')
) and not (
exists (
select 1 from ensemblemembers M where E.id = M.ensembleid
and M.instrument = 'clarinet'
) and exists (
select 1 from ensemblemembers M where E.id = M.ensembleid
and M.instrument = 'saxophone'
)
)
You want to avoid using DISTINCT, so one way to do it is by using the main ENSEMBLES table. From there, pick ensemble rows that have 'clarinet' OR 'saxophone'. Then the third step is to remove all ensemble rows that have 'clarinet' AND 'saxophone'.
Here is the most stupid solution (but I like it somehow):
SELECT EnsembleID FROM EnsembleMembers
MINUS
(
SELECT EnsembleID
FROM EnsembleMembers
WHERE Instrument = 'Saxophone'
INTERSECT
SELECT EnsembleID
FROM EnsembleMembers
WHERE Instrument = 'Clarinet' );
Here's how you could do the query using XOR:
select a.EnsembleID from
(
select max(EnsembleID) as EnsembleID,
max(case when Instrument = 'Saxophone' then 1 else 0 end) as Saxophone,
max(case when Instrument = 'Clarinet' then 1 else 0 end) as Clarinet
from
EnsembleMembers
group by EnsembleID
) a
where
a.Saxophone ^ a.Clarinet = 1
精彩评论