【问题标题】:T-SQL Efficiently getting row count and max Id valueT-SQL 有效地获取行数和最大 Id 值
【发布时间】:2019-03-11 22:14:53
【问题描述】:

我正在重写一个存储过程,该过程获取数据库中所有表的行数和最大值Id。该数据库有近 500 个表和超过 10 亿个条目,因此旧过程太慢而无法继续使用。

这是旧程序:

DECLARE @TableRowCounts TABLE ([TableName] VARCHAR(128), [RowCount] BIGINT, [MaxId] BIGINT) ;
INSERT INTO @TableRowCounts ([TableName], [RowCount], [MaxId])
EXEC sp_MSforeachtable 'SELECT ''?'' [TableName], COUNT(*) [RowCount], MAX(Id) [MaxId] FROM ?' ;
SELECT [TableName], [RowCount], [MaxId]
FROM @TableRowCounts
ORDER BY [TableName]

这将给出如下结果:

TableName | RowCount | MaxId
-------------------------------
TableA    | 12345678 | 12345688
TableB    | 90123456 | 90123466
TableC    | 78901234 | 78901244

我不能说运行需要多长时间,因为我还没有真正观察到它在当前数据库的大小下完成。

这是一个正在进行中的新查询:

SELECT 
  o.NAME, 
  i.rowcnt
FROM sysindexes AS i
INNER JOIN sysobjects AS o ON i.id = o.id
--INNER JOIN sys.tables AS t ON t.[object_id] = o.id ???
--INNER JOIN sys.schemas AS s on t.[schema_id] = s.[schema_id] ???
--INNER JOIN sys.columns AS c on t.[object_id] = c.[object_id] ???
WHERE i.indid < 2  AND OBJECTPROPERTY(o.id, 'IsMSShipped') = 0
ORDER BY o.NAME

我的想法是使用sys.schemassys.columns,这样我就可以在我的SELECT 中使用MAX(Id),但我目前还停留在如何完全整合此功能上。如果有其他更好的方法可以做到这一点,我愿意接受建议。

我确实需要行数和MAX(Id)我的数据集不应包含任何缺失的 Id,这将有助于一目了然地显示缺失的 Id。数据正在从外部源缓存,并且不应丢失任何 Id,因此如果行数不等于 MAX(Id),则使用数据库的客户端可以看到这一点并采取必要的措施来填充丢失的行。客户端还将行数和MAX(Id) 用于其他任务,例如将外部源的当前 ID 与数据库的最大 ID 进行比较。如果外部源的当前 Id 大于数据库的 MAX(Id),则有工作要做。

【问题讨论】:

  • 请注意,但如果您的表曾经对它们运行过 DELETE,或者如果 Id 是 IDENTITY 列并且标识已被重新植入,则 MAX(Id) 不会为您提供正确的行数随时。您可能不是这种情况,但想指出这一点。
  • 如果任何表中都不存在名称 ID 列怎么办?
  • 试试这个来获取行数stackoverflow.com/a/2221898/10532500
  • @squillman 我明白这一点。对不起,如果我的措辞不清楚。我想要行数以及表的最大 id。
  • @SurajKumar 所有表都有相同的模式,所以Id 保证存在。感谢您提供链接,但我的新程序已经成功获取每个表的行数,而不是 MAX(Id)

标签: sql-server tsql


【解决方案1】:

正如我在评论中所解释的,您可以使用触发器来完成此操作。

这将显着提高存储过程运行时长的速度。

以下脚本将在数据库中创建一个包含所有表的表,并为您提供在所有表上运行的触发器:

declare @loop   int
,       @query  varchar(max)

if not exists(select name from sysobjects where name = 'DatabaseTables')    --drop table DatabaseTables
create table DatabaseTables (id int identity primary key, TableName varchar(50), IdentityColumn varchar(50), [RowCount] int, MaxId int)
insert into DatabaseTables (TableName, IdentityColumn)
select
    name
,   column_name
from        sysobjects  o
inner join  information_schema.columns  c on o.name = c.table_name
where   xtype = 'u'
and     c.ordinal_position = 1
and     name    <>  'DatabaseTables'
and     data_type = 'int'
and     name not in (select TableName from DatabaseTables)
order by name

begin

select @loop = min(id) from DatabaseTables
while @loop is not null

begin
begin
    set @query = 

'set ansi_nulls on
go
set quoted_identifier on
go

create trigger '+(select TableName from DatabaseTables where id = @loop)+'_trg on '+(select TableName from DatabaseTables where id = @loop)+'
after insert
as

if (select trigger_nestlevel(object_id('''+(select TableName from DatabaseTables where id = @loop)+'_trg''))) > 1
     return

begin
    update DatabaseTables set
        [RowCount]  = (select count(*) from '+(select TableName from DatabaseTables where id = @loop)+')
    ,   MaxId       = (select max('+(select IdentityColumn from DatabaseTables where id = @loop)+') from '+(select TableName from DatabaseTables where id = @loop)+')
    where   TableName = '''+(select TableName from DatabaseTables where id = @loop)+'''
end;

'


    print (@query)
end
    select @loop = min(id) from DatabaseTables where id>@loop
end
end;

从这里开始,您无需运行存储过程来获取结果,而是运行:

select
*
from    DatabaseTables

让我知道这是否可行?

【讨论】:

    【解决方案2】:

    您可以尝试以下查询。描述以评论的形式给出。

    CREATE TABLE #x(t NVARCHAR(520), c BIGINT); --Used to store max id
    CREATE TABLE #counts --Used to store rowcount
    (
        table_name varchar(255),
        row_count int
    )
    
    --Query to get max id in a table - #x
    DECLARE @sql NVARCHAR(MAX);
    
    SET @sql = N'';
    SELECT @sql = @sql + N'INSERT #x SELECT ''' 
      + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ''',
      MAX(' + c.name + ') FROM '
      + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ';'
    
    FROM sys.columns C
        INNER JOIN sys.tables T ON C.object_id = T.object_id
        INNER JOIN sys.schemas s ON S.schema_id = T.schema_id
    WHERE c.name = 'Id'; --To specify the column name for max id value
    
    EXEC sp_executesql @sql;
    --SELECT t, c FROM #x; --To get the max id
    
    --Query to get row count in a table - #counts
    EXEC sp_MSForEachTable @command1='INSERT #counts (table_name, row_count) SELECT ''?'', COUNT(*) FROM ?'
    --SELECT table_name, row_count FROM #counts ORDER BY table_name, row_count DESC -- To get the row count
    
    Select table_name as [Table Name], c as [Max Id], row_count as [Total Rows]
    from #counts
    inner join #x on t = table_name
    
    DROP TABLE #counts
    DROP TABLE #x;
    

    输出将采用以下格式。

    Table Name  Max Id  Total Rows
    ----------------------------
    [dbo].[Employee]    8   8
    [dbo].[test]        3   3
    [dbo].[Family]      2   6
    

    【讨论】:

    • 查询需要很长时间才能运行。我认为这个(以及我的原始查询)的问题是它在每个表上使用COUNT(*),这是一个非常昂贵的操作。如果有办法使用sys.indexes 目录来代替行计数,我相信它会快得多。
    猜你喜欢
    • 2021-03-02
    • 2011-07-20
    • 2020-03-28
    • 1970-01-01
    • 2021-12-24
    • 2023-03-09
    • 2016-04-16
    • 2017-06-23
    • 2019-11-13
    相关资源
    最近更新 更多