So fragen Sie eine Datenbank nach leeren Tabellen ab


28

Aufgrund einiger Entwickler, die an unserem System gearbeitet haben, hatten wir Probleme mit leeren Tabellen. Wir haben festgestellt, dass während der Übertragung in die Cloud mehrere Tabellen kopiert wurden, die darin enthaltenen Daten jedoch nicht.

Ich möchte eine Abfrage der Systemtabellen ausführen, um festzustellen, welche Benutzertabellen leer sind. Wir verwenden MS SQL 2008 R2.

Danke für die Hilfe.

Antworten:


46

Hebel sys.tablesund 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;

Verwenden Sie eine Summe von Zeilen, um sicherzustellen, dass Sie nicht mit partitionierten Tabellen verwechselt werden. Index_ID von 0 oder 1 bedeutet, dass Sie nur die Zeilenzahlen für Ihre Heaps oder Clustered-Indizes anzeigen.


9

Wie Mike Fal und Kin beide bemerkt haben, sind die Systemtabellen dein Freund.

Für eine vollständigere Codeversion habe ich mir Folgendes ausgedacht, mit dem Sie den gesamten von jeder Tabelle in Ihrer Datenbank verwendeten Datenbereich anzeigen können.

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;

Ergebnisse der letzten 3 Aussagen:

Bildbeschreibung hier eingeben


6

Hier ist eine PowerShell-Version:

Verwenden von SQL Server-Verwaltungsobjekten (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
}

Abhängig von der Anzahl der Datenbanken können Sie die obige Funktion für eine Liste aller Datenbanknamen verwenden, die in einer Variablen enthalten sind, und diese alle gleichzeitig ausgeben, wenn Sie sich mit einem Server befassen:


$DBList = 'MyDatabase1','MyDatabase2'

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

4

Andere Antworten hier sind großartig, aber der Vollständigkeit halber: SQL Server Management Studio> Klicken Sie mit der rechten Maustaste auf die Datenbank> Berichte> Standardberichte> Datenträgernutzung nach Tabelle


Dies würde jedoch alle Tabellen zurückgeben, nicht nur die, die leer sind. Ich glaube nicht, dass Sie Filter auf diese Berichte anwenden können.
Shawn Melton

Das ist wahr. Pad Pad Pad
onupdatecascade

Aber es ist eine sehr gute Antwort. Nur 2 Klicks entfernt, einfacher als je zuvor.
Marian,

Sie können die Liste jedoch nach der Anzahl der Datensätze ordnen
Robert Mikes

1

Im Allgemeinen erstelle ich nur eine Abfrage, die die gewünschte Abfrage erstellt, und führe sie dann manuell aus, aber wenn Sie alles auf einmal möchten ...

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;

1

Als zusätzliche Antwort ist hier die undokumentierte gespeicherte Systemprozedur sp_MSforeachtablenützlich.

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 ;

Es gelten die üblichen Warnungen zu undokumentierten Funktionen.

Sie können sich den Quellcode der Prozedur in master ansehen, wenn Sie neugierig sind oder sicher sein möchten, dass er keine bösen Nebenwirkungen hat. Es verwendet dynamisches SQL zum Erstellen eines Cursors, der für die Leistung schlecht ist (Cursor = langsam!). Verwenden Sie dieses Verfahren daher nur für eine einmalige Aufgabe.

Darüber hinaus sp_MSforeachtableist in der Azure-Datenbank nicht verfügbar.


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

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

Ich denke nicht, dass dies die bestehenden Antworten verbessert
James Anderson
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.