Skip to content

Hybrid Groups

Hybrid Groups combine the flexibility of internal group membership management with the dynamic capabilities of external provider-based membership. This model allows groups to have both explicitly managed members (internal) and automatically resolved members from external providers (external), providing the best of both worlds for complex organizational structures.

Overview

Hybrid Groups use both direct database storage and external provider mappings for comprehensive membership management:

graph TB
    subgraph "Hybrid Group Model"
        HG[Hybrid Group<br/>auth.user_group<br/>group_type = 'hybrid']

        subgraph "Internal Membership"
            GM[Group Members<br/>auth.user_group_member<br/>Direct membership storage]
            U1[User 1<br/>Direct Member]
            U2[User 2<br/>Direct Member]
        end

        subgraph "External Membership"
            MAP[Group Mappings<br/>auth.user_group_mapping<br/>Provider group/role mappings]
            U3[User 3<br/>Via Azure AD Groups]
            U4[User 4<br/>Via LDAP Groups]
            U5[User 5<br/>Via SAML Roles]
        end
    end

    HG --> GM
    HG --> MAP
    GM --> U1
    GM --> U2
    MAP --> U3
    MAP --> U4
    MAP --> U5

Key Characteristics: - Combines internal and external membership models - Supports both explicit and automatic membership - Flexible membership rules and override capabilities - Perfect for transitional or complex scenarios - Allows granular control over membership sources

Database Structure

Core Tables Integration

-- Hybrid group definition (same as internal/external)
CREATE TABLE auth.user_group (
    user_group_id uuid PRIMARY KEY,
    tenant_id uuid REFERENCES auth.tenant,
    code text NOT NULL,
    name text NOT NULL,
    description text,
    group_type text DEFAULT 'hybrid',  -- Specifies hybrid behavior
    is_active boolean DEFAULT true,
    created_at timestamptz DEFAULT now(),
    UNIQUE (tenant_id, code)
);

-- Internal membership storage (same as internal groups)
CREATE TABLE auth.user_group_member (
    user_group_id uuid REFERENCES auth.user_group,
    user_id uuid REFERENCES auth.user_info,
    added_at timestamptz DEFAULT now(),
    added_by uuid REFERENCES auth.user_info,
    is_active boolean DEFAULT true,
    membership_source text DEFAULT 'internal',  -- Track source
    PRIMARY KEY (user_group_id, user_id)
);

-- External mappings (same as external groups)
CREATE TABLE auth.user_group_mapping (
    user_group_mapping_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
    user_group_id uuid REFERENCES auth.user_group,
    provider_id uuid REFERENCES auth.provider,
    provider_group_pattern text,
    provider_role_pattern text,
    mapping_priority integer DEFAULT 100,
    is_active boolean DEFAULT true,
    created_at timestamptz DEFAULT now()
);

Creating Hybrid Groups

Basic Hybrid Group Creation

-- Create hybrid group
SELECT auth.create_user_group(
    _tenant_id := 'tenant-uuid',
    _code := 'PROJECT_ALPHA',
    _name := 'Project Alpha Team',
    _description := 'Project Alpha development team with mixed membership',
    _group_type := 'hybrid'
);
-- Returns: user_group_id (uuid)

-- Add external mappings (for automatic membership)
SELECT auth.create_user_group_mapping(
    _user_group_id := 'project-alpha-group-uuid',
    _provider_id := 'azure-ad-provider-uuid',
    _provider_group_pattern := 'Project-Alpha-*',
    _provider_role_pattern := 'Developer',
    _mapping_priority := 100
);

-- Add direct members (for explicit membership)
SELECT auth.create_user_group_member(
    _user_group_id := 'project-alpha-group-uuid',
    _user_id := 'external-consultant-uuid',
    _added_by := 'project-manager-uuid'
);

Complex Hybrid Group Examples

-- Leadership group: Mix of explicit appointments and role-based
DO $$
DECLARE
    v_tenant_id uuid := 'tenant-uuid';
    v_leadership_group_id uuid;
