web-dev-qa-db-fra.com

Empêcher les doublons dans LEFT JOIN

J'ai fait face à une situation où j'ai eu des valeurs en double de LEFT JOIN. Je pense que cela pourrait être un comportement souhaité mais différent de ce que je veux.

J'ai trois tables:person,departmentetcontact.

la personne :

id bigint,
person_name character varying(255)

département :

person_id bigint,
department_name character varying(255)

contact :

person_id bigint,
phone_number character varying(255)

Requête SQL:

SELECT p.id, p.person_name, d.department_name, c.phone_number 
FROM person p
  LEFT JOIN department d 
    ON p.id = d.person_id
  LEFT JOIN contact c 
    ON p.id = c.person_id;

Résultat :

id|person_name|department_name|phone_number
--+-----------+---------------+------------
1 |"John"     |"Finance"      |"023451"
1 |"John"     |"Finance"      |"99478"
1 |"John"     |"Finance"      |"67890"
1 |"John"     |"Marketing"    |"023451"
1 |"John"     |"Marketing"    |"99478"
1 |"John"     |"Marketing"    |"67890"
2 |"Barbara"  |"Finance"      |""
3 |"Michelle" |""             |"005634"

Je sais que c'est ce que font les jointures, multiplié par le nombre de lignes sélectionnées. Mais cela donne l'impression que les numéros de téléphone 023451, 99478, 67890 sont destinés aux deux départements, alors qu'ils ne concernent que la personne john avec des valeurs répétées inutiles, ce qui aggravera le problème avec un plus grand ensemble de données.
Alors, voici ce que je veux:

id|person_name|department_name|phone_number
--+-----------+---------------+------------
1 |"John"     |"Finance"      |"023451"
1 |"John"     |"Marketing"    |"99478"
1 |"John"     |""             |"67890"
2 |"Barbara"  |"Finance"      |""
3 |"Michelle" |""             |"005634"

Ceci est un exemple de ma situation et j'utilise un grand ensemble de tables et de requêtes. Donc, besoin d’une solution générique.

16

J'aime appeler ce problème "Cross join par proxy". Comme il n'y a pas d'information (condition WHERE ou JOIN) sur la façon dont les tables department et contact sont censées correspondre, elles sont interconnectées via la table proxy person - vous donnant le produit cartésien . Très semblable à celui-ci:

Plus d'explications là-bas.

Solution pour votre requête:

SELECT p.id, p.person_name, d.department_name, c.phone_number
FROM   person p
LEFT   JOIN (
  SELECT person_id, min(department_name) AS department_name
  FROM   department
  GROUP  BY person_id
  ) d ON d.person_id = p.id
LEFT   JOIN (
  SELECT person_id, min(phone_number) AS phone_number
  FROM   contact
  GROUP  BY person_id
  ) c ON c.person_id = p.id;

Vous n'avez pas défini le qui département ou le numéro de téléphone à choisir, j'ai donc choisi arbitrairement le premier. Vous pouvez l'avoir d'une autre manière ...

11

Je pense que vous avez juste besoin d’obtenir des listes de départements et des téléphones pour une personne en particulier. Donc, utilisez simplement array_agg (ou string_agg ou json_agg):

SELECT
    p.id,
    p.person_name,
    array_agg(d.department_name) as "department_names",
    array_agg(c.phone_number) as "phone_numbers"
FROM person AS p
LEFT JOIN department AS d ON p.id = d.person_id
LEFT JOIN contact AS c on p.id = c.person_id
GROUP BY p.id, p.person_name
2
alexpods

Bien que les tableaux soient évidemment simplifiés pour la discussion, il semble qu’ils soient structurellement défectueux. Les tableaux doivent être structurés de manière à montrer les relations entre les entités, plutôt que de simples listes d'entités et/ou d'attributs. Et je considérerais qu'un numéro de téléphone est un attribut (d'une personne ou d'un département) dans ce cas.

La première étape serait de créer des tables avec des relations, chacune ayant une clé primaire et éventuellement une clé étrangère. Dans cet exemple, il serait utile que la table person utilise id_personne pour la clé primaire et que la table department utilise department_id pour sa clé primaire. Recherchez ensuite des relations un à plusieurs ou plusieurs à plusieurs et définissez vos clés étrangères en conséquence:

  • Si une personne ne peut être que dans un département à la fois, vous avez alors un (département) à plusieurs (personnes). Aucune clé étrangère dans la table department, mais department_id sera une clé étrangère dans la table personnes.
  • Si une personne peut appartenir à plus d'un ministère, vous en avez plusieurs, et vous aurez besoin d'une table de jonction supplémentaire avec person_id et department_id comme clés étrangères.

Pour résumer, votre scénario ne devrait comporter que deux tables: une table pour la personne et l'autre table pour le service. Même en tenant compte des numéros de téléphone personnels (une colonne dans la table des personnes) et des numéros de département dans la table des départements, ce serait une meilleure approche. 

Le seul inconvénient est qu'un ministère possède plusieurs numéros (ou plus d'un ministère partage le même numéro de téléphone), mais cela irait au-delà de la portée de la question initiale.

1
KiloVoltaire

Utilisez ce type de requête: SQL Server
(Vous pouvez modifier id de ORDER BY id en chaque colonne de votre choix)

SELECT 
    p.id, 
    p.person_name, 
    d.department_name, 
    c.phone_number
FROM
    person p
    LEFT JOIN 
    (SELECT *, ROW_NUMBER() OVER (PARTITION BY person_id ORDER BY id) AS seq
     FROM department) d 
    ON d.person_id = p.id And d.seq = 1
    LEFT JOIN 
    ( SELECT *, ROW_NUMBER() OVER (PARTITION BY person_id ORDER BY id) AS seq
     FROM contact) c 
    ON c.person_id = p.id And c.seq = 1;
0
shA.t
SELECT p.id, p.person_name, d.department_name, c.phone_number 
FROM person p
  LEFT JOIN department d 
    ON p.id = d.person_id
  LEFT JOIN contact c 
    ON p.id = c.person_id 
group by p.id, p.person_name, d.department_name, c.phone_number
0
JumboClip