Question

I'm using the below code to give me a list of orphaned users for a database which works great. I'm now trying to build this into something that will iterate through the list of databases and return the results in a single table.

Here is the code I am using to obtain the orphaned users:

select DB_NAME() AS [Current Database], u.uid, u.name, u.sid, rm.role_principal_id as 'Role ID', dp1.name as 'Role'
from sys.sysusers u
left join sys.syslogins l on UPPER(u.sid) = UPPER(l.sid)
inner join sys.database_role_members rm on rm.member_principal_id = u.uid
left join sys.database_principals dp on dp.principal_id = rm.member_principal_id
left join sys.database_principals dp1 on dp1.principal_id = rm.role_principal_id 
where u.uid > 4 and u.issqlrole = 0
--and issqluser = 1 --commented out to include orphaned windows logins
and l.name is null
order by 1

I can get a list of databases using the below but I want to combine with the above to go through each database in turn.

SELECT name FROM sys.databases 
WHERE database_id > 4

Any help or pointers with this would be much appreciated!

Thanks :-)

Was it helpful?

Solution

You can use the system stored procedure sp_msforeachdb to run a set of SQL statements against each database. Each execution of that procedure, in this case, will return a separate result set so we are using a temporary table to gather all the results up into a single table. This single table you then can manipulate using normal SQL to filter and sort your final results.

IF OBJECT_ID('tempdb..#Orphans') IS NOT NULL
  DROP TABLE #Orphans

CREATE TABLE #Orphans 
(
  [Current Database] SYSNAME,
  [uid]              SMALLINT,
  [name]             SYSNAME,
  [sid]              VARBINARY(85),
  [Role ID]          SMALLINT,
  [Role]             SYSNAME
)

INSERT #Orphans
EXEC sp_msforeachdb 'use [?];
    select DB_NAME() AS [Current Database]
         , u.uid, u.name
         , u.sid
         , rm.role_principal_id as [Role ID]
         , dp1.name as [Role]
      from sys.sysusers u
             left join sys.syslogins l 
               on UPPER(u.sid) = UPPER(l.sid)
                 inner join sys.database_role_members rm 
                   on rm.member_principal_id = u.uid
                     left join sys.database_principals dp 
                       on dp.principal_id = rm.member_principal_id
                         left join sys.database_principals dp1 
                           on dp1.principal_id = rm.role_principal_id 
     where u.uid > 4 and u.issqlrole = 0
     --and issqluser = 1 --commented out to include orphaned windows logins
       and l.name is null
    order by 1'

SELECT * FROM #Orphans

A few things to note is that the "?" character in the SQL template will be filled in with the database name each time the template is executed in a new database. Also I changed your column aliases to use the "[" characters instead of the single quotes.

OTHER TIPS

The only way I know of running queries in different databases is to use a cursor to iterate through the database names and executing a dynamic query (with the desired database name) in each iteration.

To run away from the difficulties (at least to me) in using table variables with dynamic queries, I used a local temporary table (the single # prefix) in which to store the results.

For this to work, I had to prefix each schema.table in your query with a placeholder that is replaced by the database name in each iteration. That way, each table will be fully qualified with db.schema.table.

Also be aware that using EXEC like this puts you vulnerable to sql injection.

I hope this helps you:

CREATE TABLE #results (
    currdb sysname,
    uid int,
    uname sysname,
    usid int,
    rpid int,
    rname sysname)

DECLARE @sqltemplate VARCHAR(4000), @sql VARCHAR(4000)
SET @sqltemplate = 'SELECT
   ''[@db]'', u.uid, u.name, u.sid, rm.role_principal_id, dp1.name
    FROM [@db].sys.sysusers u
        LEFT JOIN [@db].sys.syslogins l ON UPPER(u.sid) = UPPER(l.sid)
        INNER JOIN [@db].sys.database_role_members rm
            ON rm.member_principal_id = u.uid
        LEFT JOIN [@db].sys.database_principals dp
            ON dp.principal_id = rm.member_principal_id
        LEFT JOIN [@db].sys.database_principals dp1
            ON dp1.principal_id = rm.role_principal_id
    WHERE u.uid > 4 AND u.issqlrole = 0 AND l.name IS NULL
    ORDER BY 1'

DECLARE @dbname sysname

DECLARE dbnames CURSOR FOR
    SELECT name FROM sys.databases WHERE database_id > 4

OPEN dbnames

FETCH NEXT FROM dbnames INTO @dbname

WHILE @@FETCH_STATUS = 0
BEGIN
  SET @sql = REPLACE(@sqltemplate, '@db', @dbname)
  INSERT #results
    EXEC (@sql)
  FETCH NEXT FROM dbnames INTO @dbname
END
CLOSE dbnames
DEALLOCATE dbnames

SELECT * FROM #results
DROP TABLE #results
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top