BEGIN
    -- Create hybrid leadership group
    v_leadership_group_id := auth.create_user_group(
        v_tenant_id,
        'LEADERSHIP',
        'Leadership Team',
        'Company leadership including executives and department heads',
        'hybrid'
    );

    -- Map external executive roles (automatic)
    PERFORM auth.create_user_group_mapping(
        v_leadership_group_id,
        'azure-ad-provider-uuid',
        NULL,  -- No group pattern
        'Executive',  -- Role pattern
        50  -- High priority
    );

    -- Map department head groups (automatic)
    PERFORM auth.create_user_group_mapping(
        v_leadership_group_id,
        'azure-ad-provider-uuid',
        'Department-Head-*',  -- Group pattern
        NULL,  -- No role pattern
        75  -- Medium-high priority
    );

    -- Add specific individuals (explicit)
    -- Board members, advisors, interim leaders
    INSERT INTO auth.user_group_member (user_group_id, user_id, added_by, membership_source)
    VALUES
        (v_leadership_group_id, 'board-member-1-uuid', 'ceo-uuid', 'board_appointment'),
        (v_leadership_group_id, 'advisor-1-uuid', 'ceo-uuid', 'advisory_appointment'),
        (v_leadership_group_id, 'interim-cto-uuid', 'ceo-uuid', 'interim_appointment');

    -- Assign leadership permissions
    PERFORM auth.assign_permission(
        v_tenant_id, NULL, v_leadership_group_id, 'LEADERSHIP_PERMISSIONS', NULL
    );
END $$;

Membership Resolution Logic

Hybrid Membership Query

Hybrid groups resolve membership from both sources:

-- Get all members of hybrid group (internal + external)
WITH hybrid_members AS (
    -- Internal members (explicitly added)
    SELECT DISTINCT
        ugm.user_group_id,
        ugm.user_id,
        'internal' as membership_type,
        ugm.added_at as membership_date,
        ugm.membership_source
    FROM auth.user_group_member ugm
    WHERE ugm.user_group_id = 'hybrid-group-uuid'
      AND ugm.is_active = true

    UNION

    -- External members (via provider mappings)
    SELECT DISTINCT
        ugmp.user_group_id,
        ui.user_id,
        'external' as membership_type,
        ui.last_login_at as membership_date,
        'provider_mapping' as membership_source
    FROM auth.user_group_mapping ugmp
    JOIN auth.user_identity ui ON ugmp.provider_id = ui.provider_id
    JOIN auth.user_info u ON ui.user_id = u.user_id
    WHERE ugmp.user_group_id = 'hybrid-group-uuid'
      AND ugmp.is_active = true
      AND ui.is_last_used = true
      AND ui.is_active = true
      AND u.is_active = true
      AND (
          (ugmp.provider_group_pattern IS NOT NULL
           AND EXISTS (
               SELECT 1 FROM unnest(ui.provider_groups) AS pg(group_name)
               WHERE pg.group_name ~ ugmp.provider_group_pattern
           ))
          OR
          (ugmp.provider_role_pattern IS NOT NULL
           AND EXISTS (
               SELECT 1 FROM unnest(ui.provider_roles) AS pr(role_name)
               WHERE pr.role_name ~ ugmp.provider_role_pattern
           ))
      )
)
SELECT
    hm.user_group_id,
    hm.user_id,
    u.username,
    u.display_name,
    hm.membership_type,
    hm.membership_date,
    hm.membership_source
FROM hybrid_members hm
JOIN auth.user_info u ON hm.user_id = u.user_id
ORDER BY hm.membership_type, u.display_name;

Membership Priority and Conflicts

-- Handle membership conflicts and priorities
CREATE OR REPLACE FUNCTION auth.resolve_hybrid_group_membership(
    _user_group_id uuid,
    _user_id uuid
) RETURNS TABLE (
    is_member boolean,
    membership_type text,
    membership_source text,
    effective_date timestamptz
) AS $$
DECLARE
    v_internal_member boolean := false;
    v_external_member boolean := false;
    v_internal_date timestamptz;
    v_external_date timestamptz;
