【问题标题】:Running a large T-SQL query on all databases在所有数据库上运行大型 T-SQL 查询
【发布时间】:2016-09-23 06:48:44
【问题描述】:

我正在尝试审核整个服务器上的数据库权限。我有一个查询可以产生我想要的输出,但我需要针对所有数据库运行它。

大多数解决方案似乎都使​​用以下内容:

DECLARE @command varchar(1000)
SELECT @command = 'USE ?; SQL QUERY HERE'
EXEC sp_MSforeachdb @command

但是,这会失败并出现错误,当我自己在数据库上运行查询时不会发生这种错误。我认为这与存储在变量中有关,但使用这种格式也会失败:

EXECUTE sp_MSForEachDB 
'USE ?; SQL QUERY HERE'

我尝试运行的完整查询是:

SELECT
   ServerName          = @@SERVERNAME,
   LoginName           = AccessSummary.LoginName,
   LoginType           = CASE WHEN syslogins.isntuser = 1 THEN 'WINDOWS_LOGIN' WHEN syslogins.isntgroup = 1 THEN 'WINDOWS_GROUP' ELSE 'SQL_USER' END,
   DatabaseName        = DB_NAME(),
   SelectAccess        = MAX(AccessSummary.SelectAccess),
   InsertAccess        = MAX(AccessSummary.InsertAccess),
   UpdateAccess        = MAX(AccessSummary.UpdateAccess),
   DeleteAccess        = MAX(AccessSummary.DeleteAccess),
   DBOAccess           = MAX(AccessSummary.DBOAccess),
   SysadminAccess      = MAX(AccessSummary.SysadminAccess)
