Предотвращение дублирования значений в LEFT JOIN
Я столкнулся с ситуацией, когда получил двойные значения от LEFT JOIN
. Я думаю, что это может быть желаемое поведение, но в отличие от того, что я хочу.
У меня есть три таблицы: person
, department
и contact
.
человек:
id bigint,
person_name character varying(255)
:
person_id bigint,
department_name character varying(255)
контакт:
person_id bigint,
phone_number character varying(255)
Запрос 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;
Результат -
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"
Я знаю, что такое объединение, сохраняя умноженное на выбранные строки. Но это имеет смысл, как номера телефонов 023451,99478,67890 для обоих отделов, в то время как они связаны только с человеком Джон с ненужными повторными значениями, которые эскалируют проблему с большим набором данных. Итак, вот что я хочу -
id|person_name|department_name|phone_number
--+-----------+---------------+------------
1 |"John" |"Finance" |"023451"
1 |"John" |"Marketing" |"99478"
1 |"John" |"" |"67890"
2 |"Barbara" |"Finance" |""
3 |"Michelle" |"" |"005634"
Это пример моей ситуации, и я использую большой набор таблиц и запросов. Итак, нужно иметь общее решение.
Ответы
Ответ 1
Мне нравится называть эту проблему "cross join by proxy" . Поскольку нет информации (условие WHERE
или JOIN
), как предполагается, что таблицы department
и contact
совпадают, они пересекаются через прокси-таблицу person
- давая вам Декартовский продукт. Очень похоже на это:
Больше объяснений там.
Решение для вашего запроса:
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;
Вы не определили, какой отдел или номер телефона выбрать, поэтому я произвольно выбрал первый. Вы можете использовать его любым другим способом...
Ответ 2
Думаю, вам просто нужно получить списки отделов и телефонов для определенного человека. Поэтому просто используйте array_agg
(или string_agg
или 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
Ответ 3
Несмотря на то, что таблицы, очевидно, упрощены для обсуждения, похоже, они структурно ошибочны. Таблицы должны быть структурированы для отображения отношений между объектами, а не просто списков сущностей и/или атрибутов. И в этом случае я считаю, что номер телефона является атрибутом (лица или подразделения).
Первым шагом будет создание таблиц с отношениями, каждый из которых имеет первичный ключ и, возможно, внешний ключ. В этом примере было бы полезно, чтобы таблица Person использовала person_id для первичного ключа, а таблица отдела использовала department_id для своего первичного ключа. Затем найдите отношения "один ко многим" или "многие ко многим" и соответствующим образом установите свои внешние ключи:
- Если один человек может находиться только в одном отделе за раз, то у вас есть один (отдел) -то-многие (лица). Нет внешнего ключа в таблице отдела, но department_id будет внешним ключом в таблице лиц.
- Если один человек может находиться в более чем одном отделе, у вас есть много-ко-многим, и вам понадобится дополнительная таблица соединений с person_id и department_id как внешние ключи.
Подводя итог, в вашем сценарии должны быть только две таблицы: одна таблица для человека и другая таблица для отдела. Даже с учетом личных телефонных номеров (столбец в таблице лиц) и номеров отделов в таблице отделов это было бы лучшим подходом.
Единственное предостережение - когда один отдел имеет много номеров (или более одного отдела разделяет один номер телефона), но это выходит за рамки исходного вопроса.
Ответ 4
Используйте этот тип запроса: SQL Server
(Вы можете изменить id
ORDER BY id
на каждый столбец, который вы хотите)
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;
Ответ 5
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