简体   繁体   中英

How can I make a this selection of groups and members simpler and more efficient?

Background

I've got a group table, users table, and a group_members table.

groups { group_ varchar(50) not null, etc... }
users  { user_id varchar(50) not null, etc... }
group_members { group_ varchar(50 not null, member varchar(50) not null }

My requirements state that a group can have other groups as members. The user needs to be considered a member of all groups that any group they are a member of.
For Example, consider the following data in these tables:

group_members              | groups         | users    |
========================== | ============== | ======== |
group_          member     | group_         | user_id  |
-------------------------- | -------------- | -------- |
'SYSTEM_ADMIN'  'OE_ADMIN' | 'SYSTEM_ADMIN' | 'USER    |
'SYSTEM_ADMIN'  'AR_ADMIN' | 'OE_ADMIN'     |          |
'SYSTEM_ADMIN'  'USER'     | 'AR_ADMIN'     |          |

My desired result of asking the question What groups is 'USER' a member of? should be

member
==============
'SYSTEM_ADMIN'
'OE_ADMIN'
'AR_ADMIN'

Question

I've got the following query built and providing me with the required results, but it looks a little complex.

WITH GM
AS (
    SELECT GROUP_, MEMBER FROM group_members 
       WHERE member IN (SELECT group_ FROM groups)
)
SELECT group_ FROM group_members WHERE member = 'USER'
UNION
SELECT MEMBER AS GROUP_ FROM GM 
   WHERE group_ in (SELECT group_ FROM group_members WHERE member = 'USER')

Any suggestions on how to make this query simpler, or less cluttered?

Your recursive CTE looks fine.

How does it perform?

I don't think there's any significant way to make it a lot simpler - the recursive CTEs always look kind of messy.

The WHERE IN is equivalent to a JOIN, but I don't see it as being more readable and the execution plan should be pretty equivalent.

Your root row could be represented as a CTE before the recursive one, but it won't really save much in readability:

WITH root AS ()
     ,CTE AS ()
SELECT FROM ROOT 
    UNION
SELECT FROM CTE

In any case, you could put this in a view or inline table-valued-function (with a parameter) to make it easy to use from elsewhere in the system so you don't have to see it very much.

Something I've done for hierarchies is to build a more high-performance flattened/denormalized version and update it on a trigger or timer and use that as a read-only performance enhancer. For instance, in a technical support system we build where a question could be tagged in a hierarchy, we filled in all the parents (printers->hp->laserjet) when a child was selected, so it was easy to query for problems with hp printers on any version of Windows or problems with laserjets on Windows XP.

Based on @Cade Roux's answer, further reflection guided me to the following Stored Procedure

ALTER PROCEDURE GetUserGroups
@user_id varchar(50)
AS
BEGIN
SET NOCOUNT ON;

SELECT group_ FROM group_members WHERE member = @user_id
UNION
SELECT MEMBER AS GROUP_ FROM group_members
WHERE group_ IN (SELECT group_ FROM group_members WHERE member = @user_id) 
      AND member != @user_id
END

This simplifies the original query, and properly fulfills my requirement to get back a list of group names that @user_id is a member of, as well as the names of the groups that are members of the top-level group.

This code only allows groups in groups to one level.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM