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
,department
etcontact
.
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.
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 ...
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
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:
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.
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;
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