Как выбрать только одну строку из таблицы с внешним соединением?

Есть две таблицы.

Пользователи:

USER_ID USER_NAME
1 Иван1
2 Иван2
3 ИванЗ
4 Иван4
5 Иван5

Телефоны:

USER_ID USER_PHONE PHONE_MAIN
1 +79999999999 1
1 +78888888888 0
2 +77777777777 1
2 +76666666666 0
3 +75555555555 0
4 +74444444444 1

Необходимо объединить эти две таблицы в одну, но при этом телефон должен подвязаться только один раз, с условием, что если если есть основной (phone_main=1), то присоединить эту запись, если основного нет, то присоединить не основной (phone_main=0)

Как можно сделать оптимизированный запрос, чтобы итоговая таблица получилась такой:

USER_ID USER_NAME USER_PHONE
1 Иван1 +79999999999
2 Иван2 +77777777777
3 ИванЗ +75555555555
4 Иван4 +74444444444
5 Иван5 пусто

Есть ли возможность в join сделать проверку?

select tu.user_id, tu.user_name, tp.user_phone
from table_user tu
left join table_phone tp on tp.user_id=tu.user_id and (tp.phone_main = 1 or tp.phone_main = 0)

Вышеуказанный код возвращает две строки, т.к. or учитывает и то и другое.

Пока только такое решение нашел:

select tu.user_id, tu.user_name, coalesce(tp1.user_phone, tp2.user_phone)
from table_user tu
left join table_phone tp1 on tp1.user_id=tu.user_id and tp1.phone_main = 1
left join table_phone tp2 on tp2.user_id=tu.user_id and tp2.phone_main = 0

Ответы (2 шт):

Автор решения: Akina

Используйте коррелированный подзапрос:

SELECT tu.user_id, 
       tu.user_name, 
       ( SELECT tp.user_phone
         FROM table_phone tp
         WHERE tp.user_id=tu.user_id
         ORDER BY phone_main DESC LIMIT 1 ) user_phone
FROM table_user tu

А для PL/SQL используйте оконную функцию. Например:

WITH cte AS (
SELECT tu.user_id, 
       tu.user_name, 
       tp.user_phone, 
       ROW_NUMBER() OVER (PARTITION BY tp.user_id ORDER BY tp.phone_main DESC) rn
FROM table_user tu
LEFT JOIN table_phone tp ON tp.user_id=tu.user_id
)
SELECT * FROM cte WHERE rn = 1
→ Ссылка
Автор решения: 0xdb

при этом телефон должен подвязаться только один раз

То есть задача, взять из связаной таблицы только первую подходящую запись.

Решение со стандартным соединением:

select u.user_id, u.user_name, p.user_phone
from table_users u
left join (
    select user_id, 
        max (user_phone) keep (dense_rank first order by phone_main desc) user_phone
    from table_phone
    group by user_id) p on p.user_id=u.user_id
/    

Или более эффективно с коррелированным соединением таблиц:

select u.user_id, u.user_name, p.user_phone
from table_users u
outer apply (
    select max (user_phone) keep (dense_rank first order by phone_main desc) user_phone
    from table_phone p
    where p.user_id=u.user_id) p
/    

Оба запроса вернут ожидаемый результат (на db<>fiddle):

  USER_ID USER_NAME    USER_PHONE  
---------- ------------ ------------
         1 Иван1        +79999999999
         2 Иван2        +77777777777
         3 ИванЗ        +75555555555
         4 Иван4        +74444444444
         5 Иван5        пусто       
→ Ссылка