Cómo consultar una base de datos para tablas vacías

28

Debido a algunos 'desarrolladores' que teníamos trabajando en nuestro sistema, hemos tenido problemas con las tablas vacías. Hemos encontrado que durante la transferencia a la nube se copiaron varias tablas, pero los datos que contenían no.

Me gustaría ejecutar una consulta en las tablas del sistema para encontrar qué tablas de usuario están vacías. Estamos utilizando MS SQL 2008 R2.

Gracias por la ayuda.

martillo de código
fuente

Respuestas:

46

Apalancamiento sys.tablesy 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 una suma de filas solo para asegurarse de que no tiene confusión con las tablas particionadas. Index_ID de 0 o 1 significa que solo está mirando los recuentos de filas para sus montones o índices agrupados.

Mike Fal
fuente
9

Como Mike Fal y Kin han notado, las tablas del sistema son su amigo.

Para una versión más completa del código, se me ocurrió lo siguiente, que le permitiría ver el espacio total de datos utilizado por cada tabla en su base de datos.

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 de las últimas 3 declaraciones:

ingrese la descripción de la imagen aquí

Max Vernon
fuente
6

Aquí hay una versión de PowerShell:

Uso de objetos de administración de 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
}

Dependiendo de la cantidad de bases de datos, puede usar la función anterior en una lista de cada nombre de base de datos poblado en una variable y generarlo todo al mismo tiempo, si se trata de un servidor:


$DBList = 'MyDatabase1','MyDatabase2'

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

Otras respuestas aquí son geniales, pero para completar: SQL Server Management Studio> haga clic con el botón derecho en DB> Informes> Informes estándar> Uso de disco por tabla

onupdatecascade
fuente
Sin embargo, esto devolvería todas las tablas, no solo las que están vacías. No creo que pueda aplicar filtros a esos informes.
Shawn Melton
Eso es verdad. Pad Pad Pad
onupdatecascade
Pero es una muy buena respuesta. Solo a 2 clics de distancia, más fácil que nunca.
Marian
Sin embargo, puede ordenar la lista por número de registros
Robert Mikes,
1

En general, solo creo una consulta que crea la consulta que quiero y luego la ejecuto manualmente, pero si lo quieres todo de una 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
fuente
1

Como respuesta adicional, el procedimiento almacenado del sistema no documentado sp_MSforeachtablees útil aquí.

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 ;

Se aplican las advertencias habituales sobre las características no documentadas.

Puede ver el código fuente del procedimiento en master si tiene curiosidad o si quiere estar seguro de que no tiene efectos secundarios desagradables. Utiliza SQL dinámico para construir un cursor, lo cual es malo para el rendimiento (cursor = lento!), Así que solo use este procedimiento para una tarea única.

Además, sp_MSforeachtableno está disponible en Azure Database.

Caminante de piedra verde
fuente
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
fuente
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
fuente
No creo que esto mejore las respuestas existentes
James Anderson