sql-server 如何将“USE?”与CURSOR?一起使用

vfh0ocws  于 2022-10-31  发布在  其他
关注(0)|答案(2)|浏览(204)

我发现了一个关于如何使用光标的很棒的example

DECLARE @DB_Name varchar(100) 
DECLARE @Command nvarchar(200) 

DECLARE database_cursor CURSOR FOR 
    SELECT name 
    FROM MASTER.sys.sysdatabases 

OPEN database_cursor 

FETCH NEXT FROM database_cursor INTO @DB_Name 

WHILE @@FETCH_STATUS = 0 
BEGIN 
     SELECT @Command = 'SELECT ' + '''' + @DB_Name + '''' + ', SF.filename, SF.size FROM sys.sysfiles SF'
     EXEC sp_executesql @Command 

     FETCH NEXT FROM database_cursor INTO @DB_Name 
END 

CLOSE database_cursor 
DEALLOCATE database_cursor

问题是,当我执行它时,它总是返回相同的filenamesize
那么让我们把它放回去:

DECLARE @DB_Name varchar(100) 
DECLARE @Command nvarchar(200) 

DECLARE database_cursor CURSOR FOR 
    SELECT name 
    FROM MASTER.sys.sysdatabases 

OPEN database_cursor 

FETCH NEXT FROM database_cursor INTO @DB_Name 

WHILE @@FETCH_STATUS = 0 
BEGIN 
     SELECT @Command = ' USE ? SELECT ' + '''' + @DB_Name + '''' + ', SF.filename, SF.size FROM sys.sysfiles SF'
     EXEC sp_executesql @Command 

     FETCH NEXT FROM database_cursor INTO @DB_Name 
END 

CLOSE database_cursor 
DEALLOCATE database_cursor

但SQL Server对每个数据库都抛出此错误:
消息102,级别15,状态1,行1
'?'附近有语法错误
正好是我数据库数量的5倍。
如何使用此光标?

guicsvcw

guicsvcw1#

您需要使用@DB_Name变量,而不是?

DECLARE @DB_Name varchar(100) 
DECLARE @Command nvarchar(200) 
DECLARE database_cursor CURSOR FOR 
SELECT name 
FROM MASTER.sys.sysdatabases 

OPEN database_cursor 

FETCH NEXT FROM database_cursor INTO @DB_Name 

WHILE @@FETCH_STATUS = 0 
BEGIN 
     SELECT @Command = ' USE ' + @DB_Name + ' SELECT ' + '''' + @DB_Name + '''' + ', SF.filename, SF.size FROM sys.sysfiles SF'
     EXEC sp_executesql @Command 

     FETCH NEXT FROM database_cursor INTO @DB_Name 
END 

CLOSE database_cursor 
DEALLOCATE database_cursor
fcwjkofz

fcwjkofz2#

如果有人能够在此系统上创建数据库,他们可能会以测试、QA为幌子,或者通过允许用户创建数据库的UI,创建名为tempdb; SELECT name,salary FROM Accounting.dbo.Employees;的数据库,从而获得对敏感信息的访问权限(或者,更糟的是,造成破坏!),而这条路径只能通过动态SQL的执行上下文来实现。
此外,您不应该使用过时的兼容性视图,如sysdatabasessysfiles,并且您应该保护自己不受脱机数据库和其他不可访问数据库的影响,这些数据库将导致您的进程暂停。(考虑到数据库名称可能更长),应始终为nvarchar,因为人们可以将其数据库命名为🐳[🐻🐻🐻🐻🐻]等。
最后,您需要避免使用默认游标所导致的问题,这可能导致数据库被任意跳过。这已经被我在评论( sp_ineachdb )中提到的过程和Bertrand先生写的in the referenced article的背景所解决。
如果您真的不想使用一个开源过程,它已经完成了您所需要的所有事情,并保护您免受许多您可能还不知道的问题的影响,那么请尝试:

DECLARE @dbname  nvarchar(128), 
        @command nvarchar(1000) = N'SELECT 
            DB_NAME(), [filename] = physical_name, size 
          FROM sys.database_files;',
        @context nvarchar(1000), 
        @c       cursor;

SET @c = CURSOR FORWARD_ONLY STATIC READ_ONLY FOR 
  SELECT name FROM sys.databases WHERE state = 0;

OPEN @c;
FETCH NEXT FROM @c INTO @dbname;

WHILE @@FETCH_STATUS <> -1
BEGIN
  SET @context = QUOTENAME(@dbname) + N'.sys.sp_executesql';
  EXEC @context @command;  
  FETCH NEXT FROM @c INTO @dbname;
END 
-- also don't need to close/deallocate @local cursor.

如果您希望它们都在同一个数据集中返回,而不是每个数据库只有一个数据集,您可以:

DECLARE @dbname  nvarchar(128), 
        @command nvarchar(1000) = N'SELECT 
            DB_NAME(), [filename] = physical_name, size 
          FROM sys.database_files;',
        @context nvarchar(1000), 
        @c       cursor;

SET @c = CURSOR FORWARD_ONLY STATIC READ_ONLY FOR 
  SELECT name FROM sys.databases WHERE state = 0;

OPEN @c;
FETCH NEXT FROM @c INTO @dbname;

CREATE TABLE #t (db nvarchar(128), [filename] nvarchar(260), size int);

WHILE @@FETCH_STATUS <> -1
BEGIN
  SET @context = QUOTENAME(@dbname) + N'.sys.sp_executesql';
  INSERT #t EXEC @context @command;  
  FETCH NEXT FROM @c INTO @dbname;
END 
-- also don't need to close/deallocate @local cursor.

SELECT db, [filename], size FROM #t;

但是实际上,您可以使用sp_ineachdb。安装了该过程后,您的代码将立即变得如此简单:

EXEC master.dbo.sp_ineachdb @command = N'SELECT DB_NAME(),
  [filename] = physical_name, size FROM sys.database_files;';

相关问题