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.
Conceptuellement voici ce qui se passe lorsque vous joignez trois tables ensemble.
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 AORDER 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.
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
?
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.
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.