Подзапрос вернул более одной строки

Бывает так, что подзапрос одиночной строки возвращает более одной строки. В таком случае возникнет ошибка.

Для каждого магазина найдем одного сотрудника с должностью 'MANAGER'.

SELECT s.store_id,
       s.name,
       (SELECT e.employee_id
          FROM employee e
         WHERE e.store_id = s.store_id
           AND e.rank_id = 'MANAGER'
       ) AS employee_id
  FROM store s
 ORDER BY s.store_id
error: more than one row returned by a subquery used as an expression

Посмотрим, что там с данными не так?

SELECT e.store_id,
       e.rank_id,
       e.last_name || ' ' || e.first_name AS full_name 
  FROM employee e
 WHERE e.rank_id = 'MANAGER'
 ORDER BY e.store_id, e.last_name, e.first_name
# store_id rank_id full_name
1 100 MANAGER Тарасов Глеб
2 200 MANAGER Калашников Владислав
3 201 MANAGER Зайцева Никки
4 201 MANAGER Тимофеева Доминика
5 300 MANAGER Бирюков Владислав
6 301 MANAGER Емельянова Кристина
7 302 MANAGER Кондратьева Марина
8 400 MANAGER Медведев Валерий
9 500 MANAGER Картман Эрик
10 600 MANAGER Воробьёв Николай
11 600 MANAGER Калинин Мирон
12 600 MANAGER Кулаков Вилен
13 800 MANAGER Белозёров Зиновий
14 900 MANAGER Авдеев Павел

В магазине 201 два менеджера, а в магазине 600 - три.

В такой ситуации есть два варианта развития событий. Мы можем решать неправильную задачу. Если в магазине может быть несколько менеджеров, то мы должны работать с массивом менеджеров. В таком случае может быть приемлемым получить одного из них, например, первого по алфавиту.

Возможно, менеджер в магазине должен быть только один. Просто кто-то не создал уникальный ключ, и пользователи создали несколько менеджеров в магазине. В таком случае, мы написали правильный запрос и необходимо избавляться от неправильных данных и дорабатывать структуру данных.

В любом случае, необходимо гарантировать, что в результате подзапроса будет возвращено не более одной строки. Для этого необходимо одно из:

  • должен быть уникальный ключ, гарантирующий, что в результате подзапроса будет не более одной строки;
  • использовать агрегатную функцию;
  • использовать LIMIT 1 для ограничения количества строк.

Воспользуемся LIMIT 1:

SELECT s.store_id,
       s.name,
       (SELECT e.employee_id
          FROM employee e
         WHERE e.store_id = s.store_id
           AND e.rank_id = 'MANAGER'
         ORDER BY e.last_name,
                  e.first_name,
                  e.middle_name
         LIMIT 1
       ) AS employee_id
  FROM store s
 ORDER BY s.store_id
# store_id name employee_id
1 100 Пионер 11
2 200 Марс 12
3 201 Сатурн 21
4 300 Адалин 13
5 301 Адалин-family 22
6 302 Адалин-ultra 24
7 400 Европа 14
8 500 Март 15
9 600 Umi 16
10 800 Универсам 19
11 900 Big 20

P.S. Если нам нужен список ФИО, то можно воспользоваться string_agg:

SELECT s.store_id,
       s.name,
       (SELECT string_agg (
                  e.last_name || ' ' || e.first_name, '; '
                  ORDER BY e.last_name,
                           e.first_name
               )
          FROM employee e
         WHERE e.store_id = s.store_id
           AND e.rank_id = 'MANAGER'
       ) AS employees
  FROM store s
 ORDER BY s.store_id
# store_id name employees
1 100 Пионер Тарасов Глеб
2 200 Марс Калашников Владислав
3 201 Сатурн Зайцева Никки; Тимофеева Доминика
4 300 Адалин Бирюков Владислав
5 301 Адалин-family Емельянова Кристина
6 302 Адалин-ultra Кондратьева Марина
7 400 Европа Медведев Валерий
8 500 Март Картман Эрик
9 600 Umi Воробьёв Николай; Калинин Мирон; Кулаков Вилен
10 800 Универсам Белозёров Зиновий
11 900 Big Авдеев Павел

Таких функций в PostgreSQL довольно много, и они заслуживают отдельной темы.

6.2 Коррелированный подзапрос

6.4 Подзапрос не вернул строк