SQL-запрос, чтобы восстановить два стола связал многих со многими многим с внешними объединениями

Мой смысл SQL не работает этим утром, и я нуждаюсь в некоторой помощи.

У меня есть 3 первичных таблицы с двумя промежуточными столами между ними используемый для many-many отношений: пользователь , users_groups , группа , groups_permissions и разрешение . Каждый у разрешения есть имя .

Я хотел бы написать вопрос, который, учитывая имя разрешения и идентификатор пользователя, возвращает соответствующий id разрешения , если разрешение существует , и также возвращает стоимость, если у пользователя еще есть разрешение ПУСТОЙ УКАЗАТЕЛЬ. То есть я хочу знать и если разрешение существует и если у пользователя есть разрешение в одном вопросе. Продукция должна выглядеть примерно так:

                         |has_permission | does_not_have_permission
-------------------------------------------------------------------
permission_exists        |     (1,1)     |        (1,NULL)
permission_does_not_exist|      N/A      |        [0 rows]

Прямо сейчас вопрос, о котором я могу думать, выглядит примерно так:

SELECT p.id, ug.user_id
FROM permission AS p LEFT JOIN
     groups_permissions AS gp ON p.id = gp.permission_id LEFT JOIN
     group AS g ON gp.group_id = g.id LEFT JOIN
     users_groups AS ug ON ug.group_id = g.id AND ug.user_id = ?
WHERE p.name = ?;

Проблема состоит в том, что это возвращает больше чем один ряд в определенных случаях. Я мог бросить ORDER BY u.id DESC и ПРЕДЕЛ 1 на него, я думаю, но это оптимизирует хорошо? Каковы индексы, в которых я нуждаюсь для этого? Есть ли иначе, что я мог написать этот вопрос, чтобы получить информацию, которую я ищу быстро и легко?

править: для тех, кому любопытно, я использую PostgreSQL, хотя я хотел бы, чтобы вопрос был db агностиком, если это возможно.

1
nl ja de

3 ответы

Добавление <Закодировало> бы отличный работа для вашего вопроса?

SELECT distinct p.id, ug.user_id
FROM permission AS p LEFT JOIN
     groups_permissions AS gp ON p.id = gp.permission_id LEFT JOIN
     group AS g ON gp.group_id = g.id LEFT JOIN
     users_groups AS ug ON ug.group_id = g.id AND ug.user_id = ?
WHERE permission.name = ?;

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

Проблема в вашем комментарии может быть решена, делая скопление:

SELECT p.id, max(ug.id),
       (case when max(ug.id) is null then 'DENIED' else 'ALLOWED' end)
FROM permission AS p LEFT JOIN
     groups_permissions AS gp ON p.id = gp.permission_id LEFT JOIN
     group AS g ON gp.group_id = g.id LEFT JOIN
     users_groups AS ug ON ug.group_id = g.id AND ug.user_id = ?
WHERE permission.name = ?
group by ug.id
2
добавлено
Проблема состоит в том, что у вас может быть случай, куда он возвращает (1,1) и (1, ПУСТОЙ УКАЗАТЕЛЬ) . Это происходит, когда разрешение принадлежит двум группам, но пользователь только принадлежит одному из них.
добавлено автор dave mankoff, источник
Я думаю в вашем втором вопросе, вы хотите группироваться p.id , и требование МАКС (ug.id) вместо наоборот. Это, кажется, работает. Какая-либо идея, как оценить, если она измеряет или каковы индексы должны быть?
добавлено автор dave mankoff, источник
SELECT p.id, u.user_id
FROM permission AS p 
  LEFT JOIN
    user AS u 
      ON  u.user_id = ? 
      AND EXISTS
          ( SELECT *
            FROM users_groups AS ug 
              JOIN groups_permissions AS gp 
                ON gp.group_id = ug.group_id
            WHERE p.id = gp.permission_id 
              AND u.user_id = ug.user_id
          )
WHERE p.name = ? ;
1
добавлено

ЖАЛЬ - Просто видел PostreSQL и db агностическое предложение у основания оригинального вопроса. Этот ответ, вероятно, применится только к SQL Server MS.

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

WITH PermissionsForUser AS
(
SELECT
  p.id,
  u.id
FROM permissions p
LEFT JOIN groups_permissions gp
ON p.id = gp.permission_id
LEFT JOIN users_groups ug
ON ug.group_id = gp.group_id
LEFT JOIN users u
ON ug.user_id = u.id
WHERE p.name = @PermissionId
AND u.id = @UserId
)
SELECT
  MAX(p.id) AS permission_id
  MAX(u.id) AS user_id
FROM PermissionsForUser;
0
добавлено
You' d должен переместиться u.id = @UserId из , где и к условию объединения, начиная с него u.id может быть пустым. Вопрос остается, хотя - будет, звоня макс.() масштаб хорошо? Каковы необходимые индексы были бы?
добавлено автор dave mankoff, источник
Возможно, я неправильно понимаю, как SQL Server работает, но помещение, что условие в , ГДЕ пункт означает его, только возвратит ряды, которые имеют оба p.name и u.id соответствие. Это означает I' ll никогда не возвращают результат, где p.id установлен, но u.id ПУСТОЕ, потому что , ГДЕ пункт отфильтровывает все ПУСТЫЕ УКАЗАТЕЛИ. Я на самом деле сделал ту ошибку в первой версии моего вопроса и должен был переместить его в СОЕДИНЕНИЕ .
добавлено автор dave mankoff, источник
Не уверенный, почему необходимо было бы переместить u.id сравнение. I' m проверяющий на равенство известной стоимости. В SQL Server сравнения ПУСТОГО УКАЗАТЕЛЯ возвращают ПУСТОЙ УКАЗАТЕЛЬ, не верный. Вы могли также удалить, АННУЛИРУЕТ от сравнения в целом с: ГДЕ ISNULL (u.id, 0) = ISNULL (@UserId, 0) МАКС может измерить хорошо предположение, что вы поддерживаете его с правильными индексами.
добавлено автор BStateham, источник
Я понимаю теперь. Я don' t думают I' m помощь вы с вашим фактическим вопросом, но так же, как предмет обсуждения, другая возможность на тесте u.id были бы ТО, ГДЕ u.id ПУСТОЙ ИЛИ u.id = @UserID. Don' t думают, что это оказывает любое реальное влияние на пункт вашего оригинального q.
добавлено автор BStateham, источник
SqlCom.ru - Стиль жизни SQL
SqlCom.ru - Стиль жизни SQL
908 участник(ов)

Правила чата - https://t.me/sqlcom/88269 @sqlcom - основной канал (только MS SQL) @sql_ninja - второй канал (SQL вопросы начального уровня и свободное общение) @Gopnegbot - Викторина по SQL Server (наберите в привате /quiz). Предложения в @sql_ninja

SQL_Ninja
SQL_Ninja
340 участник(ов)

Правила чата - https://t.me/sqlcom/88269 @sqlcom - основной канал (только SQL) @sql_ninja - второй канал (SQL вопросы начального уровня и свободное общение) @Gopnegbot - Викторина по SQL Server (наберите в привате /quiz)