305

I am searching for a SQL Script that can be used to determine if there is any data (i.e. row count) in any of the tables of a given database.

The idea is to re-incarnate the database in case there are any rows existing (in any of the database).

The database being spoken of is Microsoft SQL Sserver.

Could someone suggest a sample script?

Thom A
  • 88,727
  • 11
  • 45
  • 75
vijaysylvester
  • 4,750
  • 7
  • 29
  • 41
  • A nice article discussing different approaches: https://www.brentozar.com/archive/2014/02/count-number-rows-table-sql-server/ – Rory Apr 19 '22 at 11:43

13 Answers13

454

The following SQL will get you the row count of all tables in a database:

CREATE TABLE #counts
(
    table_name varchar(255),
    row_count int
)

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
DROP TABLE #counts

The output will be a list of tables and their row counts.

If you just want the total row count across the whole database, appending:

SELECT SUM(row_count) AS total_row_count FROM #counts

will get you a single value for the total number of rows in the whole database.

daveloyall
  • 2,140
  • 21
  • 23
adrianbanks
  • 81,306
  • 22
  • 176
  • 206
  • great, works as described (though takes a while), also good as I'm not allowed to run the reports, thanks – adolf garlic Aug 24 '11 at 09:58
  • 7
    Have upvoted this - but think it important to mention that `sp_MSForEachTable` is an undocumented proc - afaik, this means that MS does not support it and may remove it from future versions without notice? – MarkD Jun 26 '14 at 08:04
  • 2
    @MarkD Yes, that is correct, although it has been there for several versions of SQL Server without change. – adrianbanks Jun 27 '14 at 20:48
  • 5
    also, this will do a full table scan on all tables in the database, so running it on a production server will cause 'a bit' of disk io. – Andrew Hill Dec 24 '15 at 00:56
  • Thank you for this quite elegant solution. Only one minor issue: The order clause (ORDER BY table_name, row_count DESC) is a bit strange. When the result is sorted by the table name, it does not make much sense to include the row count column as the second sort column. Since each table must have a unique name in a MS SQL database, there can never be two rows in the result that have an identical values in the table_name column, therefore the sorting by the row_count column is meaningless. – Michael Geller Jan 11 '17 at 18:10
  • This doesn't work for me in MS SQL 2012. The command seems to run fine, but the resulting query on the #counts table is empty (zero rows). – RoG Apr 20 '17 at 03:17
  • 2
    Doesn't work on 2016, 'sp_MSForEachTable' doesn't exist. – Alan B Jun 12 '17 at 11:54
  • 1
    @AlanB: `sp_MSForEachTable` is still there in SQL Server 2016, I've just tried it. – adrianbanks Jun 14 '17 at 14:33
  • 11
    Try this you don't need to create temp table and then drop it. SELECT T.name AS [TABLE NAME], I.rows AS [ROWCOUNT] FROM sys.tables AS T INNER JOIN sys.sysindexes AS I ON T.object_id = I.id AND I.indid < 2 ORDER BY I.rows DESC – Muhammad Sohail May 25 '18 at 05:07
  • Tried it, works on SQL Server 2017 as well. @MarkD - in theory you're right, in practice it's efficient and good enough. If MS removes it in the future, we'll resort to something else. Remember it's not for production use anyway, so there's virtually no issue. – Ofer Zelig Apr 11 '19 at 02:59
  • @AndrewHill - of course, it goes without saying – Ofer Zelig Apr 11 '19 at 02:59
  • @RoG - guess it's a permission issue. The feature works. – Ofer Zelig Apr 11 '19 at 03:00
  • @AlanB - guess it's a permission issue. The feature works. – Ofer Zelig Apr 11 '19 at 03:00
  • This works. But, I was unsuccessful at implementing it with a linked database. – givonz Mar 29 '22 at 16:05
  • @AlanB Very late comment, I know, but for anyone out there, also make sure the server does not have a case-sensitive collation - for example, sp_msforeachtable would not be found on a case-sensitive server. – jrdevdba Apr 03 '23 at 15:05
  • Can you not do this via a CTE and avoid the ugly temp table creation/deletion? – JGFMK Jul 26 '23 at 14:14
