web-dev-qa-db-fra.com

Obtention de lignes distinctes à partir d'une jointure externe gauche

Je construis une application qui génère dynamiquement SQL pour rechercher des lignes d'une table particulière (c'est la classe de domaine principale, comme un employé).

Il existe trois tables Table1, Table2 et Table1Table2Map. Table1 a une relation plusieurs à plusieurs avec Table2 et est mappée via la table Table1Table2Map. Mais puisque Table1 est ma table principale, la relation est pratiquement comme une à plusieurs.

Mon application génère un sql qui donne essentiellement un jeu de résultats contenant des lignes de toutes ces tables. La clause select et les jointures ne changent pas tandis que la clause where est générée en fonction de l'interaction de l'utilisateur. Dans tous les cas, je ne veux pas de lignes en double de Table1 dans mon jeu de résultats car c'est la table principale pour l'affichage des résultats. À l'heure actuelle, la requête qui est générée est la suivante:

select distinct Table1.Id as Id, Table1.Name, Table2.Description from Table1
left outer join Table1Table2Map on (Table1Table2Map.Table1Id = Table1.Id)
left outer join Table2 on (Table2.Id = Table1Table2Map.Table2Id)

Par souci de simplicité, j'ai exclu la clause where. Le problème est quand il y a plusieurs lignes dans Table2 pour Table1 même si j'ai dit distinct de Table1.Id l'ensemble de résultats a des lignes en double de Table1 car il doit sélectionner toutes les lignes correspondantes dans Table2.

Pour en savoir plus, considérez que pour une ligne du tableau 1 avec Id = 1, il y a deux lignes dans Table1Table2Map (1, 1) et (1, 2) mappant le tableau 1 à deux lignes du tableau 2 avec les ID 1, 2. La requête mentionnée ci-dessus renvoie lignes en double pour ce cas. Maintenant, je veux que la requête renvoie une seule fois la ligne Table1 avec l'ID 1. En effet, il n'y a qu'une seule ligne dans le tableau 2 qui est comme une valeur active pour l'entrée correspondante dans le tableau 1 (ces informations se trouvent dans le tableau de mappage). Existe-t-il un moyen d'éviter de recevoir des lignes en double de Table1.

Je pense qu'il y a un problème fondamental dans la façon dont j'essaie de résoudre le problème, mais je ne suis pas en mesure de savoir de quoi il s'agit. Merci d'avance.

20
Nazgul

Essayer:

left outer join (select distinct YOUR_COLUMNS_HERE ...) SUBQUERY_ALIAS on ...

En d'autres termes, ne vous joignez pas directement à la table, joignez-vous à une sous-requête qui limite les lignes contre lesquelles vous vous joignez.

Vous pouvez utiliser GROUP BY sur Table1.Id, et cela supprimera les lignes supplémentaires. Vous n'auriez pas à vous soucier de la mécanique du côté jointure.

J'ai trouvé cette solution dans une énorme requête et cette solution n'a pas beaucoup affecté le temps de requête.

REMARQUE: je réponds à cette question 3 ans après sa pose, mais cela peut aider quelqu'un que je crois.

12
kommradHomer

Vous pouvez réécrire vos jointures gauches pour qu'elles soient externes, afin d'utiliser un top 1 et une commande comme suit:

select Table1.Id as Id, Table1.Name, Table2.Description 
from Table1
outer apply (
   select top 1 *
   from Table1Table2Map
   where (Table1Table2Map.Table1Id = Table1.Id) and Table1Table2Map.IsActive = 1
   order by somethingCol 
) t1t2
outer apply (
   select top 1 *
   from Table2
   where (Table2.Id = Table1Table2Map.Table2Id)
) t2;

Notez qu'une application externe sans "top" ou "order by" est exactement équivalente à une jointure externe gauche, elle vous donne juste un peu plus de contrôle. (l'application croisée équivaut à une jointure interne).

Vous pouvez également faire quelque chose de similaire en utilisant la fonction row_number ():

 select * from (
      select distinct Table1.Id as Id, Table1.Name, Table2.Description,
        rowNum = row_number() over ( partition by table1.id order by something )
      from Table1
      left outer join Table1Table2Map on (Table1Table2Map.Table1Id = Table1.Id)
      left outer join Table2 on (Table2.Id = Table1Table2Map.Table2Id)
 ) x
 where rowNum = 1;

La plupart de cela ne s'applique pas si l'indicateur IsActive peut réduire vos autres tables à une ligne, mais elles peuvent vous être utiles.

7
John Gibb

Pour développer un point: vous avez dit qu'il n'y a qu'une seule ligne "active" dans le tableau 2 par ligne dans le tableau 1. Cette ligne n'est-elle pas marquée comme active de sorte que vous puissiez la placer dans la clause where? Ou y a-t-il de la magie dans les conditions dynamiques fournies par l'utilisateur qui détermine ce qui est actif et ce qui ne l'est pas.

Si vous n'avez pas besoin de sélectionner quoi que ce soit dans Table2, la solution est relativement simple en ce sens que vous pouvez utiliser la fonction EXISTS, mais puisque vous avez mis TAble2.Description dans la clause, je suppose que ce n'est pas le cas.

Fondamentalement, qu'est-ce qui sépare les lignes pertinentes du tableau 2 des lignes non pertinentes? S'agit-il d'un indicateur actif ou d'une condition dynamique? La première rangée? C'est vraiment ainsi que vous devriez supprimer les doublons.

les clauses DISTINCT ont tendance à être surutilisées . Ce n'est peut-être pas le cas ici, mais il semble qu'il soit possible que vous essayiez de pirater les résultats souhaités avec DISTINCT plutôt que de résoudre le vrai problème, qui est un problème assez courant.

3
cletus

Vous devez inclure une clause d'activité dans votre jointure (et pas besoin de distinct):

select Table1.Id as Id, Table1.Name, Table2.Description from Table1
left outer join Table1Table2Map on (Table1Table2Map.Table1Id = Table1.Id) and Table1Table2Map.IsActive = 1
left outer join Table2 on (Table2.Id = Table1Table2Map.Table2Id)
2
Arvo

Si vous souhaitez afficher plusieurs lignes du tableau 2, vous aurez des données en double du tableau 1 affichées. Si vous le souhaitez, vous pouvez utiliser une fonction d'agrégation (IE Max, Min) sur table2, cela éliminerait les lignes en double de table1, mais masquerait également certaines des données de table2.

Voir aussi ma réponse à la question # 70161 pour une explication supplémentaire

1
Nathan Koop