FROM
   (
       /* Get logins with permissions */
       SELECT
           LoginName           = sysDatabasePrincipal.name,
           SelectAccess        = CASE WHEN permission_name = 'SELECT' THEN 1 ELSE 0 END,
           InsertAccess        = CASE WHEN permission_name = 'INSERT' THEN 1 ELSE 0 END,
           UpdateAccess        = CASE WHEN permission_name = 'UPDATE' THEN 1 ELSE 0 END,
           DeleteAccess        = CASE WHEN permission_name = 'DELETE' THEN 1 ELSE 0 END,
           DBOAccess           = 0,
           SysadminAccess      = 0
       FROM sys.database_permissions AS sysDatabasePermission
       INNER JOIN sys.database_principals AS sysDatabasePrincipal
           ON sysDatabasePrincipal.principal_id = sysDatabasePermission.grantee_principal_id
       INNER JOIN sys.server_principals AS sysServerPrincipal
           ON sysServerPrincipal.sid = sysDatabasePrincipal.sid
       WHERE sysDatabasePermission.class_desc = 'OBJECT_OR_COLUMN'
           AND sysDatabasePrincipal.type_desc IN ('WINDOWS_LOGIN', 'WINDOWS_GROUP', 'SQL_USER')
           AND sysServerPrincipal.is_disabled = 0
       UNION ALL
       /* Get group members with permissions */
       SELECT
           LoginName           = sysDatabasePrincipalMember.name,
           SelectAccess        = CASE WHEN permission_name = 'SELECT' THEN 1 ELSE 0 END,
           InsertAccess        = CASE WHEN permission_name = 'INSERT' THEN 1 ELSE 0 END,
           UpdateAccess        = CASE WHEN permission_name = 'UPDATE' THEN 1 ELSE 0 END,
           DeleteAccess        = CASE WHEN permission_name = 'DELETE' THEN 1 ELSE 0 END,
           DBOAccess           = 0,
           SysadminAccess      = 0
       FROM sys.database_permissions AS sysDatabasePermission
       INNER JOIN sys.database_principals AS sysDatabasePrincipalRole
           ON sysDatabasePrincipalRole.principal_id = sysDatabasePermission.grantee_principal_id
       INNER JOIN sys.database_role_members AS sysDatabaseRoleMember
           ON sysDatabaseRoleMember.role_principal_id = sysDatabasePrincipalRole.principal_id
       INNER JOIN sys.database_principals AS sysDatabasePrincipalMember
           ON sysDatabasePrincipalMember.principal_id = sysDatabaseRoleMember.member_principal_id
       INNER JOIN sys.server_principals AS sysServerPrincipal
           ON sysServerPrincipal.sid = sysDatabasePrincipalMember.sid
       WHERE sysDatabasePermission.class_desc = 'OBJECT_OR_COLUMN'
           AND sysDatabasePrincipalRole.type_desc = 'DATABASE_ROLE'
           AND sysDatabasePrincipalRole.name <> 'public'
           AND sysDatabasePrincipalMember.type_desc IN ('WINDOWS_LOGIN', 'WINDOWS_GROUP', 'SQL_USER')
           AND sysServerPrincipal.is_disabled = 0
       UNION ALL
       /* Get users in db_owner, db_datareader and db_datawriter */
       SELECT
           LoginName           = sysServerPrincipal.name,
           SelectAccess        = CASE WHEN sysDatabasePrincipalRole.name IN ('db_owner', 'db_datareader') THEN 1 ELSE 0 END,
           InsertAccess        = CASE WHEN sysDatabasePrincipalRole.name IN ('db_owner', 'db_datawriter') THEN 1 ELSE 0 END,
           UpdateAccess        = CASE WHEN sysDatabasePrincipalRole.name IN ('db_owner', 'db_datawriter') THEN 1 ELSE 0 END,
           DeleteAccess        = CASE WHEN sysDatabasePrincipalRole.name IN ('db_owner', 'db_datawriter') THEN 1 ELSE 0 END,
           DBOAccess           = CASE WHEN sysDatabasePrincipalRole.name = 'db_owner' THEN 1 ELSE 0 END,
           SysadminAccess      = 0
       FROM sys.database_principals AS sysDatabasePrincipalRole
       INNER JOIN sys.database_role_members AS sysDatabaseRoleMember
           ON sysDatabaseRoleMember.role_principal_id = sysDatabasePrincipalRole.principal_id
       INNER JOIN sys.database_principals AS sysDatabasePrincipalMember
           ON sysDatabasePrincipalMember.principal_id = sysDatabaseRoleMember.member_principal_id
       INNER JOIN sys.server_principals AS sysServerPrincipal
           ON sysServerPrincipal.sid = sysDatabasePrincipalMember.sid
       WHERE sysDatabasePrincipalRole.name IN ('db_owner', 'db_datareader', 'db_datawriter')
           AND sysServerPrincipal.type_desc IN ('WINDOWS_LOGIN', 'WINDOWS_GROUP', 'SQL_LOGIN')
           AND sysServerPrincipal.is_disabled = 0
       UNION ALL
       /* Get users in sysadmin */
       SELECT
           LoginName           = sysServerPrincipalMember.name,
           SelectAccess        = 1,
           InsertAccess        = 1,
           UpdateAccess        = 1,
           DeleteAccess        = 1,
           DBOAccess           = 0,
           SysadminAccess      = 1
       FROM sys.server_principals AS sysServerPrincipalRole
       INNER JOIN sys.server_role_members AS sysServerRoleMember
           ON sysServerRoleMember.role_principal_id = sysServerPrincipalRole.principal_id
       INNER JOIN sys.server_principals AS sysServerPrincipalMember
           ON sysServerPrincipalMember.principal_id = sysServerRoleMember.member_principal_id
       WHERE sysServerPrincipalMember.type_desc IN ('WINDOWS_LOGIN', 'WINDOWS_GROUP', 'SQL_LOGIN')
           AND sysServerPrincipalMember.is_disabled = 0
   ) AS AccessSummary
INNER JOIN MASTER.dbo.syslogins AS syslogins
   ON syslogins.loginname = AccessSummary.LoginName
WHERE AccessSummary.LoginName NOT IN ('NT SERVICE\MSSQLSERVER', 'NT AUTHORITY\SYSTEM', 'NT SERVICE\SQLSERVERAGENT')
GROUP BY
   AccessSummary.LoginName,
   CASE WHEN syslogins.isntuser = 1 THEN 'WINDOWS_LOGIN' WHEN syslogins.isntgroup = 1 THEN 'WINDOWS_GROUP' ELSE 'SQL_USER' END

我收到此错误(多次):

消息 102,第 15 级,状态 1,第 35 行
'THEN' 附近的语法不正确。