BEGIN
    -- Check internal membership
    SELECT true, ugm.added_at INTO v_internal_member, v_internal_date
    FROM auth.user_group_member ugm
    WHERE ugm.user_group_id = _user_group_id
      AND ugm.user_id = _user_id
      AND ugm.is_active = true;

    -- Check external membership
    SELECT true, ui.last_login_at INTO v_external_member, v_external_date
    FROM auth.user_group_mapping ugmp
    JOIN auth.user_identity ui ON ugmp.provider_id = ui.provider_id
    WHERE ugmp.user_group_id = _user_group_id
      AND ui.user_id = _user_id
      AND ugmp.is_active = true
      AND ui.is_last_used = true
      AND ui.is_active = true
      AND (
          (ugmp.provider_group_pattern IS NOT NULL
           AND EXISTS (
               SELECT 1 FROM unnest(ui.provider_groups) AS pg(group_name)
               WHERE pg.group_name ~ ugmp.provider_group_pattern
           ))
          OR
          (ugmp.provider_role_pattern IS NOT NULL
           AND EXISTS (
               SELECT 1 FROM unnest(ui.provider_roles) AS pr(role_name)
               WHERE pr.role_name ~ ugmp.provider_role_pattern
           ))
      );

    -- Return membership result with priority (internal takes precedence)
    IF v_internal_member THEN
        RETURN QUERY SELECT true, 'internal'::text, 'explicit'::text, v_internal_date;
    ELSIF v_external_member THEN
        RETURN QUERY SELECT true, 'external'::text, 'provider_mapping'::text, v_external_date;
    ELSE
        RETURN QUERY SELECT false, 'none'::text, 'none'::text, NULL::timestamptz;
    END IF;
END;
$$ LANGUAGE plpgsql;

Managing Hybrid Group Membership

Adding Internal Members

-- Add explicit members to hybrid group
SELECT auth.create_user_group_member(
    _user_group_id := 'hybrid-group-uuid',
    _user_id := 'contractor-uuid',
    _added_by := 'manager-uuid'
);

-- Add multiple internal members with source tracking
DO $$
DECLARE
    v_group_id uuid := 'project-team-hybrid-uuid';
    v_admin_id uuid := 'project-manager-uuid';
    v_contractors uuid[] := ARRAY[
        'contractor-1-uuid', 'contractor-2-uuid', 'freelancer-uuid'
    ];
    v_user_id uuid;
BEGIN
    FOREACH v_user_id IN ARRAY v_contractors
    LOOP
        INSERT INTO auth.user_group_member
            (user_group_id, user_id, added_by, membership_source)
        VALUES
            (v_group_id, v_user_id, v_admin_id, 'contractor_assignment')
        ON CONFLICT (user_group_id, user_id) DO NOTHING;
    END LOOP;
END $$;

Managing External Mappings

-- Add additional external mappings to hybrid group
SELECT auth.create_user_group_mapping(
    _user_group_id := 'hybrid-group-uuid',
    _provider_id := 'ldap-provider-uuid',
    _provider_group_pattern := 'cn=Project-Alpha,ou=Projects,dc=company,dc=com',
    _provider_role_pattern := NULL,
    _mapping_priority := 80
);

-- Update mapping priority
UPDATE auth.user_group_mapping
SET mapping_priority = 90
WHERE user_group_id = 'hybrid-group-uuid'
  AND provider_id = 'azure-ad-provider-uuid';

Membership Override Patterns

-- Override external membership with explicit control
-- Example: Temporarily exclude user from external group
INSERT INTO auth.user_group_member (user_group_id, user_id, added_by, is_active, membership_source)
VALUES (
    'hybrid-group-uuid',
    'user-to-exclude-uuid',
    'admin-uuid',
    false,  -- Explicitly inactive
    'manual_override'
)
ON CONFLICT (user_group_id, user_id)
DO UPDATE SET
    is_active = false,
    membership_source = 'manual_override';

Common Hybrid Group Patterns

Transition from Internal to External

-- Gradual transition: Start with internal, add external mappings
DO $$
DECLARE
    v_group_id uuid := 'developers-group-uuid';
    v_tenant_id uuid := 'tenant-uuid';
BEGIN
    -- Update existing internal group to hybrid
    UPDATE auth.user_group
    SET group_type = 'hybrid'
    WHERE user_group_id = v_group_id;

    -- Add external mapping for new hires (automatic inclusion)
    PERFORM auth.create_user_group_mapping(
        v_group_id,
        'azure-ad-provider-uuid',
        'New-Developer-*',
        'Junior Developer',
        100
    );

    -- Add mapping for experienced developers
    PERFORM auth.create_user_group_mapping(
        v_group_id,
        'azure-ad-provider-uuid',
        NULL,
        'Senior Developer',
        80
    );

    -- Keep existing internal members (they remain explicit)
    -- New users will be added automatically via mappings
END $$;

