2010-10-27 8 views
0

Disons que nous avons une table (EnsembleMembers) dans une base de données SQL avec les données suivantes. Il répertorie les musiciens qui font partie de différents ensembles, ainsi que leurs instruments.Problème avec requête SQL impliquant une condition de type XOR

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' 

Je veux sélectionner les ID d'ensemble où l'ensemble a un ou plusieurs saxophone ou un ou plusieurs joueurs de clarinette, mais pas les deux. J'ai essayé l'instruction SQL suivante, mais elle renvoie 1,2,2,99, plutôt que le 2,99 attendu.

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)); 

Qu'est-ce que je fais mal? PS - Je ne souhaite pas utiliser DISTINCT pour des raisons de performances.

Répondre

1

Je suppose que vous avez une table appelée 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' 
) 
) 

Vous souhaitez éviter d'utiliser DISTINCT. Pour ce faire, utilisez la table principale ENSEMBLES. À partir de là, choisissez des rangées d'ensemble qui ont «clarinette» OU «saxophone». Ensuite, la troisième étape consiste à supprimer toutes les lignes d'ensemble qui ont «clarinette» ET «saxophone».

3
SELECT EnsembleID 
FROM EnsembleMembers 
WHERE Instrument IN ('Saxophone', 'Clarinet') 
GROUP BY EnsembleID 
HAVING COUNT(DISTINCT Instrument) = 1 

Vous pouvez également utiliser un FULL OUTER JOIN pour cela, mais ce type de jointure est pas pris en charge par MySQL et quelques autres bases de données mineures.

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 

Si vous avez besoin que cela fonctionne sans FULL OUTER JOIN, essayez ceci:

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; 

À l'avenir, s'il vous plaît étiquette votre question avec la marque de SGBDR que vous utilisez.

+0

SANS utiliser DISTINCT? –

+1

Dans la solution GROUP BY, vous devez utiliser DISTINCT s'il peut y avoir plusieurs saxophones ou plusieurs clarinettes dans un ensemble. Voir aussi la deuxième solution que je viens d'ajouter, qui peut fonctionner en fonction de la marque de base de données que vous utilisez. –

+0

Etes-vous sûr que l'exemple de jointure externe complète est correct? Je sais que MySQL ne les supporte pas mais j'ai dupliqué votre exemple, changé l'un en jointure externe gauche, l'autre en jointure externe droite, et union'd les deux (ce qui est le moyen d'émuler la fonctionnalité AFAIK), mais je l'ai toujours récupéré un ensemble d'identification d'un ensemble ayant à la fois un saxophone et un clarinettiste. –

1

Voici la solution la plus stupide (mais je l'aime en quelque sorte):

SELECT EnsembleID FROM EnsembleMembers 
MINUS 
( 
     SELECT EnsembleID 
     FROM EnsembleMembers 
     WHERE Instrument = 'Saxophone' 
    INTERSECT 
     SELECT EnsembleID 
     FROM EnsembleMembers 
     WHERE Instrument = 'Clarinet'); 
+0

Changer 'MINUS' à' EXCEPT' et il devient la syntaxe SQL-92 et fonctionne sur SQL Server ... donc je l'aime aussi :) Par intérêt, 'MINUS' ne semble pas être dans l'un des SQL Normes, est propriétaire d'un produit SQL particulier? – onedaywhen

+0

@onedaywhen: oui, je suis sur Oracle. Je n'ai pas pensé que ce n'était pas ANSI, merci! –

1

Voici comment vous pouvez faire la requête en utilisant 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 
Questions connexes