【问题讨论】:

  • 抛出的错误是什么?
  • Msg 102,级别 15,状态 1,第 35 行 'THEN' 附近的语法不正确。消息 102,级别 15,状态 1,第 35 行 'THEN' 附近的语法不正确。这突出显示: /* 获取具有权限的组成员 */ SELECT Msg 102, Level 15, State 1, Line 35 “THEN”附近的语法不正确。消息 102,级别 15,状态 1,第 35 行 'THEN' 附近的语法不正确。消息 102,级别 15,状态 1,第 35 行 'THEN' 附近的语法不正确。
  • sp_MSForEachDB 仅适用于 2000 个字符及以下的字符串 - 这可能是问题吗?解决这个问题的一个好方法是实现一个替代过程,就像我做的codereview.stackexchange.com/questions/117386/…

标签: sql-server tsql audit


【解决方案1】:
DECLARE @sqlCommand VARCHAR(8000)
SET @sqlCommand =
'
USE[?]
IF (db_name() like ''%Filter_DB_Name%'')

BEGIN
DECLARE @sql VARCHAR(1000)
SELECT @sql = ''<SQL Here>''
END
exec (@sql)'
EXEC sp_MSforeachdb @sqlCommand

使用 Filter_DB_Name 以防您不想遍历运行它的服务器中的所有数据库。我通常使用 db_name() 作为 sql 的一部分,所以我得到了链接到特定结果的 db 的反馈

