2

我正在尝试获取服务器中所有表的行数(不是特定数据库,而是服务器上的所有数据库,不包括 msdb、model、master 等)。除了数据库名称、表名称和行数之外,我不需要返回任何其他详细信息。

我解决这个问题的方法是获取服务器中的所有数据库并在它们上放置一个 id,这将在 while 循环中引用(从 id 开始直到最大 id)。然后,在 while 循环中,我获取匹配数据库 ID 中的表和行数。我的问题是 USE DatabaseName 似乎不允许我使其成为动态的,这意味着我不能将数据库名称存储在变量中,并在执行具有行计数查询的表时将其用作引用的数据库。

是否有另一种我缺少的方法(我查看了许多其他示例 - 经常使用游标,它在代码中似乎更长并且似乎使用更多资源 - 即使我使用,这也是一个相对快速的查询表中最大的数据库,除了它没有命中下一个数据库等等),还是我在代码中遗漏了一些明显的东西来使这个动态?

DECLARE @ServerTable TABLE(
    DatabaseID INT IDENTITY(1,1),
    DatabaseName VARCHAR(50)
)

DECLARE @count INT
DECLARE @start INT = 1
SELECT @count = COUNT(*) FROM sys.databases WHERE name NOT IN ('master','tempdb','model','msdb')

INSERT INTO @ServerTable (DatabaseName)
SELECT name 
FROM sys.databases
WHERE name NOT IN ('master','tempdb','model','msdb')

WHILE @start < @count
BEGIN

    DECLARE @db VARCHAR(50)
    SELECT @db = DatabaseName FROM @ServerTable WHERE DatabaseID = @start

    -- This is the problem, as the USE doesn't seem to allow it to be dynamic.
    USE @db
    GO

    SELECT @db
        ,o.name [Name]
        ,ddps.row_count [Row Count]
    FROM sys.indexes AS i
        INNER JOIN sys.objects AS o ON i.OBJECT_ID = o.OBJECT_ID
        INNER JOIN sys.dm_db_partition_stats AS ddps ON i.OBJECT_ID = ddps.OBJECT_ID AND i.index_id = ddps.index_id 
    WHERE i.index_id < 2  AND o.is_ms_shipped = 0 
    ORDER BY o.NAME

    SET @start = @start + 1
END

注意:我尝试检查 sys.objects 和 sys.indexes 以查看是否可以使用数据库名称进行过滤,但我没有运气。

更新:我试着把它SELECT变成动态的东西,但没有成功(注意下面的代码只显示了变化SELECT):

SET @sql = '
    SELECT ' + @db + ' [Database]
        ,o.name [Name]
        ,ddps.row_count [Row Count]
    FROM  ' + @db + '.sys.objects
        INNER JOIN ' + @db + ' sys.objects AS o ON i.OBJECT_ID = o.OBJECT_ID
        INNER JOIN ' + @db + ' sys.dm_db_partition_stats AS ddps ON i.OBJECT_ID = ddps.OBJECT_ID AND i.index_id = ddps.index_id 
    WHERE i.index_id < 2  AND o.is_ms_shipped = 0 
    ORDER BY o.NAME'
4

2 回答 2

3

不,这基本上就是你这样做的方式。

我不确定你为什么认为while 循环比游标快(尽管这是一个常见的误解)。它们本质上是一样的。我并不总是使用游标,但当我这样做时,我会使用LOCAL FAST_FORWARD- 确保你也这样做。有关更多信息,请参阅这篇文章:

为了减少像这样的单个任务所需的代码,您可能对sp_MSforeachdb我编写的替换感兴趣 (sp_MSforeachdb是一个内置的、未记录的和不受支持的存储过程,它将为每个数据库重复一个命令,但是不可能,比如说,过滤掉系统数据库,并且它还有一个严重的错误,有时会停止执行):

另一种方法是动态 SQL。

DECLARE @sql NVARCHAR(MAX) = N'';

SELECT @sql += '
  SELECT db = N''' + name + '''
    ,o.name [Name]
    ,ddps.row_count [Row Count]
  FROM ' + QUOTENAME(name) + '.sys.indexes AS i
    INNER JOIN ' + QUOTENAME(name) + '.sys.objects AS o 
      ON i.OBJECT_ID = o.OBJECT_ID
    INNER JOIN ' + QUOTENAME(name) + '.sys.dm_db_partition_stats AS ddps 
      ON i.OBJECT_ID = ddps.OBJECT_ID AND i.index_id = ddps.index_id 
    WHERE i.index_id < 2  AND o.is_ms_shipped = 0 
    ORDER BY o.NAME;'
FROM sys.databases 
WHERE database_id > 4;

PRINT @sql;
--EXEC sp_executesql @sql;

(打印在那里,以便您可以在执行之前检查命令。如果您有大量数据库,它可能会在 8K 处被截断,但不要惊慌 - 这只是 SSMS 中的显示问题,命令已完成.)

您也可以先构建一个#temp 表,然后插入其中,这样您就可以使用一个结果集,例如

CREATE TABLE #x(db SYSNAME, o SYSNAME, rc SYSNAME);

DECLARE @sql NVARCHAR(MAX) = N'';

SELECT @sql += 'INSERT #x(db,o,rc)
  SELECT db = N''' + name + '''
    ,o.name [Name]
    ,ddps.row_count [Row Count]
  FROM ' + QUOTENAME(name) + '.sys.indexes AS i
    INNER JOIN ' + QUOTENAME(name) + '.sys.objects AS o 
      ON i.OBJECT_ID = o.OBJECT_ID
    INNER JOIN ' + QUOTENAME(name) + '.sys.dm_db_partition_stats AS ddps 
      ON i.OBJECT_ID = ddps.OBJECT_ID AND i.index_id = ddps.index_id 
    WHERE i.index_id < 2  AND o.is_ms_shipped = 0 
    ORDER BY o.NAME;'
FROM sys.databases 
WHERE database_id > 4;

EXEC sp_executesql @sql;

SELECT db, o, rc FROM #x ORDER BY db, o;

现在,不要误以为这也不是在使用游标或循环——它是。但它是在循环中构建命令,而不是在循环中执行它。

于 2013-03-27T20:25:47.217 回答
0

就您进行动态查询而言,而不是使用 using,您可以使用您选择的@db变量为您的表名创建一个完全限定的名称。

所以它会是'FROM ' + @db+'.sys.objects'等等。

您必须检查您的数据库名称是否有效(例如,如果您的名称由于某种原因需要括号)。

于 2013-03-27T20:27:56.163 回答