237

If you want to by pass the time and resources it takes to count(*) your 3million row tables. Try this per SQL SERVER Central by Kendal Van Dyke.


Row Counts Using sysindexes If you're using SQL 2000 you'll need to use sysindexes like so:

-- Shows all user tables and row counts for the current database 
-- Remove OBJECTPROPERTY function call to include system objects 
SELECT o.NAME,
  i.rowcnt 
FROM sysindexes AS i
  INNER JOIN sysobjects AS o ON i.id = o.id 
WHERE i.indid < 2  AND OBJECTPROPERTY(o.id, 'IsMSShipped') = 0
ORDER BY o.NAME

If you're using SQL 2005 or 2008 querying sysindexes will still work but Microsoft advises that sysindexes may be removed in a future version of SQL Server so as a good practice you should use the DMVs instead, like so:

-- Shows all user tables and row counts for the current database 
-- Remove is_ms_shipped = 0 check to include system objects 
-- i.index_id < 2 indicates clustered index (1) or hash table (0) 
SELECT o.name,
  ddps.row_count 
FROM sys.indexes AS i
  INNER JOIN sys.objects AS o ON i.OBJECT_ID = o.OBJECT_ID
  INNER JOIN sys.dm_db_partition_stats AS ddps ON i.OBJECT_ID = ddps.OBJECT_ID
  AND i.index_id = ddps.index_id 
WHERE i.index_id < 2  AND o.is_ms_shipped = 0 ORDER BY o.NAME 
Keng
  • 52,011
  • 32
  • 81
  • 111
  • Ok, the script works but requires a "use DB" at the beginning of it. So, while it works as-is, it was misleading. – djangofan Oct 05 '12 at 15:04
  • 4
    On my database, the accepted answer takes 10 seconds. *This* answer takes 0! +1 for you Keng. Though I did add `JOIN sys.schemas s ON s.schema_id = o.schema_id` and included s.Name to see the qualified table names. – Bernhard Hofmann Sep 19 '13 at 09:25
  • 6
    Note that using sysindexes or dm_dp_partition_stats will give you an **approximation** of the number of rows! – krisku Oct 02 '13 at 10:09
  • 2
    Make sure you use the right DB - if you select master 05/08 query will come back blank. I would suggest adding "USE [enter your db name here]" to the top of that query in the answer. – user1221784 Mar 18 '14 at 17:56
  • The row_count values might not always reflect the exact current count of rows. For example, when a transaction is rolled back, the row_count value might not be updated immediately. In such situations, you might need to manually update the statistics to reflect the accurate row counts. – Paul Maxwell Aug 19 '23 at 02:52
140

Works on Azure, doesn't require stored procs.

SELECT t.name       AS table_name
       ,s.row_count AS row_count
FROM   sys.tables t
JOIN   sys.dm_db_partition_stats s
  ON t.OBJECT_ID = s.OBJECT_ID
 AND t.type_desc = 'USER_TABLE'
 AND t.name NOT LIKE '%dss%' --Exclude tables created by SQL Data Sync for Azure.
 AND s.index_id IN (0, 1)
ORDER  BY table_name;

Credit.

AeyJey
  • 2,111
  • 2
  • 14
  • 21
