web-dev-qa-db-fra.com

La jointure de plusieurs tables entraîne des lignes en double

J'obtiens plus de lignes alors je m'attends à ce qu'elles soient renvoyées par ma requête.

Je crois que cela a quelque chose à voir avec mes déclarations de jointure.

Il existe plusieurs tables contenant des informations différentes. Person contient les informations principales sur la personne mais pas l'adresse, le téléphone ou l'e-mail. En effet, le concepteur d'origine voulait que la table puisse contenir plusieurs numéros de téléphone, e-mails et adresses.

SELECT (person.FirstName + ' ' + person.LastName) as FullName
    ,ISNULL(Person.isClient, '')
    ,ISNULL(Person.UDF1, '')
    ,ISNULL(Address.City, '')
    ,ISNULL(Address.state, '')
    ,PersonAddress.Person
    ,PersonAddress.Address
    ,ISNULL(Phone.PhoneNumber, 'N/A')
    ,Email.Email
    ,Person.Website
FROM Person
    left join PersonAddress on Person.ID = PersonAddress.Person
    left join Address on PersonAddress.Address = Address.ID
    left join PersonPhone on Person.ID = PersonPhone.Person
    left join Phone on PersonPhone.Person = Phone.ID
    left join Email with (nolock) on Person.ID = Email.Person
WHERE (
        isclient = 'prospect'
        or isclient = 'client'
        )
    and Address is not null
    and name like '%Mike%'
ORDER BY isClient asc;

Pour cet exemple, je reçois 6 rangées de "Mike Worths". 3 des copies ont un email et trois ont un autre email.

Pour "Mike Pamstein", je reçois deux lignes en double avec le même e-mail.

J'ai besoin que les résultats ne contiennent qu'une seule ligne unique pour chaque personne.

Je veux laisser tomber le deuxième e-mail.

7
normandantzig

Vraisemblablement, vous voulez voir une seule entrée pour chaque combinaison personne/adresse/e-mail/site Web unique. Si oui, essayez ceci:

SELECT (person.FirstName + ' ' + person.LastName) as FullName
    , ISNULL(Person.isClient, '')
    , ISNULL(Person.UDF1, '')
    , ISNULL([Address].City, '')
    , ISNULL([Address].[state], '')
    , PersonAddress.Person
    , PersonAddress.[Address]
    , ISNULL(Phone.PhoneNumber, 'N/A')
    , Email.Email
    , Person.Website
FROM dbo.Person
    LEFT JOIN dbo.PersonAddress ON Person.ID = PersonAddress.Person
    LEFT JOIN dbo.[Address] ON PersonAddress.[Address] = [Address].ID
    LEFT JOIN dbo.PersonPhone ON Person.ID = PersonPhone.Person
    LEFT JOIN dbo.Phone ON PersonPhone.Person = Phone.ID
    LEFT JOIN dbo.Email WITH (NOLOCK) ON Person.ID = Email.Person
WHERE (
        isclient = 'prospect'
        or isclient = 'client'
        )
    and [Address] is not null
    and name like '%Mike%'
GROUP BY (person.FirstName + ' ' + person.LastName)
    , ISNULL(Person.isClient, '')
    , ISNULL(Person.UDF1, '')
    , ISNULL([Address].City, '')
    , ISNULL([Address].state, '')
    , PersonAddress.Person
    , PersonAddress.[Address]
    , ISNULL(Phone.PhoneNumber, 'N/A')
    , Email.Email
    , Person.Website
ORDER BY isClient asc;

La clause GROUP BY À la fin garantit qu'une seule ligne est retournée pour chaque combinaison unique de colonnes dans la clause GROUP BY. Cela devrait empêcher l'affichage de lignes en double dans vos résultats.

Quelques points à noter:

  1. Utilisez toujours le qualificatif de schéma sur la clause FROM. FROM Person Devrait être FROM dbo.Person -> cela élimine toute confusion si vous introduisez de nouveaux schémas à l'avenir et empêche l'optimiseur de requête d'avoir à rechercher le schéma par défaut pour votre utilisateur.

  2. Pour assurer la maintenabilité à l'avenir, vous souhaiterez probablement nommer les colonnes de la même manière, quelle que soit la table dans laquelle elles se trouvent. Ainsi, par exemple, au lieu que la colonne ID de la table People soit nommée ID, et étant nommé Person dans la table Address, je le nommerais PersonID dans les deux tables. Cela évite la confusion (lire les bogues) dans les jointures telles que dbo.Person LEFT JOIN dbo.Address ON Person.ID = Address.Person.

  3. Au lieu de nommer des tables comme Person, elles doivent être nommées d'après la collection d'éléments qu'elles contiennent, au pluriel. Ainsi, Person devient People et Address devient Addresses. Cela élimine la confusion -> la table Address contient-elle réellement une seule adresse ou plusieurs adresses?

  4. WITH (NOLOCK) doit être évité comme la peste qu'elle est, à moins que vous ne compreniez parfaitement les conséquences de la lecture de lignes qui ont été modifiées par d'autres transactions mais pas encore validées. Depuis MSDN:

Les transactions s'exécutant au niveau LIRE NON COMMIS n'émettent pas de verrous partagés pour empêcher d'autres transactions de modifier les données lues par la transaction en cours. Les transactions READ UNCOMMITTED ne sont pas non plus bloquées par des verrous exclusifs qui empêcheraient la transaction en cours de lire les lignes qui ont été modifiées mais non validées par d'autres transactions. Lorsque cette option est définie, il est possible de lire les modifications non validées, appelées lectures incorrectes. Les valeurs des données peuvent être modifiées et des lignes peuvent apparaître ou disparaître dans l'ensemble de données avant la fin de la transaction. Cette option a le même effet que la définition de NOLOCK sur toutes les tables de toutes les instructions SELECT d'une transaction. C'est le moins restrictif des niveaux d'isolement.

Dans SQL Server, vous pouvez également minimiser les conflits de verrouillage tout en protégeant les transactions des lectures incorrectes des modifications de données non validées en utilisant soit:

Le niveau d'isolement READ COMMITTED avec l'option de base de données READ_COMMITTED_SNAPSHOT définie sur ON.

Le niveau d'isolement SNAPSHOT.

7
Max Vernon

Pourriez-vous utiliser des sous-requêtes pour renvoyer un enregistrement dans vos jointures comme ça?

    ...
    FROM dbo.Person
    LEFT JOIN (SELECT MAX(AddressID) AS AddressID, Person FROM  dbo.PersonAddress GROUP BY Person) PersonAddress ON Person.ID = PersonAddress.Person
    LEFT JOIN dbo.[Address] ON PersonAddress.[Address] = [Address].ID

Dans ce cas, j'utilise simplement MAX pour le forcer à une seule personne, mais vous pouvez utiliser une autre logique pour le réduire à un enregistrement par personne et éliminer les doublons de cette façon.

1
codedawg82