Union of multiple sp_MSforeachdb result sets - sql

I can successfully query the same table in multiple databases as follows:
DECLARE #command varchar(1000)
SELECT #command = 'select * from table'
EXEC sp_MSforeachdb #command
However, all of these results are, as expected, returned in different result windows. What's the easiest way to perform a union of all of these results?

Please stop using sp_MSforeachdb. For anything. Seriously. It's undocumented, unsupported, and spectacularly broken:
Making a more reliable and flexible sp_MSforeachdb
Execute a Command in the Context of Each Database in SQL Server
If you know that all databases have the same table (and that they all have the same structure!), you can do this:
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql = #sql + N'UNION ALL SELECT col1,col2 /*, etc. */
FROM ' + QUOTENAME(name) + '.dbo.tablename'
FROM sys.databases WHERE database_id > 4 AND state = 0;
SET #sql = STUFF(#sql, 1, 10, '');
EXEC sp_executesql #sql;
This ignores system databases and doesn't attempt to access any databases that are currently not ONLINE.
Now, you may want to filter this further, e.g. not include any databases that don't have a table called tablename. You'll need to nest dynamic SQL in this case, e.g.:
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'DECLARE #cmd NVARCHAR(MAX);
SET #cmd = N'''';';
SELECT #sql = #sql + N'
SELECT #cmd = #cmd + N''UNION ALL
SELECT col1,col2 /*, etc. */ FROM '
+ QUOTENAME(name) + '.dbo.tablename ''
WHERE EXISTS (SELECT 1 FROM ' + QUOTENAME(name)
+ '.sys.tables AS t
INNER JOIN ' + QUOTENAME(name) + '.sys.schemas AS s
ON t.[schema_id] = s.[schema_id]
WHERE t.name = N''tablename''
AND s.name = N''dbo'');'
FROM sys.databases WHERE database_id > 4 AND state = 0;
SET #sql = #sql + N';
SET #cmd = STUFF(#cmd, 1, 10, '''');
PRINT #cmd;
--EXEC sp_executesql #cmd;';
PRINT #sql;
EXEC sp_executesql #sql;
This doesn't validate the column structure is compatible, but you'll find that out pretty quickly.