Adrian Hope-Bailie
  • 2,445
  • 1
  • 23
  • 26
  • 2
    What's "AND t.name not like '%dss%'" accomplish? – quillbreaker Nov 14 '14 at 15:30
  • 4
    @quillbreaker: excluding tables created by SQL Azure Data Sync – Adam Szabo Feb 27 '15 at 15:08
  • 1
    This worked great. I sat back and said "this might take a while" and it was done in two seconds. Was dealing with a 528 table database and had no idea how large it actually was – trench Jul 25 '18 at 12:52
  • This worked fine on SQL Server 2016; Not required this '%dss%' – Zaki Mohammed Jul 26 '18 at 09:19
  • Beautiful, as @trench exclaimed, I was a bit scary, running and querying some command, on production, with hundreds of million records each, in several tables.. but it just went by in a jiffy _on Azure database_ . Thnx Very handy... – Irf Jan 23 '19 at 09:49
  • Thank you, this is perfect! how can I execute this for the entire sever with about 100+ databases? – rachel May 21 '20 at 01:50
  • requires `VIEW DATABASE PERFORMANCE STATE` permission – scottrudy Feb 21 '23 at 13:56
71

This one looks better than the others I think.

USE  [enter your db name here]
GO

SELECT      SCHEMA_NAME(A.schema_id) + '.' +
        --A.Name, SUM(B.rows) AS 'RowCount'  Use AVG instead of SUM
          A.Name, AVG(B.rows) AS 'RowCount'
FROM        sys.objects A
INNER JOIN sys.partitions B ON A.object_id = B.object_id
WHERE       A.type = 'U'
GROUP BY    A.schema_id, A.Name
GO
Community
  • 1
  • 1
İsmet Alkan
  • 5,361
  • 3
  • 41
  • 64
  • 7
    +1: this query just requires `data_reader` permissions. –  Jun 05 '14 at 18:09
  • 4
    I don't know why but this query doesn't count rows in all tables accurately. Counts in some tables are doubled. I found Rikin Patel's query to be accurate. – Dan Jan 14 '15 at 20:36
  • 10
    @Dan The difference between this one and the one by Rikin Patel is that this one is not checking that the index_id of the sys.partitions entry is either 0 or 1. So it isn't just that some rows are doubled, it is that some rows are multiplied by the number of indexes they have. So if you have a table with 100 rows in it and you've defined 3 indexes on it, the above query would show 3*100=300 rows for that table. – Anssssss Oct 06 '15 at 21:49
  • The comments above are valid - but all I needed was to understand which tables had _some_ rows in and which had none. I only had `data_reader` permissions, so none of the other solutions worked for me - only this one. – Ed Graham Aug 04 '20 at 16:27
28

Short and sweet

sp_MSForEachTable 'DECLARE @t AS VARCHAR(MAX); 
SELECT @t = CAST(COUNT(1) as VARCHAR(MAX)) 
+ CHAR(9) + CHAR(9) + ''?'' FROM ? ; PRINT @t'

Output:

enter image description here

Rikin Patel
  • 8,848
  • 7
  • 70
  • 78
  • Years later, this one worked perfectly for a quick look at how many rows each of your tables have on SQL Server 2017. – Dwight Jun 21 '19 at 05:24
17
SELECT 
    sc.name +'.'+ ta.name TableName, SUM(pa.rows) RowCnt
FROM 
    sys.tables ta
INNER JOIN sys.partitions pa
    ON pa.OBJECT_ID = ta.OBJECT_ID
INNER JOIN sys.schemas sc
    ON ta.schema_id = sc.schema_id
WHERE ta.is_ms_shipped = 0 AND pa.index_id IN (1,0)
GROUP BY sc.name,ta.name
ORDER BY SUM(pa.rows) DESC
Rikin Patel
  • 8,848
  • 7
  • 70
  • 78
15

SQL Server 2005 or later gives quite a nice report showing table sizes - including row counts etc. It's in Standard Reports - and it is Disc Usage by Table.

Programmatically, there's a nice solution at: http://www.sqlservercentral.com/articles/T-SQL/67624/

Peter Schofield
  • 939
  • 7
  • 13
  • 4
    Without any admin rights this works well. SELECT T.name AS [TABLE NAME], I.rows AS [ROWCOUNT] FROM sys.tables AS T INNER JOIN sys.sysindexes AS I ON T.object_id = I.id AND I.indid < 2 ORDER BY I.rows DESC – ASH Nov 02 '17 at 15:15
