2 votes

oracle sql multigrouping

J'ai une base de données Oracle 11.2 et une question intéressante.

Il se trouve que j'avais un tableau qui ressemblait à

USER_ID ROLE_ID  
user1   role1  
user1   role2  
user1   role3  
user2   role1  
user2   role4  
user3   role2  
user3   role6  
user3   role5  
user4   role1  

Mon objectif est d'obtenir le plus petit nombre de rôles possible pour couvrir tous les utilisateurs. Dans ce cas, il s'agit de rôle1+rôle2.

Je me suis dit que je devais le faire :

  1. Obtenir toutes les combinaisons disponibles comme

    1er rôle du 1er utilisateur + 1er rôle du 2ème utilisateur +....1ème rôle du nième utilisateur
    1er rôle du 1er utilisateur + 1er rôle du 2ème utilisateur +....2ème rôle du nième utilisateur
    ....
    nième rôle du 1er utilisateur + nième rôle du 2ème utilisateur +....nième rôle du 1er utilisateur +....nième rôle du 2ème utilisateur +....nième rôle du 2ème utilisateur le du nième utilisateur
    J'obtiens ainsi toutes les combinaisons possibles

  2. J'ai besoin de supprimer les doublons de toutes les lignes que j'ai obtenues.

  3. Après avoir supprimé les doublons, je dois obtenir la ligne avec la plus petite longueur/le plus petit nombre de séparateurs.

Le problème est que je ne sais pas comment obtenir toutes les combinaisons de rôles possibles. J'ai essayé d'expérimenter avec "connect by level" mais je n'ai même pas réussi à obtenir ce que j'attendais.

Quelqu'un peut-il m'aider ?

0voto

Gordon Linoff Points 213350

L'une des méthodes est une approche itérative. Commencez par un rôle :

select r.*
from roles r
where (select count(*) from user_roles ur where ur.role_id = r.role_id) =
      (select count(distinct ur.user_id) from user_roles ur);

Puis deux :

select r1.*, r2.*
from roles r1 join
     roles r2
     on r1.role_id < r2.role_id
where (select count(*) from user_roles ur where ur.role_id in (r1.role_id, r2.role_id)) =
      (select count(distinct ur.user_id) from user_roles ur);

Et continuez jusqu'à ce que la requête renvoie un ou plusieurs ensembles de rôles.

Dans la version 12+, vous pouvez utiliser un CTE récursif. Vous pourriez le faire en utilisant connect Mais l'exécution de plusieurs requêtes comme celle-ci pourrait être un moyen plus rapide de parvenir à la solution.

0voto

Matthew McPeak Points 9107