Another way to skin this cat is to use dynamic SQL:
DECLARE #sql varchar(max);
SELECT #sql = Coalesce(#sql + ' UNION ALL ', '') + 'SELECT list, of, columns FROM ' + QuoteName(name) + '.schema.table'
FROM sys.databases
;
PRINT #sql
--EXEC (#sql);

Had some collation issues and had to use
AND COLLATION_NAME = 'SQL_Latin1_General_CP1_CI_AS'

Related

query sql server in multiple database

i would like to execute this query 'select count(*) from Aircraft' on multiple database. We have 50 database and all of those have the same table. i'm using sql server 2019.
I know there is a possibilty to loop this query so that's why i'm asking you.
I found some old reply but not recently.
I used this query but it didn't work
SELECT #Query = COALESCE(#Query + ' UNION ALL ', '') + 'select * from [' + TABLE_CATALOG+'].dbo.[Aircraft]'
FROM information_schema.tables
SET #Query = STUFF(#Query, CHARINDEX('UNION ALL', #Query), 10, '')
PRINT #Query
EXEC(#Query)
If all target databases are located in one instance, this can be done using the string_agg function in the following way:
Declare #schema_name sysname = N'dbo'
Declare #table_name sysname = N'Aircraft'
Declare #max nVarChar(max) = ''
Declare #QueryText nVarChar(max)
Select #QueryText = String_Agg(Concat(#max, N'Select * From ',
QuoteName([name]), N'.',
QuoteName(#schema_name),N'.',
QuoteName(#table_name), Char(10)),
Concat(N'UNION ALL',Char(10)))
From master.dbo.sysdatabases
Where OBJECT_ID(Concat(QuoteName([name]),'.',
QuoteName(#schema_name),'.',
QuoteName(#table_name))) Is Not Null
Print #QueryText
Exec sp_executesql #QueryText
You can achieve this by taking advantage of the undocumented stored procedure in SQL Server i.e. sp_MSforeachdb
DECLARE #query NVARCHAR(1000)
SET #query =
'USE ?
IF DB_NAME() NOT IN (''master'', ''tempdb'', ''model'', ''msdb'')
SELECT COUNT(*) AS Count FROM ?.dbo.fin_MemberAccount'
EXEC sp_MSforeachdb #command1 = #query
-- ? : this means the current db while iterating through db by stored procedure

OPENQUERY query stored in string fails with EXEC

I'm trying to pull back some data via a linked server that has 'Geography' fields present, consequntly I'm trying to use Open Query.
I'm also trying to pass in a variable...
Can anyone explain this:
This is my sql...
DECLARE #Sql VARCHAR(200)
DECLARE #tnum VARCHAR(20)= 'abc';
SET #Sql = 'SELECT * FROM NationalPolygon.dbo.Polygon WHERE TitleNumber = ''''' + #tnum + '''''';
SET #Sql = 'SELECT * FROM OPENQUERY(mylinkedserver01, ''' + REPLACE(#Sql, '?', '''') + ''')'
SELECT #Sql;
EXEC #Sql;
If I select #Sql and run it, it works.
If I run the #Sql via EXEC it fails with:
Database 'SELECT * FROM OPENQUERY(mylinkedserver01, 'SELECT * FROM NationalPolygon' does not exist. Make sure that the name is entered correctly.
Thanks
C
You need to surround #Sql with brackets
EXEC (#Sql);

SQL Server: Select from #variable_table_name (problem with nested variables)

I need to select form a variable table name, which seems to not work in SQL Server (see example nr 2).
I tried making an intermediate step that creates a string with my variable table name as static text (see example nr3), but that failed too, probably because I have nested variables.
Can anyone help me fix it?
------------Example Nr 1--------------
-- Working query without variable Table
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = 'SELECT ';
SELECT #Sql = #Sql +'['+[Modell Pset (S1 Slab)]+']'+ ' AS ''' +''+[C3D Pset (S1 Slab)]+''', ' FROM [MTO_Psets_S1_Slab]
PRINT #sql
--Result of "print #sql": SELECT [c3d:abschnitt] AS 'Abschnitt', [ALL] AS 'Eigenschaft_1', [ALL] AS 'Eigenschaft_2', [Geschoss] AS 'Geschoss', [ALL] AS 'Ist_Aussen', [ALL] AS 'Material_1', [ALL] AS 'Material_2', [ALL] AS 'Objekt', [c3d:teilprojekt] AS 'Teilprojekt',
SET #Sql = (SELECT LEFT(#Sql, LEN(#Sql)-1));
SET #Sql = #Sql + ' FROM [modell_properties_slab]';
EXEC sp_executesql #Sql;
------------Example Nr 2--------------
-- When I replace table with a variable, the query doesn't work anymore
DECLARE #Sql NVARCHAR(MAX);
DECLARE #Table NVARCHAR(MAX);
SET #table = '[MTO_Psets_S1_Slab]'
SET #Sql = 'SELECT ';
SELECT #Sql = #Sql +'['+[Modell Pset (S1 Slab)]+']'+ ' AS ''' +''+[C3D Pset (S1 Slab)]+''', ' FROM #table
SET #Sql = (SELECT LEFT(#Sql, LEN(#Sql)-1));
SET #Sql = #Sql + ' FROM [modell_properties_slab]';
EXEC sp_executesql #Sql
-- Error message:
-- Msg 1087, Level 16, State 1, Line 24
-- Must declare the table variable "#Table".
------------Example Nr 3--------------
-- When I rewrite the query to have the table inside the apostrophes,
-- it doesn't work (compare the result of "print #sql" with the example nr1)
declare #slab nvarchar(MAX) ='slab'
DECLARE #intermediate_step nvarchar(MAX);
set #intermediate_step = '''[''[Modell Pset (S1 Slab)]+'']''+ '' AS '''''' +''''+[C3D Pset (S1 Slab)]+'''''', '' FROM [MTO_Psets_S1_'+#Slab+']'
print #intermediate_step
--Result of "print #intermediate_step": '['[Modell Pset (S1 Slab)]+']'+ ' AS ''' +''+[C3D Pset (S1 Slab)]+''', ' FROM [MTO_Psets_S1_slab]
DECLARE #Sql nvarchar(MAX);
SET #Sql = 'SELECT ';
SELECT #Sql = #Sql + #intermediate_step
print #sql
--Result of "print #sql": SELECT '['[Modell Pset (S1 Slab)]+']'+ ' AS ''' +''+[C3D Pset (S1 Slab)]+''', ' FROM [MTO_Psets_S1_slab]
SET #Sql = (SELECT LEFT(#Sql, LEN(#Sql)-1));
SET #Sql = #Sql + ' FROM [modell_properties_slab]';
EXEC sp_executesql #Sql;
I'll attach the tables i used in the query and the result table from the working query "example nr 1" as JPG: MTO_Psets_S1_Slab, modell_properties_slab, Result query
MTO_Psets_S1_Slab
modell_properties_slab
Result query
I rewrote the query like this and it works:
exec('
declare #t as table ([C3D Pset (S1 '+#slab+')] nvarchar (100), [Modell Pset (S1 '+#slab+')] nvarchar (100));
DECLARE #Sql nvarchar(MAX);
insert #t ([C3D Pset (S1 '+#slab+')] , [Modell Pset (S1 '+#slab+')] )
select * from [MTO_Psets_S1_'+#slab+']
SET #Sql = ''SELECT '';
SELECT #Sql = #Sql +''[''+[Modell Pset (S1 Slab)]+'']''+ '' AS '''''' +''''+[C3D Pset (S1 Slab)]+'''''', '' FROM #t
SET #Sql = (SELECT LEFT(#Sql, LEN(#Sql)-1));
SET #Sql = #Sql + '' FROM [modell_properties_'+#slab+']'';
EXEC sp_executesql #Sql
')

Query Across different database - Same table SQL Server 2012

I Have a common table "User Activity" in about 200 different databases on different servers, is it possible to select rows from all databases for that table in one statement?
I have a list of those databases and servers they are on ina main database/table, can obtain by
Select servername, dbname from DBases from CustomersList
Yes but you need to explicitly mention them all:
SELECT COl1,Col2,Col3 FROM Database1.schema.Table1
UNION ALL
SELECT COl1,Col2,Col3 FROM Database2.schema.Table1
UNION ALL
SELECT COl1,Col2,Col3 FROM Database3.schema.Table1
UNION ALL
.......
...
SELECT COl1,Col2,Col3 FROM Database200.schema.Table1
This is the kind of thing I would just build in Excel and paste into SSMS.
Then you might want to reconsider whether it's a good design to have this in 200 databases.
I am getting this error:
Incorrect syntax near 'ALL'
While I try to run your code.
I have address table in two databases so I modified your code as:
DECLARE #tableName nvarchar(256) = 'dbo.Address'
DECLARE #sql nvarchar(max) = ''
SELECT #sql = #sql + 'SELECT * FROM [' + dbs.name + ']..[' + #tableName + '] '
+ CASE WHEN #sql <> '' THEN 'UNION ALL ' ELSE '' END
FROM sys.sysdatabases dbs where dbs.dbid in (7,8)
and dbs.name NOT IN ('master', 'tempdb', 'msdb', 'model','SSISall')
EXEC(#sql)
I suggest you to use this syntax:
DECLARE #tableName nvarchar(256) = 'Table1'
DECLARE #sql nvarchar(max) = ''
SELECT #sql = #sql + CASE WHEN #sql <> '' THEN 'UNION ALL ' ELSE '' END
+ 'SELECT * FROM [' + dbs.name + ']..[' + #tableName + '] '
FROM sys.sysdatabases dbs
WHERE dbs.name NOT IN ('master', 'tempdb', 'msdb', 'model')
EXEC(#sql)
You can easily optimize it to use in a stored procedure.

sp_MSforeachdb: only include results from databases with results

I'm running the below stored procedure sp_MSforeachdb with a simple command. My question is how to limit the result to show only the databases that have at least 1 record satisfying the command:
Here's my stored procedure:
EXECUTE master.sys.sp_MSforeachdb 'USE [?];
IF (EXISTS (SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = ''Tabs''))
BEGIN
SELECT ''?'' as dbname,T.TabName, T.TabPath
FROM Tabs T
WHERE T.TabID IN (
SELECT Distinct TM.TabID
FROM TabModules TM
WHERE mID IN (
...
)
)
ORDER BY T.TabName
END
'
Any ideas how I can modify the sp so that it doesn't display the databases that have empty results (see image)?
Well, first, stop using sp_MSforEachDb. Oh, the problems (if you want proof, see here).
How about:
DECLARE #cmd NVARCHAR(MAX) = N'', #sql NVARCHAR(MAX) = N'';
SELECT #cmd += N'IF EXISTS (SELECT 1 FROM '
+ QUOTENAME(name) + '.sys.tables WHERE name = N''Tabs'')
SET #sql += N''UNION ALL
SELECT ''''' + name + ''''',T.TabName
FROM ' + QUOTENAME(name) + '.dbo.Tabs AS T
WHERE EXISTS
(
SELECT 1 FROM ' + QUOTENAME(name) + '.dbo.TabModules AS TM
WHERE TM.TabID = T.TabID
AND TM.mID IN -- this should probably be exists too
(
...
)
)
'''
FROM sys.databases
WHERE state = 0 -- assume you only want online databases
AND database_id > 4; -- assume you don't want system dbs
EXEC sp_executesql #cmd, N'#sql NVARCHAR(MAX) OUTPUT', #sql OUTPUT;
SET #sql = STUFF(#sql, 1, 10, '') + N' ORDER BY TabName;';
PRINT #sql; -- this will appear truncated, but trust me, it is not truncated
-- EXEC sp_executesql #sql;
If you really want some unknown, arbitrary number of separate resultsets, the change is simple.
DECLARE #cmd NVARCHAR(MAX) = N'', #sql NVARCHAR(MAX) = N'';
SELECT #cmd += N'IF EXISTS (SELECT 1 FROM '
+ QUOTENAME(name) + '.sys.tables WHERE name = N''Tabs'')
SET #sql += N''SELECT ''''' + name + ''''',T.TabName
FROM ' + QUOTENAME(name) + '.dbo.Tabs AS T
WHERE EXISTS
(
SELECT 1 FROM ' + QUOTENAME(name) + '.dbo.TabModules AS TM
WHERE TM.TabID = T.TabID
AND TM.mID IN -- this should probably be exists too
(
...
)
)
ORDER BY T.TabName;
'';'
FROM sys.databases
WHERE state = 0 -- assume you only want online databases
--AND database_id > 4; -- assume you don't want system dbs
EXEC sp_executesql #cmd, N'#sql NVARCHAR(MAX) OUTPUT', #sql OUTPUT;
PRINT #sql; -- this will appear truncated, but trust me, it is not truncated
-- EXEC sp_executesql #sql;
You basically need another IF to only run the select if data exists.
Here's what i did to test.
On DB1:
create table mytesttable(a int)
insert mytesttable values(1)
On DB2:
create table mytesttable(a int)
So you want DB1 to return results, but DB2 not to. you can use the following sql:
EXECUTE master.sys.sp_MSforeachdb 'USE [?];
IF (EXISTS (SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = ''mytesttable''))
BEGIN
IF EXISTS (SELECT 1 FROM mytesttable) BEGIN
SELECT ''?'' as dbname,T.A
FROM mytesttable AS T
END
END
'
This only returns:
db1, 1