Comment interroger une base de données pour des tables vides

28

En raison de certains «développeurs» que nous avions à travailler sur notre système, nous avons eu des problèmes avec des tables vides. Nous avons constaté que lors du transfert vers le cloud, plusieurs tables ont été copiées, mais pas les données qu'elles contiennent.

Je voudrais lancer une requête sur les tables système pour trouver quelles tables utilisateur sont vides. Nous utilisons MS SQL 2008 R2.

Merci pour l'aide.

marteau de code
la source

Réponses:

46

Effet de levier sys.tableset 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;

Utilisez une somme de lignes pour vous assurer de ne pas avoir de confusion avec les tables partitionnées. Index_ID de 0 ou 1 signifie que vous ne regardez que le nombre de lignes pour vos segments ou index cluster.

Mike Fal
la source
9

Comme l'ont noté Mike Fal et Kin, les tables système sont votre ami.

Pour une version plus complète du code, j'ai trouvé ce qui suit, qui vous permettrait de voir l'espace de données total utilisé par chaque table de votre base de données.

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;

Résultats des 3 dernières déclarations:

entrez la description de l'image ici

Max Vernon
la source
6

Voici une version PowerShell:

Utilisation des objets de gestion 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
}

En fonction du nombre de bases de données, vous pouvez utiliser la fonction ci-dessus par rapport à une liste de chaque nom de base de données remplie dans une variable et les afficher toutes en même temps, si vous traitez avec un serveur:


$DBList = 'MyDatabase1','MyDatabase2'

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

Les autres réponses ici sont excellentes, mais pour être complet: SQL Server Management Studio> cliquez avec le bouton droit sur la base de données> Rapports> Rapports standard> Utilisation du disque par table

onupdatecascade
la source
Cela retournerait toutes les tables, pas seulement celles qui sont vides. Je ne pense pas que vous puissiez appliquer des filtres à ces rapports.
Shawn Melton
C'est vrai. Pad Pad Pad
onupdatecascade
Mais c'est une très bonne réponse. Juste à 2 clics de distance, plus facile que jamais.
Marian
Cependant, vous pouvez commander la liste par nombre d'enregistrements
Robert Mikes
1

Je crée généralement une requête qui crée la requête que je veux, puis je l'exécute manuellement, mais si vous le souhaitez en une seule fois ...

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
la source
1

Comme réponse supplémentaire, la procédure stockée système non documentée sp_MSforeachtableest utile ici.

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 ;

Les avertissements habituels concernant les fonctionnalités non documentées s'appliquent.

Vous pouvez consulter le code source de la procédure dans master si vous êtes curieux ou si vous voulez être certain qu'il n'a pas d'effets secondaires désagréables. Il utilise du SQL dynamique pour créer un curseur, ce qui est mauvais pour les performances (curseur = lent!), Donc n'utilisez cette procédure que pour une tâche ponctuelle.

En outre, sp_MSforeachtablen'est pas disponible dans Azure Database.

Marcheur de Greenstone
la source
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
la source
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
la source
Je ne pense pas que cela améliore les réponses existantes
James Anderson