2010-07-22 2 views
32

J'ai deux tables (tâches et Timeentries), qui sont reliés par une clé étrangère (référence TimeEntries.TaskID Tasks.ID)façon élégante de supprimer des lignes qui ne sont pas référencées par autre table

Maintenant, je voudrais pour supprimer toutes les lignes des tâches qui ne sont pas référencées par la table TimeEntries. Je pensais que cela devrait fonctionner:

DELETE FROM Tasks WHERE ID not IN (SELECT TaskID FROM TimeEntries) 

mais il touche 0 lignes, même si il y a beaucoup de lignes non référencées dans la table des tâches.

Quel pourrait être le problème ici? Bien sûr, je pourrais écrire un SP qui répète toutes les lignes, mais il semble que cela pourrait être fait dans un seul paquebot. Je suppose que c'est l'une de ces erreurs de sous-dépassement de temps de sommeil. S'il vous plaît aider!

+2

Obtenez-vous les résultats attendus si vous exécutez simplement la sous-requête SELECT seule? – JNK

+0

@ J-N-K: oui, je l'ai fait. –

Répondre

49

Il y a un gotcha notoire pour not in. Fondamentalement, id not in (1,2,3) est un raccourci pour:

id <> 1 and id <> 2 and id <> 3 

Maintenant, si votre table TimeEntries contient une ligne avec une TaskID de null, le not in se traduit:

ID <> null and ID <> 1 and ID <> 2 AND ... 

Le résultat d'une comparaison avec null est toujours unknown . Comme unknown n'est pas true dans SQL, la clause where filtre toutes les lignes et vous ne supprimez rien.

Une solution facile est une somme supplémentaire de clause where dans la sous-requête:

DELETE FROM Tasks 
WHERE ID not IN 
     (
     SELECT TaskID 
     FROM TimeEntries 
     WHERE TaskID is not null 
     ) 
+0

Avez-vous une suggestion pour le contourner? – DOK

+0

@DOK: Bien sûr, une manière ajoutée à la réponse. SQLMenace a posté un autre bon – Andomar

+0

Excellente réponse. Merci beaucoup! –

18

Une façon, cela prendra soin de vous rencontrez « problème » avec des valeurs nulles (voir le lien ci-dessous pour plus d'infos)

DELETE FROM Tasks 
WHERE NOT EXISTS (SELECT 1 FROM TimeEntries 
        WHERE TimeEntries.TaskID = Tasks.ID) 

Pour comprendre le problème que vous rencontrez, jetez un oeil à Select all rows from one table that don't exist in another table

+0

Cette réponse est plus élégante, je suppose. – wieczorek1990

3
Delete FROM Tasks 
     WHERE not Exists 
      (SELECT 'X' FROM TimeEntries where TimeEntries.TaskID = Tasks.ID) 

Le code SQL ci-dessus doit supprimer toutes les lignes des tâches où l'ID de tâche n'existe pas dans la table d'entrées de temps. Je l'exécuterais en tant que déclaration Select à tester :)

9

Puisque vous exécutez SQL 2008, vous pouvez utiliser la nouvelle syntaxe de fusion nifty.

MERGE Tasks AS target 
USING TimeEntries as Source ON (Target.TaskID=Source.TaskID) 
WHEN NOT MATCHED BY Source THEN DELETE; 
+1

+1: Un peu overkill dans ce cas, mais il est certainement utile de connaître la commande merge. Je ne savais même pas que quelque chose comme ça existait. –

+1

Pourquoi pensez-vous que c'est exagéré? Il est à peu près fait pour des tâches comme celle-ci (faire correspondre les lignes entre les tables). – JohnFx

+1

Qu'en est-il de ses performances par rapport aux anciennes solutions? Y a-t-il des avantages ou est-ce la manière supportée de faire de telles actions? Thx –

3

Je sais c'est vieux, mais je me demande pourquoi personne n'a mentionné une requête de suppression comme décrit here. Donc, pour référence:

DELETE FROM Tasks 
FROM Tasks LEFT OUTER JOIN 
    TimeEntries ON TimeEntries.TaskID = Tasks.ID 
WHERE TimeEntries.TaskID IS NULL; 

Cette syntaxe n'est pas compatible avec ISO, donc ne fonctionne que pour T-SQL.

Questions connexes