Como consultar um banco de dados para tabelas vazias

28

Devido a alguns 'desenvolvedores' que estávamos trabalhando em nosso sistema, tivemos problemas com tabelas vazias. Descobrimos que durante a transferência para a nuvem várias tabelas foram copiadas, mas os dados nelas não foram.

Eu gostaria de executar uma consulta nas tabelas do sistema para encontrar quais tabelas de usuários estão vazias. Estamos usando o MS SQL 2008 R2.

Obrigado pela ajuda.

codehammer
fonte

Respostas:

46

Alavancagem sys.tablese sys.partitions:

select
    t.name table_name,
    s.name schema_name,
    sum(p.rows) total_rows
from
    sys.tables t
    join sys.schemas s on (t.schema_id = s.schema_id)
    join sys.partitions p on (t.object_id = p.object_id)
where p.index_id in (0,1)
group by t.name,s.name
having sum(p.rows) = 0;

Use uma soma de linhas apenas para garantir que você não tenha confusão com tabelas particionadas. Index_ID de 0 ou 1 significa que você está olhando apenas para as contagens de linhas dos seus heaps ou índices agrupados.

Mike Fal
fonte
9

Como Mike Fal e Kin observaram, as tabelas do sistema são suas amigas.

Para uma versão mais completa do código, criei o seguinte, que permitiria ver o espaço total de dados usado por cada tabela no seu banco de dados.

USE master;

CREATE DATABASE TestDB;
GO

USE tempdb;
ALTER DATABASE TestDB SET RECOVERY SIMPLE;
GO

USE TestDB;
CREATE TABLE Test1 (
    Test1ID INT NOT NULL PRIMARY KEY IDENTITY(1,1)
    , TestData nvarchar(255) CONSTRAINT DF_Test1_TestData DEFAULT (NEWID())
);

GO

TRUNCATE TABLE Test1;

SELECT s.name + '.' + t.name AS TableName,
    sum(p.rows) AS TotalRows,
    SUM(au.data_pages) AS DataPagesUsed
FROM sys.tables t
    INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
    INNER JOIN sys.partitions p ON t.object_id = p.object_id
    INNER JOIN sys.allocation_units au ON p.hobt_id = au.container_id
WHERE au.type = 1 or au.type = 3 
    AND t.is_ms_shipped = 0
GROUP BY s.name, t.name
    ORDER BY SUM(au.data_pages) DESC;

INSERT INTO Test1 DEFAULT VALUES;

SELECT s.name + '.' + t.name AS TableName,
    sum(p.rows) AS TotalRows,
    SUM(au.data_pages) AS DataPagesUsed
FROM sys.tables t
    INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
    INNER JOIN sys.partitions p ON t.object_id = p.object_id
    INNER JOIN sys.allocation_units au ON p.hobt_id = au.container_id
WHERE au.type = 1 or au.type = 3 
    AND t.is_ms_shipped = 0
GROUP BY s.name, t.name
    ORDER BY SUM(au.data_pages) DESC;

Resultados das últimas 3 declarações:

insira a descrição da imagem aqui

Max Vernon
fonte
6

Aqui está uma versão do PowerShell:

Usando objetos de gerenciamento do SQL Server (SMO)


