#sql #postgresql #subquery #left-join #knex.js
#sql #postgresql #подзапрос #левое соединение #knex.js
Вопрос:
У меня есть одна таблица пользователей.
users
| user_id | name |
| ------- | ------ |
| 1 | Jerry |
| 2 | George |
| 3 | Elaine |
| 4 | Kramer |
У меня есть одна таблица, которая связывает роли с пользователями, а роли назначаются в дереве.
user_roles
| user_id | role_id | tree_id |
| ------- | ------- | ------- |
| 1 | 5 | 1 |
| 1 | 5 | 2 |
| 2 | 6 | 1 |
| 3 | 7 | 1 |
| 4 | 8 | 1 |
Мне нужно возвращать результаты только в тех случаях, когда роль пользователя назначается с определенным tree_id, поэтому я проверяю все роли и деревья. В конце я хочу, чтобы он возвращал одну строку для каждого пользователя.
Я использую Knex и выполняю запрос, который выглядит так:
knex('users')
.leftJoin('user_roles', {'user.user_id': 'user_roles.user_id'})
.whereIn('user_roles.tree_id', arrayOfTreeIds)
.andWhere(moreFilters)
SELECT *
FROM users
LEFT JOIN user_roles on users.user_id = user_roles.user_id
WHERE user_roles.tree_id in (1, 2, 3)
Однако я получаю пять результатов вместо четырех. Если я попытаюсь SELECT DISTINCT
, он скажет мне, что мне нужно GROUP BY
, но я не могу заставить это работать. Что мне нужно сделать, чтобы получить только один результат для каждого идентификатора пользователя?
Ответ №1:
У вас есть пользователь, который совпадает с двумя разными tree_id
s, поэтому это умножает строки.
В чистом SQL вы могли бы использовать exists
вместо join
:
SELECT *
FROM users u
WHERE EXISTS (
SELECT 1
FROM user_roles ur
WHERE ur.user_id = u.user_id AND ur.tree_id in (1, 2, 3)
)
Другой вариант — агрегирование:
SELECT u.*
FROM users u
INNER JOIN user_roles ur on u.user_id = ur.user_id
WHERE ur.tree_id in (1, 2, 3)
GROUP BY u.user_id
Я изменил значение LEFT JOIN
на an INNER JOIN
, потому что это, по сути, то, что вы хотите (и что делает ваш исходный запрос).
Вы даже можете перечислить совпадающие роли с помощью агрегирования строк:
SELECT u.*, STRING_AGG(ur.tree_id::text, ',' ORDER BY ur.tree_id) tree_ids
FROM users u
INNER JOIN user_roles ur on u.user_id = ur.user_id
WHERE ur.tree_id in (1, 2, 3)
GROUP BY u.user_id
Отказ от ответственности: я не знаю, как написать это в knex!
Комментарии:
1. Привет! Спасибо за все ваши быстрые и подробные ответы. Сначала попробуйте ваш средний, так как его было проще всего перевести на knex (к сожалению, я должен его использовать), но я продолжаю получать сообщение об ошибке:
"user_roles.user_id" must appear in the GROUP BY clause or be used in an aggregate function
и я не могу понять, почему…2. Я тоже попробовал первый и получил
column "user.user_id" must appear in the GROUP BY clause or be used in an aggregate function
…3. К сожалению, 2-й вариант не сработал для меня, но первый работает, если я добавляю группу by. Это больше не дает мне правильного подсчета, но это прогресс!
4. @TaniaRascia: это удивительно. Я добавил ссылку на скрипку к своему ответу: как вы можете видеть, запросы не выдают ошибок (ПРИМЕЧАНИЕ: предполагается, что второй запрос
user_id
является первичным ключом таблицыusers
).