Project-Based Hybrid Groups

-- Project team with core team (internal) + department members (external)
CREATE OR REPLACE FUNCTION create_project_hybrid_group(
    _tenant_id uuid,
    _project_code text,
    _project_name text,
    _core_team_members uuid[],
    _department_mapping text
) RETURNS uuid AS $$
DECLARE
    v_group_id uuid;
    v_user_id uuid;
BEGIN
    -- Create hybrid project group
    v_group_id := auth.create_user_group(
        _tenant_id,
        'PROJECT_' || upper(_project_code),
        'Project: ' || _project_name,
        _project_name || ' team with core and department members',
        'hybrid'
    );

    -- Add core team members (internal)
    FOREACH v_user_id IN ARRAY _core_team_members
    LOOP
        PERFORM auth.create_user_group_member(
            v_group_id, v_user_id, v_user_id  -- Self-added initially
        );

        UPDATE auth.user_group_member
        SET membership_source = 'core_team'
        WHERE user_group_id = v_group_id AND user_id = v_user_id;
    END LOOP;

    -- Add department mapping (external)
    PERFORM auth.create_user_group_mapping(
        v_group_id,
        'azure-ad-provider-uuid',
        _department_mapping,  -- e.g., 'Engineering-*'
        NULL,
        100
    );

    -- Assign project permissions
    PERFORM auth.assign_permission(
        _tenant_id, NULL, v_group_id, 'PROJECT_MEMBER', NULL
    );

    RETURN v_group_id;
END;
$$ LANGUAGE plpgsql;

-- Usage
SELECT create_project_hybrid_group(
    'tenant-uuid',
    'BETA',
    'Project Beta',
    ARRAY['team-lead-uuid', 'architect-uuid', 'pm-uuid'],
    'Engineering-Beta'
);

Department with Contractors

-- Department group: Employees via AD + contractors via explicit membership
DO $$
DECLARE
    v_tenant_id uuid := 'tenant-uuid';
    v_dept_group_id uuid;
BEGIN
    -- Create hybrid department group
    v_dept_group_id := auth.create_user_group(
        v_tenant_id,
        'MARKETING_EXTENDED',
        'Marketing Department (Extended)',
        'Marketing employees and contractors',
        'hybrid'
    );

    -- Map full-time employees (external)
    PERFORM auth.create_user_group_mapping(
        v_dept_group_id,
        'azure-ad-provider-uuid',
        'Marketing-*',
        NULL,
        50  -- High priority for employees
    );

    -- Map marketing roles (external)
    PERFORM auth.create_user_group_mapping(
        v_dept_group_id,
        'azure-ad-provider-uuid',
        NULL,
        'Marketing Specialist',
        60
    );

    -- Contractors will be added explicitly as internal members
    -- This allows for contractors who don't have AD accounts
    -- or need specific access that doesn't match AD groups

    -- Assign base marketing permissions
    PERFORM auth.assign_permission(
        v_tenant_id, NULL, v_dept_group_id, 'MARKETING_BASE', NULL
    );
END $$;

Querying Hybrid Groups

Membership Analysis

-- Analyze membership composition of hybrid group
WITH membership_analysis AS (
    SELECT
        'Internal' as source_type,
        COUNT(*) as member_count,
        array_agg(DISTINCT ugm.membership_source) as sources
    FROM auth.user_group_member ugm
    JOIN auth.user_info u ON ugm.user_id = u.user_id
    WHERE ugm.user_group_id = 'hybrid-group-uuid'
      AND ugm.is_active = true
      AND u.is_active = true

    UNION ALL

    SELECT
        'External' as source_type,
        COUNT(DISTINCT ui.user_id) as member_count,
        array_agg(DISTINCT p.name) as sources
    FROM auth.user_group_mapping ugmp
    JOIN auth.user_identity ui ON ugmp.provider_id = ui.provider_id
    JOIN auth.provider p ON ugmp.provider_id = p.provider_id
    JOIN auth.user_info u ON ui.user_id = u.user_id
    WHERE ugmp.user_group_id = 'hybrid-group-uuid'
      AND ugmp.is_active = true
      AND ui.is_last_used = true
      AND ui.is_active = true
      AND u.is_active = true
      AND (
          (ugmp.provider_group_pattern IS NOT NULL
           AND EXISTS (
               SELECT 1 FROM unnest(ui.provider_groups) AS pg(group_name)
               WHERE pg.group_name ~ ugmp.provider_group_pattern
           ))
          OR
          (ugmp.provider_role_pattern IS NOT NULL
           AND EXISTS (
               SELECT 1 FROM unnest(ui.provider_roles) AS pr(role_name)
               WHERE pr.role_name ~ ugmp.provider_role_pattern
           ))
      )
)
SELECT
    source_type,
    member_count,
    sources