Un problème intéressant. Voici une solution pour autant que le nombre de rôles dans votre système soit inférieur à 128. (Tout nombre supérieur à 128 rôles distincts entraînera la création de BITAND à l'échec).

L'approche repose sur la création d'un jeu de pouvoirs ("P") de votre ensemble de rôles et sur la recherche de l'ensemble ("Q") contenant des membres de "P" qui couvrent tous les utilisateurs. Il s'agit ensuite de trouver les membres de "Q" ayant le moins de rôles distincts.

Aucune promesse de performance n'est faite ici. Il s'agit d'une approche de force brute, mais les problèmes de type combinaison nécessitent souvent une force brute.

with user_role_data (USER_ID, ROLE_ID) AS (
SELECT 'user1','role1' FROM DUAL UNION ALL 
SELECT 'user1','role2' FROM DUAL UNION ALL  
SELECT 'user1','role3' FROM DUAL UNION ALL  
SELECT 'user2','role1' FROM DUAL UNION ALL
SELECT 'user2','role4' FROM DUAL UNION ALL
SELECT 'user3','role2' FROM DUAL UNION ALL
SELECT 'user3','role6' FROM DUAL UNION ALL
SELECT 'user3','role5' FROM DUAL UNION ALL
SELECT 'user4','role1' FROM DUAL )
-- End of sample data
, 
distinct_roles as ( SELECT distinct role_id FROM user_role_data ORDER BY role_id),
numbered_roles as ( SELECT power(2, rownum-1) role_ps_id, role_id FROM distinct_roles),
-- There will be 2**n entries in the powerset, where n = the number of distinct roles
ps_driver as ( SELECT rownum-1 ps_id FROM DUAL connect by ROWNUM <= power(2, ( SELECT count(*) FROM distinct_roles))),
-- Create a powerset of all the roles.
powerset as ( 
  SELECT psd.ps_id, r.role_id
  FROM   ps_driver psd
  CROSS APPLY ( SELECT  nr.role_id
                FROM    numbered_roles nr
                -- Note: this bit requires that the # of roles be less than 128.
                WHERE   bitand(nr.role_ps_id, psd.ps_id) > 0 ) r ),
-- Build a summary of each power set, just for display purposes
powerset_summary as (
  SELECT ps_id,         
         listagg(role_id,',') within group ( order by role_id ) role_list
  FROM   powerset
  GROUP BY ps_id ),
-- Get the sets the cover 100% of the users and the size and contents of each set
ps_users as ( 
  SELECT   ps.ps_id, 
           count(distinct urd.user_id) covered_users, 
           count(distinct ps.role_id) required_roles,
           ( SELECT role_list FROM powerset_summary pss WHERE pss.ps_id = ps.ps_id ) role_list
  FROM     powerset ps
  INNER JOIN user_role_data urd ON urd.role_id = ps.role_id
  GROUP BY ps.ps_id
  HAVING count(distinct urd.user_id) = ( SELECT count(distinct urd2.user_id) from user_role_data urd2 ) 
  )
-- List the sets that cover all the users with the fewest number of roles
select * from ps_users
order by required_roles
fetch first 1 row with ties;
+-------+---------------+----------------+-------------+
| PS_ID | COVERED_USERS | REQUIRED_ROLES |  ROLE_LIST  |
+-------+---------------+----------------+-------------+
|     3 |             4 |              2 | role1,role2 |
|    33 |             4 |              2 | role1,role6 |
|    17 |             4 |              2 | role1,role5 |
+-------+---------------+----------------+-------------+

Version compatible avec Oracle 11.2

with user_role_data (USER_ID, ROLE_ID) AS (
SELECT 'user1','role1' FROM DUAL UNION ALL 
SELECT 'user1','role2' FROM DUAL UNION ALL  
SELECT 'user1','role3' FROM DUAL UNION ALL  
SELECT 'user2','role1' FROM DUAL UNION ALL
SELECT 'user2','role4' FROM DUAL UNION ALL
SELECT 'user3','role2' FROM DUAL UNION ALL
SELECT 'user3','role6' FROM DUAL UNION ALL
SELECT 'user3','role5' FROM DUAL UNION ALL
SELECT 'user4','role1' FROM DUAL )
-- End of sample data
, 
distinct_roles as ( SELECT distinct role_id FROM user_role_data ORDER BY role_id),
numbered_roles as ( SELECT power(2, rownum-1) role_ps_id, role_id FROM distinct_roles),
-- There will be 2**n entries in the powerset, where n = the number of distinct roles
ps_driver as ( SELECT rownum-1 ps_id FROM DUAL connect by ROWNUM <= power(2, ( SELECT count(*) FROM distinct_roles))),
-- Create a powerset of all the roles.
powerset as ( 
  SELECT psd.ps_id, nr.role_id
  FROM   ps_driver psd 
  CROSS JOIN numbered_roles nr 
  WHERE  bitand(nr.role_ps_id, psd.ps_id) > 0 ),
-- Build a summary of each power set, just for display purposes
powerset_summary as (
  SELECT ps_id,         
         listagg(role_id,',') within group ( order by role_id ) role_list
  FROM   powerset
  GROUP BY ps_id ),
-- Get the sets the cover 100% of the users and the size and contents of each set
ps_users as ( 
  SELECT   ps.ps_id, 
           count(distinct urd.user_id) covered_users, 
           count(distinct ps.role_id) required_roles,
           ( SELECT role_list FROM powerset_summary pss WHERE pss.ps_id = ps.ps_id ) role_list,
           dense_rank() over ( order by count(distinct ps.role_id)) result_number
  FROM     powerset ps
  INNER JOIN user_role_data urd ON urd.role_id = ps.role_id
  GROUP BY ps.ps_id
  HAVING count(distinct urd.user_id) = ( SELECT count(distinct urd2.user_id) from user_role_data urd2 ) 
  )
select * from ps_users
where result_number = 1
order by required_roles;

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X