web-dev-qa-db-fra.com

Comprendre le fonctionnement de JOIN lorsque 3 tables ou plus sont impliquées. [SQL]

Je me demande si quelqu'un peut m'aider à améliorer ma compréhension des JOIN en SQL. [Si cela est important pour le problème, je pense spécifiquement à MS SQL Server.]

Prenez 3 tableaux A, B [A liés à certains A.AId] et C [B liés à C par certains B.BId]

Si je compose une requête, par exemple

SELECT *
FROM A JOIN B 
ON A.AId = B.AId

Tout va bien - je suis adorable avec la façon dont cela fonctionne.

Que se passe-t-il lorsque le tableau C (ou un autre D, E, .... est ajouté)

Dans la situation

SELECT *
FROM A JOIN B 
  ON A.AId = B.AId
JOIN C ON C.BId = B.BId

À quoi C rejoint-il? - est-ce cette table B (et les valeurs de la table B?) Ou est-ce un autre jeu de résultats temporaire qui est le résultat de la jointure A + B à laquelle la table C est jointe?

[L'implication n'étant pas toutes les valeurs qui sont dans la table B seront nécessairement dans le jeu de résultats temporaire A + B basé sur la condition de jointure pour A, B]

Un exemple spécifique (et assez artificiel) de la raison pour laquelle je demande est parce que j'essaie de comprendre le comportement que je vois dans ce qui suit:

Tables 
Account (AccountId, AccountBalanceDate, OpeningBalanceId, ClosingBalanceId)
Balance (BalanceId)
BalanceToken (BalanceId, TokenAmount)

Where:
Account->Opening, and Closing Balances are NULLABLE 
(may have opening balance, closing balance, or none)

Balance->BalanceToken is 1:m - a balance could consist of many tokens

Conceptuellement, le solde de clôture d'une date serait le solde d'ouverture de demain

Si j'essayais de trouver une liste de tous les soldes d'ouverture et de clôture d'un compte

Je pourrais faire quelque chose comme

SELECT AccountId
, AccountBalanceDate
, Sum (openingBalanceAmounts.TokenAmount) AS OpeningBalance
, Sum (closingBalanceAmounts.TokenAmount) AS ClosingBalance
FROM Account A 
   LEFT JOIN BALANCE OpeningBal 
      ON A.OpeningBalanceId = OpeningBal.BalanceId
   LEFT JOIN BALANCE ClosingBal 
      ON A.ClosingBalanceId = ClosingBal.BalanceId
   LEFT JOIN BalanceToken openingBalanceAmounts 
      ON openingBalanceAmounts.BalanceId = OpeningBal.BalanceId
   LEFT JOIN BalanceToken closingBalanceAmounts 
      ON closingBalanceAmounts.BalanceId = ClosingBal.BalanceId
   GROUP BY AccountId, AccountBalanceDate  

Les choses fonctionnent comme je m'y attendais jusqu'à ce que le dernier JOIN apporte les jetons de solde de clôture - où je me retrouve avec des doublons dans le résultat.