FROM membership_analysis
ORDER BY source_type;

Membership Source Tracking

-- Track how users gained membership in hybrid group
SELECT
    u.username,
    u.display_name,
    CASE
        WHEN ugm.user_id IS NOT NULL THEN 'Internal'
        WHEN ext.user_id IS NOT NULL THEN 'External'
        ELSE 'Unknown'
    END as membership_type,
    COALESCE(ugm.membership_source, 'provider_mapping') as source_detail,
    COALESCE(ugm.added_at, ext.last_login_at) as effective_date,
    COALESCE(adder.display_name, ext.provider_name) as added_by_source
FROM auth.user_info u
LEFT JOIN auth.user_group_member ugm ON u.user_id = ugm.user_id
    AND ugm.user_group_id = 'hybrid-group-uuid' AND ugm.is_active = true
LEFT JOIN auth.user_info adder ON ugm.added_by = adder.user_id
LEFT JOIN LATERAL (
    SELECT DISTINCT
        ui.user_id,
        ui.last_login_at,
        p.name as provider_name
    FROM auth.user_group_mapping ugmp
    JOIN auth.user_identity ui ON ugmp.provider_id = ui.provider_id
    JOIN auth.provider p ON ugmp.provider_id = p.provider_id
    WHERE ugmp.user_group_id = 'hybrid-group-uuid'
      AND ui.user_id = u.user_id
      AND ugmp.is_active = true
      AND ui.is_last_used = true
      AND ui.is_active = true
      AND (
          (ugmp.provider_group_pattern IS NOT NULL
           AND EXISTS (
               SELECT 1 FROM unnest(ui.provider_groups) AS pg(group_name)
               WHERE pg.group_name ~ ugmp.provider_group_pattern
           ))
          OR
          (ugmp.provider_role_pattern IS NOT NULL
           AND EXISTS (
               SELECT 1 FROM unnest(ui.provider_roles) AS pr(role_name)
               WHERE pr.role_name ~ ugmp.provider_role_pattern
           ))
      )
) ext ON true
WHERE ugm.user_id IS NOT NULL OR ext.user_id IS NOT NULL
ORDER BY membership_type, u.display_name;

Performance Considerations

Optimized Membership Queries

-- Efficient hybrid group membership check
CREATE OR REPLACE FUNCTION auth.is_hybrid_group_member(
    _user_group_id uuid,
    _user_id uuid
) RETURNS boolean AS $$
DECLARE
    v_is_internal_member boolean := false;
    v_is_external_member boolean := false;
BEGIN
    -- Quick internal check first (usually faster)
    SELECT true INTO v_is_internal_member
    FROM auth.user_group_member ugm
    WHERE ugm.user_group_id = _user_group_id
      AND ugm.user_id = _user_id
      AND ugm.is_active = true
    LIMIT 1;

    IF v_is_internal_member THEN
        RETURN true;
    END IF;

    -- External check if not found internally
    SELECT true INTO v_is_external_member
    FROM auth.user_group_mapping ugmp
    JOIN auth.user_identity ui ON ugmp.provider_id = ui.provider_id
    WHERE ugmp.user_group_id = _user_group_id
      AND ui.user_id = _user_id
      AND ugmp.is_active = true
      AND ui.is_last_used = true
      AND ui.is_active = true
      AND (
          (ugmp.provider_group_pattern IS NOT NULL
           AND EXISTS (
               SELECT 1 FROM unnest(ui.provider_groups) AS pg(group_name)
               WHERE pg.group_name ~ ugmp.provider_group_pattern
           ))
          OR
          (ugmp.provider_role_pattern IS NOT NULL
           AND EXISTS (
               SELECT 1 FROM unnest(ui.provider_roles) AS pr(role_name)
               WHERE pr.role_name ~ ugmp.provider_role_pattern
           ))
      )
    LIMIT 1;

    RETURN COALESCE(v_is_external_member, false);