function Find-EmptyTables ($server,$database) 
{

    # Load SMO assembly
    [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | Out-Null

    $s = New-Object 'Microsoft.SqlServer.Management.Smo.Server' $server
    $db = $s.Databases.Item($database)
    $db.Tables | Where-Object { $_.RowCount -eq 0 } | Select Schema, Name, RowCount
}

Dependendo do número de bancos de dados, você pode usar a função acima em uma lista de cada nome de banco de dados preenchido em uma variável e produzir tudo ao mesmo tempo, se estiver lidando com um servidor:


$DBList = 'MyDatabase1','MyDatabase2'

foreach ($d in $DBList) {
Find-EmptyTables -server MyServer -database $d | 
  Select @{Label="Database";Expression={$d}}, Schema, Name, RowCount
}
Shawn Melton
fonte
4

Outras respostas aqui são ótimas, mas para completar: SQL Server Management Studio> clique com o botão direito do mouse em DB> Relatórios> Relatórios Padrão> Uso de Disco por Tabela

onupdatecascade
fonte
Isso retornaria todas as tabelas, não apenas as que estão vazias. Não acredito que você possa aplicar filtros a esses relatórios.
quer
Isso é verdade. Pad Pad Pad
onupdatecascade 13/08
Mas é uma resposta muito boa. Apenas a 2 cliques de distância, mais fácil do que nunca.
Marian
No entanto, você pode solicitar a lista por número de registros
Robert Mikes
1

Geralmente, apenas crio uma consulta que cria a consulta que desejo e depois a executo manualmente, mas se você quiser tudo de uma só vez ...

declare @sql nvarchar(max) ;

set @sql = ';with cte as (' + (select  
        ( 
            SELECT case when row_number() 
                 over (order by table_schema, table_name) = 1 then '       ' 
                   else ' union ' end + 
                'select count(*) rws, ''[' +
                      t.TABLE_SCHEMA +'].[' + t.table_name + 
                ']'' tbl from ' + '['+ 
                      t.TABLE_SCHEMA + '].[' + TABLE_NAME + ']' + 
                CHAR(10) AS [data()] 
            FROM INFORMATION_SCHEMA.TABLES t
            FOR XML PATH ('') 
        )) + ') select * from cte where rws = 0;'

execute sp_executesql @sql;
jmoreno
fonte
1

Como resposta adicional, o procedimento armazenado do sistema não documentado sp_MSforeachtableé útil aqui.

CREATE TABLE #CountRows ( TableName nvarchar(260), NumRows int) ;
GO
EXEC sp_MSforeachtable 'insert into #CountRows select ''?'', count(*) from ?' ;
SELECT * FROM #CountRows WHERE NumRows = 0 ORDER BY TableName ;
DROP TABLE #CountRows ;

Os avisos usuais sobre recursos não documentados se aplicam.

Você pode consultar o código-fonte do procedimento no master se estiver curioso ou se quiser ter certeza de que ele não tem efeitos colaterais desagradáveis. Ele usa SQL dinâmico para criar um cursor, o que é ruim para o desempenho (cursor = lento!), Portanto, use este procedimento apenas para uma tarefa pontual.

Além disso, sp_MSforeachtablenão está disponível no Banco de Dados do Azure.

Andarilho de Pedra Verde
fonte
1
DECLARE @toCheck INT;
DECLARE @countoftables INT;
DECLARE @Qry NVARCHAR(100);
DECLARE @name VARCHAR(100);
BEGIN
    IF object_id('TEMPDB.DBO.#temp') IS NOT NULL drop table #temp;
    SELECT ROW_NUMBER() OVER(ORDER BY name) AS ROW,CountStatement = 'SELECT @toCheck = COUNT(*) FROM  ' + name,name INTO #temp FROM SYS.TABLES  WITH (NOLOCK)
    --SELECT * FROM #temp  ORDER BY ROW
    SET @countoftables  =(SELECT COUNT(*) FROM #temp)
    WHILE (@countoftables > 0)
        BEGIN
            SET @Qry =  (SELECT CountStatement FROM #temp  WITH (NOLOCK) WHERE ROW = @countoftables);
            SET @name = (SELECT name FROM #temp  WITH (NOLOCK) WHERE ROW = @countoftables);
            EXEC SP_EXECUTESQL @qry,N'@toCheck INT OUTPUT',@toCheck OUTPUT;
            IF(@toCheck=0)
                BEGIN
                    PRINT 'Table: ' + @name + ', count: ' +  convert(varchar(10),@toCheck);
                END
            --ELSE
            --  BEGIN
            --      PRINT 'Table: ' + @name + ', count: ' +  convert(varchar(10),@toCheck);
            --  END
            SET  @countoftables = @countoftables -1;            
        END
END
Dinesh Ramaian
fonte
1
SELECT      T.name [Table Name],i.Rows [Number Of Rows]
FROM        sys.tables T
JOIN        sys.sysindexes I ON T.OBJECT_ID = I.ID
WHERE       indid IN (0,1) AND i.Rows<1
ORDER BY    i.Rows DESC,T.name
Pavan Kumar Gupta
fonte
Eu não acho que isso melhora nas respostas existentes
James Anderson