[Je peux réparer avec un DISTINCT - mais j'essaie de comprendre pourquoi ce qui se passe se passe]

On m'a dit que le problème est dû au fait que la relation entre Balance et BalanceToken est 1: M - et que lorsque j'apporte le dernier JOIN, j'obtiens des doublons parce que le 3e JOIN a déjà introduit BalanceIds plusieurs fois dans le (je suppose) jeu de résultats temporaire.

Je sais que les exemples de tableaux ne sont pas conformes à une bonne conception de base de données

Toutes mes excuses pour l'essai, merci pour tout éclaircissement :)

Modifier en réponse à la question de Marc

Conceptuellement, pour un compte, il ne devrait pas y avoir de doublons dans BalanceToken pour un compte (par AccountingDate) - Je pense que le problème vient du fait que 1 compte/AccountingDates clôture le solde est que les comptes ouvrent le solde pour le lendemain - donc lors de l'auto-adhésion à Balance, BalanceToken plusieurs fois pour obtenir des soldes d'ouverture et de clôture Je pense que les soldes (BalanceId) sont introduits plusieurs fois dans le "mix des résultats". Si cela aide à clarifier le deuxième exemple, considérez-le comme un rapprochement quotidien - donc des jointures à gauche - un solde d'ouverture (et/ou) de clôture peut ne pas avoir été calculé pour une combinaison compte/date comptable donnée.

67
Delaney

Conceptuellement voici ce qui se passe lorsque vous joignez trois tables ensemble.

  1. L'optimiseur propose un plan, qui comprend un ordre de jointure. Il peut s'agir de A, B, C ou C, B, A ou l'une des combinaisons
  2. Le moteur d'exécution des requêtes applique tous les prédicats (clause WHERE) à la première table qui n'implique aucune des autres tables. Il sélectionne les colonnes mentionnées dans les conditions JOIN ou la liste SELECT ou ORDER BY liste. Appelez ce résultat A
  3. Il joint cet ensemble de résultats à la deuxième table. Pour chaque ligne, il rejoint la deuxième table, en appliquant tous les prédicats qui peuvent s'appliquer à la deuxième table. Il en résulte un autre jeu de résultats temporaire.
  4. Il rejoint ensuite la table finale et applique le ORDER BY

C'est conceptuellement ce qui se passe. En fait, il existe de nombreuses optimisations possibles en cours de route. L'avantage du modèle relationnel est que la base mathématique solide permet diverses transformations de plan sans changer la justesse.

Par exemple, il n'est vraiment pas nécessaire de générer les jeux de résultats complets en cours de route. Le ORDER BY peut être fait à la place en accédant aux données en utilisant un index en premier lieu. Il existe également de nombreux types de jointures.

40
WW.

Nous savons que les données de B vont être filtrées par la jointure (interne) vers A (les données de A sont également filtrées). Donc, si nous (intérieur) joignons de B à C, donc l'ensemble C est aussi filtré par la relation à A. Et notez également que tous les doublons de la jointure seront inclus.

Toutefois; l'ordre dans lequel cela se produit dépend de l'optimiseur; il pourrait décider de faire la jointure B/C d'abord, puis introduire A, ou toute autre séquence (probablement basée sur le nombre estimé de lignes de chaque jointure et les index appropriés ).


TOUTEFOIS; dans votre dernier exemple, vous utilisez un LEFT OUTER rejoindre; donc Account n'est pas filtré du tout , et peut bien être dupliqué si l'une des autres tables a plusieurs correspondances.

Y a-t-il des doublons (par compte) dans BalanceToken?

5
Marc Gravell

Je trouve souvent qu'il est utile de visualiser le plan d'exécution réel. Dans l'analyseur de requêtes/studio de gestion, vous pouvez l'activer pour les requêtes à partir du menu Requête ou utiliser Ctrl + M. Après avoir exécuté la requête, le plan qui a été exécuté est affiché dans un autre onglet de résultat. De là, vous verrez que C et B sont joints en premier, puis le résultat est joint à A. Le plan peut varier en fonction des informations dont dispose le SGBD car les deux jointures sont internes, ce qui en fait A-et-B-et-C . Ce que je veux dire, c'est que le résultat sera le même quel que soit celui qui est joint en premier, mais le temps qu'il faut peut différer considérablement, et c'est là que l'optimiseur et les astuces entrent en jeu.

1
Bernhard Hofmann

Les jointures peuvent être délicates et une grande partie du comportement est bien sûr dictée par la façon dont les données sont stockées dans les tables réelles.

Sans voir les tableaux, il est difficile de donner une réponse claire dans votre cas particulier, mais je pense que le problème de base est que vous additionnez plusieurs ensembles de résultats qui sont combinés en un seul.

Au lieu de plusieurs jointures, vous devriez peut-être créer deux tables temporaires distinctes dans votre requête, l'une avec l'ID de compte, la date et la somme des soldes d'ouverture, une seconde avec l'ID de compte, la date et la somme des soldes de clôture, puis en joignant ces deux sur l'ID de compte et la date.

Afin de savoir exactement ce qui se passe avec les jointures, également dans votre cas spécifique, je ferais ce qui suit:

Changer la pièce initiale

SELECT accountID Accountbalancedate, somme (...) comme solde d'ouverture, somme (...) comme solde de fermeture FROM

simplement

"SELECT * FROM"

Étudiez le tableau résultant et vous verrez exactement quelles données sont dupliquées. Supprimez les jointures une par une et voyez ce qui se passe. Cela devrait vous donner une idée de ce que sont vos données particulières qui causent les dupes.

Si vous ouvrez la requête dans SQL Server Management Studio (la version gratuite existe), vous pouvez modifier la requête dans le concepteur. La vue visuelle de la façon dont les tables sont jointes peut également vous aider à comprendre ce qui se passe.

1
Console