【讨论】:

    【解决方案2】:

    您的查询似乎超过 2000 个字符 - 尝试将 sp_MSforEachDB 替换为以下内容

    CREATE Proc [Process].[ExecForEachDB] ( @cmd NVarchar(Max) )
    As /*
    Stored Procedure created by Chris Johnson
    20th January 2016
    The purpose of this stored procedure is to replace the undocumented procedure sp_MSforeachdb as this may be removed in future versions
    of SQL Server. The stored procedure iterates through all user databases and executes the code passed to it.
    Based off of https://sqlblog.org/2010/02/08/bad-habits-to-kick-relying-on-undocumented-behavior  
    */
        Begin
            Set NoCount On;
    
        --Declare variables
            Declare @SqlScript NVarchar(Max)= ''
              , @Database NVarchar(257)=''
              , @ErrorMessage NVarchar(Max)='';
    
    
        --Test validity, all scripts should contain a "?" to be used in place of a db name
            If @cmd Not Like '%?%'
                Begin
                    Set @ErrorMessage = Cast('' As NVarchar(max))
                    Set @ErrorMessage = @ErrorMessage+'ExecForEachDB failed, script does not contain the string "?" '
                        + @cmd;
    
                    --If is included as permissions may not be available to create this table
                    If Object_Id('[History].[ExecForEachDBLogs]') Is Not Null
                        Begin
                            Insert  [History].[ExecForEachDBErrorLogs]
                                    ( [Error] )
                            Values  ( @ErrorMessage );
                        End;
    
                    If Object_Id('[History].[ExecForEachDBLogs]') Is Null
                        Begin
                            Raiserror ('** Warning - Errors are not being logged **',1,1); --if Errors are not being logged raise a low level error
                        End;
                    Raiserror (@ErrorMessage,13,1);
                End;
    
            If @cmd Like '%?%' 
                Begin
        --Use Cursor to hold list of databases to execute against
                    Declare [DbNames] Cursor Local Forward_Only Static Read_Only
                    For
                        Select  QuoteName([name])
                        From    [sys].[databases]
                        Where   [state] = 0 --online databases
                                And [is_read_only] = 0 --only databases that can be executed against
                                And [database_id] > 4 --only user databases
                                And has_dbaccess([name]) = 1 --only dbs current user has access to
                        Order By [name];
    
                    Open [DbNames];
    
                    Fetch Next From [DbNames] Into @Database; --Get first database to execute against
    
                    While @@fetch_status = 0 --when fetch is successful
                        Begin
                            Set @SqlScript = Cast('' As NVarchar(Max));
                            Set @SqlScript = @SqlScript
                                + Replace(Replace(Replace(@cmd , '?' , @Database) ,
                                                  '[[' , '[') , ']]' , ']');--[[ & ]] caused by script including [?]
                            Begin Try 
                                Exec(@SqlScript);
                            End Try
                            Begin Catch --if error happens against any db, raise a high level error advising the database and print the script
                                Set @ErrorMessage = Cast('' As NVarchar(max))
                                Set @ErrorMessage = @ErrorMessage + 'Script failed against database '
                                    + @Database;
                                Raiserror (@ErrorMessage,13,1);
                                Print @SqlScript;
                            End Catch;
    
                            Fetch Next From [DbNames] Into @Database;--Get next database to execute against
                        End;
    
                    Close [DbNames];
                    Deallocate [DbNames];
                End;
        End;
    GO
    

    【讨论】:

      【解决方案3】:

      我终于用游标让它工作了(有些人似乎讨厌它,但它确实有效)。您必须在 SQL 查询中使用两个单引号代替单引号。

      DECLARE @db_name AS nvarchar(max)
      DECLARE c_db_names CURSOR FOR
      SELECT name
      FROM sys.databases
      WHERE name NOT IN('master', 'model', 'msdb', 'tempdb')
      
      OPEN c_db_names
      
      FETCH c_db_names INTO @db_name
      
      WHILE @@Fetch_Status = 0
      BEGIN
        EXEC('
         USE ' + @db_name + '
         SELECT
         ServerName          = @@SERVERNAME,
         DatabaseName        = DB_NAME(),
         LoginName           = AccessSummary.LoginName,
         LoginType           = CASE WHEN syslogins.isntuser = 1 THEN ''WINDOWS_LOGIN'' WHEN syslogins.isntgroup = 1 THEN ''WINDOWS_GROUP'' ELSE ''SQL_USER'' END,
         SelectAccess        = MAX(AccessSummary.SelectAccess),
         InsertAccess        = MAX(AccessSummary.InsertAccess),
         UpdateAccess        = MAX(AccessSummary.UpdateAccess),
         DeleteAccess        = MAX(AccessSummary.DeleteAccess),
         DBOAccess           = MAX(AccessSummary.DBOAccess),
         SysadminAccess      = MAX(AccessSummary.SysadminAccess)
      FROM
         (
             /* Get logins with permissions */
             SELECT
                 LoginName           = sysDatabasePrincipal.name,
                 SelectAccess        = CASE WHEN permission_name = ''SELECT'' THEN 1 ELSE 0 END,
                 InsertAccess        = CASE WHEN permission_name = ''INSERT'' THEN 1 ELSE 0 END,
                 UpdateAccess        = CASE WHEN permission_name = ''UPDATE'' THEN 1 ELSE 0 END,
                 DeleteAccess        = CASE WHEN permission_name = ''DELETE'' THEN 1 ELSE 0 END,
                 DBOAccess           = 0,
                 SysadminAccess      = 0
             FROM sys.database_permissions AS sysDatabasePermission
             INNER JOIN sys.database_principals AS sysDatabasePrincipal
                 ON sysDatabasePrincipal.principal_id = sysDatabasePermission.grantee_principal_id
             INNER JOIN sys.server_principals AS sysServerPrincipal
                 ON sysServerPrincipal.sid = sysDatabasePrincipal.sid
             WHERE sysDatabasePermission.class_desc = ''OBJECT_OR_COLUMN''
                 AND sysDatabasePrincipal.type_desc IN (''WINDOWS_LOGIN'', ''WINDOWS_GROUP'', ''SQL_USER'')
                 AND sysServerPrincipal.is_disabled = 0
             UNION ALL
             /* Get group members with permissions */
             SELECT
                 LoginName           = sysDatabasePrincipalMember.name,
                 SelectAccess        = CASE WHEN permission_name = ''SELECT'' THEN 1 ELSE 0 END,
                 InsertAccess        = CASE WHEN permission_name = ''INSERT'' THEN 1 ELSE 0 END,
                 UpdateAccess        = CASE WHEN permission_name = ''UPDATE'' THEN 1 ELSE 0 END,
                 DeleteAccess        = CASE WHEN permission_name = ''DELETE'' THEN 1 ELSE 0 END,
                 DBOAccess           = 0,
                 SysadminAccess      = 0
             FROM sys.database_permissions AS sysDatabasePermission
             INNER JOIN sys.database_principals AS sysDatabasePrincipalRole
                 ON sysDatabasePrincipalRole.principal_id = sysDatabasePermission.grantee_principal_id
             INNER JOIN sys.database_role_members AS sysDatabaseRoleMember
                 ON sysDatabaseRoleMember.role_principal_id = sysDatabasePrincipalRole.principal_id
             INNER JOIN sys.database_principals AS sysDatabasePrincipalMember
                 ON sysDatabasePrincipalMember.principal_id = sysDatabaseRoleMember.member_principal_id
             INNER JOIN sys.server_principals AS sysServerPrincipal
                 ON sysServerPrincipal.sid = sysDatabasePrincipalMember.sid
             WHERE sysDatabasePermission.class_desc = ''OBJECT_OR_COLUMN''
                 AND sysDatabasePrincipalRole.type_desc = ''DATABASE_ROLE''
                 AND sysDatabasePrincipalRole.name <> ''public''
                 AND sysDatabasePrincipalMember.type_desc IN (''WINDOWS_LOGIN'', ''WINDOWS_GROUP'', ''SQL_USER'')
                 AND sysServerPrincipal.is_disabled = 0
             UNION ALL
             /* Get users in db_owner, db_datareader and db_datawriter */
             SELECT
                 LoginName           = sysServerPrincipal.name,
                 SelectAccess        = CASE WHEN sysDatabasePrincipalRole.name IN (''db_owner'', ''db_datareader'') THEN 1 ELSE 0 END,
                 InsertAccess        = CASE WHEN sysDatabasePrincipalRole.name IN (''db_owner'', ''db_datawriter'') THEN 1 ELSE 0 END,
                 UpdateAccess        = CASE WHEN sysDatabasePrincipalRole.name IN (''db_owner'', ''db_datawriter'') THEN 1 ELSE 0 END,
                 DeleteAccess        = CASE WHEN sysDatabasePrincipalRole.name IN (''db_owner'', ''db_datawriter'') THEN 1 ELSE 0 END,
                 DBOAccess           = CASE WHEN sysDatabasePrincipalRole.name = ''db_owner'' THEN 1 ELSE 0 END,
                 SysadminAccess      = 0
             FROM sys.database_principals AS sysDatabasePrincipalRole
             INNER JOIN sys.database_role_members AS sysDatabaseRoleMember
                 ON sysDatabaseRoleMember.role_principal_id = sysDatabasePrincipalRole.principal_id
             INNER JOIN sys.database_principals AS sysDatabasePrincipalMember
                 ON sysDatabasePrincipalMember.principal_id = sysDatabaseRoleMember.member_principal_id
             INNER JOIN sys.server_principals AS sysServerPrincipal
                 ON sysServerPrincipal.sid = sysDatabasePrincipalMember.sid
             WHERE sysDatabasePrincipalRole.name IN (''db_owner'', ''db_datareader'', ''db_datawriter'')
                 AND sysServerPrincipal.type_desc IN (''WINDOWS_LOGIN'', ''WINDOWS_GROUP'', ''SQL_LOGIN'')
                 AND sysServerPrincipal.is_disabled = 0
             UNION ALL
             /* Get users in sysadmin */
             SELECT
                 LoginName           = sysServerPrincipalMember.name,
                 SelectAccess        = 1,
                 InsertAccess        = 1,
                 UpdateAccess        = 1,
                 DeleteAccess        = 1,
                 DBOAccess           = 0,
                 SysadminAccess      = 1
             FROM sys.server_principals AS sysServerPrincipalRole
             INNER JOIN sys.server_role_members AS sysServerRoleMember
                 ON sysServerRoleMember.role_principal_id = sysServerPrincipalRole.principal_id
             INNER JOIN sys.server_principals AS sysServerPrincipalMember
                 ON sysServerPrincipalMember.principal_id = sysServerRoleMember.member_principal_id
             WHERE sysServerPrincipalMember.type_desc IN (''WINDOWS_LOGIN'', ''WINDOWS_GROUP'', ''SQL_LOGIN'')
                 AND sysServerPrincipalMember.is_disabled = 0
         ) AS AccessSummary
      INNER JOIN MASTER.dbo.syslogins AS syslogins
         ON syslogins.loginname = AccessSummary.LoginName
      WHERE AccessSummary.LoginName NOT IN (''NT SERVICE\MSSQLSERVER'', ''NT AUTHORITY\SYSTEM'', ''NT SERVICE\SQLSERVERAGENT'')
      GROUP BY
         AccessSummary.LoginName,
         CASE WHEN syslogins.isntuser = 1 THEN ''WINDOWS_LOGIN'' WHEN syslogins.isntgroup = 1 THEN ''WINDOWS_GROUP'' ELSE ''SQL_USER'' END')
        FETCH c_db_names INTO @db_name
      END
      
      CLOSE c_db_names
      DEALLOCATE c_db_names
      

      【讨论】:

        猜你喜欢
        • 2015-01-22
        • 1970-01-01
        • 2011-08-22
        • 1970-01-01
        • 2011-09-15
        • 1970-01-01
        • 1970-01-01
        • 2013-08-30
        相关资源
        最近更新 更多