42

I have (for testing purposes) many dbs with the same schema (=same tables and columns basically) on a sql server 2008 r2 instance.

i would like a query like

SELECT COUNT(*) FROM CUSTOMERS

on all DBs on the instance. I would like to have as result 2 columns:

1 - the DB Name

2 - the value of COUNT(*)

Example:

DBName  //   COUNT (*)

TestDB1 // 4

MyDB  // 5

etc...

Note: i assume that CUSTOMERS table exists in all dbs (except master).

UnDiUdin
  • 14,924
  • 39
  • 151
  • 249
  • Get `sp_ineachdb` (articles on it [here](https://www.mssqltips.com/sqlservertip/5686/execute-a-command-in-the-context-of-each-database-in-sql-server-using-spineachdb/) and [here](https://www.mssqltips.com/sqlservertip/5694/execute-a-command-in-the-context-of-each-database-in-sql-server-part-2/); download [here](https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/blob/dev/sp_ineachdb.sql)). – Aaron Bertrand Sep 27 '21 at 18:11

5 Answers5

50

Try this one -

    SET NOCOUNT ON;
    
    IF OBJECT_ID (N'tempdb.dbo.#temp') IS NOT NULL
       DROP TABLE #temp
    
    CREATE TABLE #temp
    (
          [COUNT] INT
        , DB VARCHAR(50)
    )
    
    DECLARE @TableName NVARCHAR(50) 
    SELECT @TableName = '[dbo].[CUSTOMERS]'
    
    DECLARE @SQL NVARCHAR(MAX)
    SELECT @SQL = STUFF((
        SELECT CHAR(13) + 'SELECT ' + QUOTENAME(name, '''') + ', COUNT(1) FROM ' + QUOTENAME(name) + '.' + QUOTENAME(@TableName)
        FROM sys.databases 
        WHERE OBJECT_ID(QUOTENAME(name) + '.' + QUOTENAME(@TableName)) IS NOT NULL
        FOR XML PATH(''), TYPE).value('text()[1]', 'NVARCHAR(MAX)'), 1, 1, '')
    
    INSERT INTO #temp (DB, [COUNT])              
    EXEC sys.sp_executesql @SQL
    
    SELECT * 
    FROM #temp t

Output (for example, in AdventureWorks) -

COUNT       DB
----------- --------------------------------------------------
19972       AdventureWorks2008R2
19975       AdventureWorks2012
19472       AdventureWorks2008R2_Live
Charlieface
  • 52,284
  • 6
  • 19
  • 43
Devart
  • 119,203
  • 23
  • 166
  • 186
  • Hi Devart, i am using this sql and works great. But is it possible to add a check if the user running this query has access to each database or not and if not, the query should still continue to run and return only the databases the user has access to. What I am after is whether the user has DBO role permission on each of the database and if not move onto next one.. please can you advise. – DK2014 Sep 27 '16 at 10:39
  • @Charliface, this has stopped working for me with the latest revision. – Robin Salih Oct 13 '21 at 13:13
  • The best method which I've ever seen. – Elvin Ahmadov Nov 23 '22 at 17:23
40

Straight forward query

EXECUTE sp_MSForEachDB 
        'USE ?; SELECT DB_NAME()AS DBName, 
        COUNT(1)AS [Count] FROM CUSTOMERS'

This query will show you what you want to see, but will also throw errors for each DB without a table called "CUSTOMERS". You will need to work out a logic to handle that.

Raj

Raj
  • 10,653
  • 2
  • 45
  • 52
  • 1
    +1 it is a very compact solution but i choose the other since your solution crates many result tables, while the other not – UnDiUdin Aug 27 '13 at 14:12
  • 10
    It probably worth mentioning that the sp_MSForEachDB is undocumented, unsupported, and has some known issues as discussed by Aaron Bertrand here https://sqlblog.org/2020/05/12/bad-habits-to-kick-relying-on-undocumented-behavior – Karl Kieninger May 13 '14 at 12:15
8

How about something like this:

DECLARE c_db_names CURSOR FOR
SELECT name 
FROM sys.databases
WHERE name NOT IN('master', 'tempdb') --might need to exclude more dbs

OPEN c_db_names

FETCH c_db_names INTO @db_name

WHILE @@Fetch_Status = 0
BEGIN
  EXEC('
    INSERT INTO #report
    SELECT 
      ''' + @db_name + '''
      ,COUNT(*)
    FROM ' + @db_name + '..linkfile
  ')
  FETCH c_db_names INTO @db_name
END

CLOSE c_db_names
DEALLOCATE c_db_names

SELECT * FROM #report
Dave Sexton
  • 10,768
  • 3
  • 42
  • 56
  • This is a useful general snippet, I just replaced `sys.databases` with `sys.tables` for a very different purpose, using the same boilerplate. – Tomasz Gandor May 19 '15 at 11:14
  • 4
    You have forgotten in the script to declare [at]db_name, you need to add one line: DECLARE @db_name NVARCHAR (150), otherwise very useful script :-) – inkubpl Oct 31 '16 at 08:18
  • Where is the temp table delcared? – Phil3992 Mar 31 '17 at 10:06
  • @Phil3992 you're right it should be added, but it would just be a two column table with a name as VARCHAR and a count as INT. – Dave Sexton Mar 31 '17 at 13:31
0
declare @userdb_list table (name varchar(4000) not null);
-- fill the db list with custom subset
insert into @userdb_list
select name from sys.databases --can add where condition to filter db names

declare
@curr_userdb varchar(300),
@db_placeholder varchar(300),
@final_db_exec_query varchar(max),
@query varchar(max);
set @query = '' --  <add ur query here> 

set @db_placeholder = 'use {db}'; 
set @curr_userdb = (select min(name) from @userdb_list);

while @curr_userdb is not null
begin
set @final_db_exec_query = replace(@db_placeholder, '{db}', @curr_userdb +  '  ' + @query);
exec (@final_db_exec_query);
--print @final_db_exec_query
set @curr_userdb = (select min(name) from @userdb_list where name > @curr_userdb);
end
GO

Solution without cursor - clean and simple

aquesh
  • 9
  • 1
  • 1
  • 8
-1

Because I know that a question was just referred to here that asked a slightly different question... if you only want to execute on certain databases, those databases could be stored in some table. Here I stored in a temporary table.

CREATE TABLE #Databases (
DbName varchar(255))

INSERT INTO #Databases (DbName)
Values ('GIS_NewJersey'), ('GIS_Pennsylvania')

DECLARE @command varchar(1000)

SELECT @command = 'Use [' + DbName + '];
Update sde.SAP_Load
    SET FullAddress =  CONCAT_WS('','', HouseNumber, Street, City, Postal, RegionName)   
    Update sde.PREMISE
    SET FullAddress =  CONCAT_WS('', '', HouseNumber, Street, City, Postal, RegionName)
    Update sde.PREMISE_GEOCODE
    SET FullAddress =  CONCAT_WS('', '', HouseNumber, Street, City, Postal, RegionName)'
FROM #Databases

EXEC @command
CMGeek
  • 136
  • 5