END;
$$ LANGUAGE plpgsql;

Indexing for Hybrid Groups

-- Specialized indexes for hybrid group performance
CREATE INDEX idx_user_group_member_hybrid_lookup
    ON auth.user_group_member(user_group_id, user_id, is_active);

CREATE INDEX idx_user_group_mapping_hybrid_lookup
    ON auth.user_group_mapping(user_group_id, provider_id)
    WHERE is_active = true;

CREATE INDEX idx_user_identity_hybrid_resolution
    ON auth.user_identity(user_id, provider_id, is_last_used, is_active);

Best Practices for Hybrid Groups

Design Guidelines

  1. Clear Membership Rules: Document which users come from internal vs external sources
  2. Priority Handling: Establish clear precedence rules (internal usually overrides external)
  3. Source Tracking: Always track membership source for auditing
  4. Regular Reconciliation: Periodically verify membership consistency

Migration Strategies

-- Migrate from internal to hybrid group
CREATE OR REPLACE FUNCTION migrate_to_hybrid_group(
    _user_group_id uuid,
    _provider_mappings jsonb  -- Array of mapping configurations
) RETURNS void AS $$
DECLARE
    v_mapping jsonb;
BEGIN
    -- Update group type
    UPDATE auth.user_group
    SET group_type = 'hybrid'
    WHERE user_group_id = _user_group_id;

    -- Add external mappings
    FOR v_mapping IN SELECT jsonb_array_elements(_provider_mappings)
    LOOP
        PERFORM auth.create_user_group_mapping(
            _user_group_id,
            (v_mapping->>'provider_id')::uuid,
            v_mapping->>'group_pattern',
            v_mapping->>'role_pattern',
            COALESCE((v_mapping->>'priority')::integer, 100)
        );
    END LOOP;

    -- Update existing members to track source
    UPDATE auth.user_group_member
    SET membership_source = 'migrated_internal'
    WHERE user_group_id = _user_group_id
      AND membership_source IS NULL;
END;
$$ LANGUAGE plpgsql;

Security Considerations

  1. Access Control: Ensure external mappings are secure and properly validated
  2. Audit Trail: Log both internal and external membership changes
  3. Override Protection: Prevent accidental override of critical memberships
  4. Regular Reviews: Audit hybrid group configurations regularly

Troubleshooting Hybrid Groups

Membership Conflicts

-- Identify users with both internal and external membership
SELECT
    u.username,
    u.display_name,
    'Both internal and external membership' as issue,
    ugm.membership_source as internal_source,
    ext.provider_name as external_source
FROM auth.user_info u
JOIN auth.user_group_member ugm ON u.user_id = ugm.user_id
JOIN LATERAL (
    SELECT DISTINCT p.name as provider_name
    FROM auth.user_group_mapping ugmp
    JOIN auth.user_identity ui ON ugmp.provider_id = ui.provider_id
    JOIN auth.provider p ON ugmp.provider_id = p.provider_id
    WHERE ugmp.user_group_id = ugm.user_group_id
      AND ui.user_id = u.user_id
      AND ugmp.is_active = true
      AND ui.is_last_used = true
      AND ui.is_active = true
) ext ON true
WHERE ugm.user_group_id = 'hybrid-group-uuid'
  AND ugm.is_active = true;

Performance Issues

-- Find hybrid groups with performance issues
SELECT
    ug.code,
    ug.name,
    COUNT(DISTINCT ugm.user_id) as internal_members,
    COUNT(DISTINCT ugmp.user_group_mapping_id) as external_mappings,
    CASE
        WHEN COUNT(DISTINCT ugmp.user_group_mapping_id) > 10 THEN 'Too many mappings'
        WHEN COUNT(DISTINCT ugm.user_id) > 1000 THEN 'Large internal membership'
        ELSE 'OK'
    END as performance_status
FROM auth.user_group ug
LEFT JOIN auth.user_group_member ugm ON ug.user_group_id = ugm.user_group_id
    AND ugm.is_active = true
LEFT JOIN auth.user_group_mapping ugmp ON ug.user_group_id = ugmp.user_group_id
    AND ugmp.is_active = true
WHERE ug.group_type = 'hybrid'
  AND ug.is_active = true
GROUP BY ug.user_group_id
ORDER BY internal_members DESC, external_mappings DESC;

What's Next