<- Back to Blogs

Understanding SQL Server Role and Access Inspection Through a Data Model

- By Sam Rajarathinam Databases
Understanding SQL Server Role and Access Inspection Through a Data Model

A practical explanation of how SQL Server stores users, roles, permissions, schemas, and objects, and how to inspect database access using system views.

Database security is not only about creating users and granting permissions. In real-world systems, access is usually layered. A user may not have direct access to a table, but they may still be able to read or modify data because they belong to a database role. That is why, when we inspect SQL Server access, we need to understand the relationship between users, roles, permissions, schemas, and objects. A simple way to think about it is: User → Role → Permission → Securable This model helps us answer some important questions: Who has access? - Through which role is the access granted? - What permission is granted? - Is the permission at database, schema, or object level? - Is the access direct, or inherited through a role?

Why a Data Model Helps

When working with SQL Server security, it is easy to run individual queries and get partial answers.

For example, one query may show all database users. Another query may show all roles. Another query may show role permissions. But unless we connect these pieces together, we may not get the full picture.

A data model gives us a structured view of how SQL Server stores access-related information internally.

The key system views involved are:

| System View | Purpose |

| --------------------------- | ------------------------------------------------------------- |

| `sys.database_principals` | Stores users, roles, and external groups |

| `sys.database_role_members` | Maps users or groups to database roles |

| `sys.database_permissions` | Stores permissions granted to users or roles |

| `sys.schemas` | Stores database schema information |

| `sys.objects` | Stores database objects such as tables, views, and procedures |

Database Principals

The starting point is sys.database_principals.

This view contains database-level security principals such as:

  • SQL users
  • External users
  • External groups
  • Database roles

In SQL Server, both users and roles are stored in the same system view. The difference is identified using the type column.

For example:

  • R represents database roles
  • S represents SQL users
  • E represents external users
  • X represents external groups

This is important because roles are not stored in a separate role table. They are also principals.

Role Memberships

The next important view is sys.database_role_members.

This view acts like a relationship table between users and roles.

It contains two important columns:

  • role_principal_id
  • member_principal_id

Both values point back to sys.database_principals.

This means a role is a principal, and a user or group is also a principal. The membership table connects them together.

In simple terms:

One role can have many members.

One user or group can belong to many roles.

This is what makes SQL Server access flexible, but it can also make access harder to understand without a clear model.

Permissions

Permissions are stored in sys.database_permissions.

This view tells us what permission has been granted and to whom.

The important columns include:

  • grantee_principal_id
  • permission_name
  • state_desc
  • class
  • class_desc
  • major_id
  • minor_id

The grantee_principal_id points to the principal receiving the permission. In many cases, this will be a database role. However, permissions can also be granted directly to a user.

The permission_name shows the type of permission, such as:

  • SELECT
  • INSERT
  • UPDATE
  • DELETE
  • EXECUTE

The state_desc shows whether the permission is granted, denied, or granted with further grant permission.

Permission Scope

One of the most important parts of access inspection is understanding the scope of a permission.

A permission can be applied at different levels:

| Class | Scope |

| ----- | ---------------------- |

| `0` | Database level |

| `3` | Schema level |

| `1` | Object or column level |

This matters because access granted at a higher level may not appear as individual table-level permissions.

For example, if a role has SELECT permission at schema level, the role may be able to read all tables in that schema. You may not see one row per table in the permissions view.

Similarly, built-in roles such as db_datareader and db_datawriter provide broad access without listing every object individually.

Schemas and Objects

To understand object-level and schema-level access, we also need to look at:

  • sys.schemas
  • sys.objects

sys.schemas helps resolve schema-level permissions.

sys.objects helps resolve object-level permissions, such as access to tables, views, stored procedures, or functions.

When sys.database_permissions.class_desc is SCHEMA, the major_id refers to a schema.

When class_desc is OBJECT_OR_COLUMN, the major_id refers to an object.

This is why permission inspection queries often use logic like:

CASE

WHEN perm.class_desc = 'OBJECT_OR_COLUMN' THEN OBJECT_NAME(perm.major_id)

WHEN perm.class_desc = 'SCHEMA' THEN SCHEMA_NAME(perm.major_id)

ELSE perm.class_desc

END AS securable

End-to-End Access Mapping

The most useful view is often the combined mapping:

User → Role → Permission → Object/Schema/Database

This helps identify the effective access path.

For example:

A user may not directly have SELECT permission on a table.

But the user may belong to a role.

That role may have SELECT permission on a schema.

Therefore, the user effectively has read access to objects inside that schema.

This kind of mapping is very useful during:

  • Security audits
  • Access reviews
  • Production support
  • Migration planning
  • Troubleshooting permission errors
  • Compliance checks
  • Database hardening activities

Why Built-in Roles Need Special Attention

SQL Server has built-in database roles such as:

  • db_datareader
  • db_datawriter
  • db_owner

These roles are powerful.

For example, db_datareader gives read access to all user tables and views in the database. db_datawriter gives insert, update, and delete access to all user tables.

The important point is that these permissions may not appear as individual object-level permissions in the same way custom permissions do.

So, when inspecting access, we should not only check sys.database_permissions. We should also check role memberships carefully.

A user who belongs to db_datareader may have broad access even if no direct table-level permission is visible.

Conclusion

SQL Server access inspection becomes much easier when we look at it as a data model.

The core relationship is:

Principals define users and roles.

Role memberships connect users to roles.

Permissions define what those principals can do.

Schemas and objects define where those permissions apply.

By connecting these views together, we can clearly understand:

  • Who has access
  • How the access is granted
  • What level the access applies to
  • Whether access is direct or inherited
  • Which roles may be giving broad permissions

Good database security starts with visibility. A clear access data model gives us that visibility.