8

Don't use SELECT COUNT(*) FROM TABLENAME, since that is a resource intensive operation. One should use SQL Server Dynamic Management Views or System Catalogs to get the row count information for all tables in a database.

daveloyall
  • 2,140
  • 21
  • 23
4

Here's a dynamic SQL approach that also gives you the schema as well:

DECLARE @sql nvarchar(MAX)

SELECT
    @sql = COALESCE(@sql + ' UNION ALL ', '') +
        'SELECT
            ''' + s.name + ''' AS ''Schema'',
            ''' + t.name + ''' AS ''Table'',
            COUNT(*) AS Count
            FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name)
    FROM sys.schemas s
    INNER JOIN sys.tables t ON t.schema_id = s.schema_id
    ORDER BY
        s.name,
        t.name

EXEC(@sql)

If needed, it would be trivial to extend this to run over all databases in the instance (join to sys.databases).

Jon Seigel
  • 12,251
  • 8
  • 58
  • 92
3

I would make a minor change to Frederik's solution. I would use the sp_spaceused system stored procedure which will also include data and index sizes.


declare c_tables cursor fast_forward for 
select table_name from information_schema.tables 

open c_tables 
declare @tablename varchar(255) 
declare @stmt nvarchar(2000) 
declare @rowcount int 
fetch next from c_tables into @tablename 

while @@fetch_status = 0 
begin 

    select @stmt = 'sp_spaceused ' + @tablename 

    exec sp_executesql @stmt

    fetch next from c_tables into @tablename 

end 

close c_tables 
deallocate c_tables 

Vod
  • 106
  • 2
2

select all rows from the information_schema.tables view, and issue a count(*) statement for each entry that has been returned from that view.

declare c_tables cursor fast_forward for
select table_name from information_schema.tables

open c_tables
declare @tablename varchar(255)
declare @stmt nvarchar(2000)
declare @rowcount int
fetch next from c_tables into @tablename

while @@fetch_status = 0
begin

    select @stmt = 'select @rowcount = count(*) from ' + @tablename

    exec sp_executesql @stmt, N'@rowcount int output', @rowcount=@rowcount OUTPUT

    print N'table: ' + @tablename + ' has ' + convert(nvarchar(1000),@rowcount) + ' rows'

    fetch next from c_tables into @tablename

end

close c_tables
deallocate c_tables
Frederik Gheysels
  • 56,135
  • 11
  • 101
  • 154
1
    SELECT
          SUM(sdmvPTNS.row_count) AS [DBRows]
    FROM
          sys.objects AS sOBJ
          INNER JOIN sys.dm_db_partition_stats AS sdmvPTNS
                ON sOBJ.object_id = sdmvPTNS.object_id
    WHERE 
          sOBJ.type = 'U'
          AND sOBJ.is_ms_shipped = 0
          AND sdmvPTNS.index_id < 2
    GO
Howard Rothenburg
  • 1,220
  • 1
  • 11
  • 7
1

This is my favorite solution for SQL 2008 , which puts the results into a "TEST" temp table that I can use to sort and get the results that I need :

SET NOCOUNT ON 
DBCC UPDATEUSAGE(0) 
DROP TABLE #t;
CREATE TABLE #t 
( 
[name] NVARCHAR(128),
[rows] CHAR(11),
reserved VARCHAR(18), 
data VARCHAR(18), 
index_size VARCHAR(18),
unused VARCHAR(18)
) ;
INSERT #t EXEC sp_msForEachTable 'EXEC sp_spaceused ''?''' 
SELECT * INTO TEST FROM #t;
DROP TABLE #t;
SELECT  name, [rows], reserved, data, index_size, unused FROM TEST \
WHERE ([rows] > 0) AND (name LIKE 'XXX%')
djangofan
  • 28,471
  • 61
  • 196
  • 289