2011-09-19 3 views
2

3 sont définies comme suit:Aide-moi à cette table requête SQL

utilisateurs

User_ID  INT 
First_Name VARCHAR 
Last_Name VARCHAR 
Email  VARCHAR 

Rôles

Role_ID  INT 
Role_Name VARCHAR 
Access_Level INT 

Roles_Users

User_ID  INT 
Role_ID  INT 

Roles_Users est une table de liaison many-to-many entre Users et Roles. Je veux retirer les informations suivantes:

First_Name, Last_Name, Email, Role_Name 

Ce que j'ai à ce jour est:

SELECT 
    U.First_Name, 
    U.Last_Name, 
    U.Email, 
    R.Name AS Role_Name 
FROM Users U 
INNER JOIN Roles_Users RU ON U.User_ID = RU.User_ID 
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID 

La partie la plus délicate (au moins pour moi) est que je veux tirer seulement revenir le Role_Name avec le MIN(Access_Level) pour cet utilisateur particulier. Donc, fondamentalement, le jeu d'enregistrements que je veux tirer aura chaque utilisateur seulement une fois avec leur nom de rôle de niveau d'accès le plus bas.

Je suis sûr que c'est assez simple mais ça me bloque tout de suite.

Merci

+0

C'est pourquoi je déteste les tables * many-to-many *. –

Répondre

3

Vous pouvez utiliser un CTE (Common Table Expression) en conjonction avec la fonction ROW_NUMBER de fenêtrage comme ceci:

;WITH MinAccessData AS 
(
SELECT 
    U.First_Name, 
    U.Last_Name, 
    U.Email, 
    R.Name AS Role_Name, 
    ROW_NUMBER() OVER(PARTITION BY U.User_ID ORDER BY R.Access_Level) AS RowNum 
FROM Users U 
INNER JOIN Roles_Users RU ON U.User_ID = RU.User_ID 
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID 
) 
SELECT * 
FROM MinAccessData 
WHERE RowNum = 1 

CTE "partitions" de vos données par User_ID, par exemple chaque utilisateur obtient une "partition". A l'intérieur de cette partition, les rôles sont classés par Access_level et le plus petit est le premier - donc il obtient RowNum = 1 - pour chaque utilisateur.

Vous sélectionnez ensuite dans ce CTE toutes les entrées où RowNum = 1 - ceci délivre toutes les entrées pour chaque utilisateur qui ont la plus petite valeur Access_Level.

+1

+1 J'ai envoyé quelque chose de très similaire avant que j'ai vu le vôtre. –

2

Alternatives sans CTE (juste pour avoir un autre outil dans votre boîte)

SELECT 
    U.First_Name, 
    U.Last_Name, 
    U.Email, 
    R.Name AS Role_Name 
FROM Users U 
INNER JOIN Roles_Users RU ON U.User_ID = RU.User_ID 
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID 
INNER JOIN (SELECT 
       MIN(r.Access_Level) access_level, 
       ru.UserID, 
      FROM Roles r 
       INNER JOIN Roles_Users ru 
       ON r.Role_ID = ru.Role_ID 
      GROUP BY UserID 
       ) minAccess 
ON ru.UserId = minAccess.UserId 
    and ru. 
    ON r.access_level = minAccess .access_level 

Vous pouvez également utiliser un CROSS APPLY

SELECT 
     U.First_Name, 
     U.Last_Name, 
     U.Email, 
     R.Name AS Role_Name 
    FROM Users U 
    CROSS APPLY (SELECT TOP 1 
        Role_Name 
        FROM Roles_Users RU ON U.User_ID = RU.User_ID 
         INNER JOIN Roles R ON RU.Role_ID = R.Role_ID 
        WHERE u.user_id = ru.user_id 
        ORDER BY 
         Access_Level desc 
        ) 
+1

Quel est le problème avec un CTE? :-) –

+1

Works - mais seulement pour "top 1" - essayez de faire "Top 3" ou "Top 5" avec ce ..... c'est pourquoi je ** aime ** le CTE! –

+0

@Aaron. Rien, sauf que marc_s était plus rapide Oh et c'est bien de savoir les solutions portables –

0

Non testé, mais il va quelque chose comme ça

SELECT 
    U.First_Name, 
    U.Last_Name, 
    U.Email, 
    R.Role_Name 
FROM Users U 
JOIN Roles_Users RU ON U.User_ID = RU.User_ID 
JOIN (
     SELECT ROLE_ID, MIN(ROLE_NAME) ROLE_NAME 
     FROM ROLES 
     GROUP BY ROLE_ID 
     HAVING ACCESS_LEVEL = MIN(ACCESS_LEVEL) 
    ) R ON RU.Role_ID = R.Role_ID 
0
SELECT Users.*, Roles.* 
FROM 
    Users 
    JOIN Roles_Users ON Users.User_ID = Roles_Users.User_ID 
    JOIN Roles ON Roles.Role_ID = Roles_Users.Role_ID 
WHERE 
    Access_Level = (
     SELECT MIN(Access_Level) 
     FROM 
      Roles_Users 
      JOIN Roles ON Roles.Role_ID = Roles_Users.Role_ID 
     WHERE Users.User_ID = Roles_Users.User_ID 
    ) 

NOTA Ne pas lister les utilisateurs sans aucun rôle.

1

Corrélées sous-requête:

SELECT 
    U.First_Name, 
    U.Last_Name, 
    U.Email, 
(
select top 1 R.RName from Roles_Users RU ON U.User_ID = RU.User_ID 
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID 
ORDER BY R.Access_Level 
) 
AS Role_Name 
FROM Users U 

À mon avis une sous-requête est plus facile à lire et à écrire. Dans ce code, la sous-requête corrélée exécutera 1x par ligne renvoyée.J'aime la solution de jointure interne de @ Conrad, la plus facile et probablement la plus performante, et probablement ce que j'utiliserais, juste en donnant ceci comme une autre option.