Query Across different database - Same table SQL Server 2012 - sql

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.

Related

SQL query on certain database names

I currently have many databases on a server. I want to run a query on only databases that end in "AccountsLive".
Not all of them end with this, so I kind of want to do a wildcard %AccountsLive query and not using a WHERE NOT name IN('master', 'tempdb', 'model', 'msdb')
Is this possible?
Below is the code I currently have:
DECLARE #Sql NVARCHAR(MAX) = NULL;
SELECT #Sql = COALESCE(#Sql + ' UNION ALL ' + CHAR(13) + CHAR(10), '' ) + 'SELECT * FROM ' + QUOTENAME([name]) + '.SL_TRANSACTIONS'
FROM sys.databases
WHERE not [name] in ('master', 'tempdb', 'model', 'msdb');
EXECUTE ( #Sql );
You can put list of table names in a table variable and query them accordingly. I have also added schema as dbo, to make the name as three part name, assuming the table in the dbo schema.
DECLARE #table table(dbname sysname)
INSERT INTO #table(dbname)
SELECT NAME FROm sys.databases where name like '%AccountsLive'
DECLARE #Sql NVARCHAR(MAX) = NULL;
SELECT #Sql = COALESCE(#Sql + ' UNION ALL ' + CHAR(13) + CHAR(10), '' ) + 'SELECT * FROM ' + QUOTENAME(dbname) + '.dbo.SL_TRANSACTIONS'
FROM #table
EXEC( #Sql );

Select all databases that have a certain table and a certain column

I need to create a query that is executed on all databases of my SQL server instance. An additional constraint is, that the query should only be executed on databases that contain a special table with a special column. Background is that in some databases the special table does (not) have the special column.
Based on this solution, what I have until now is a query that executes only on databases that contain a certain table.
SELECT *
FROM sys.databases
WHERE DATABASEPROPERTY(name, 'IsSingleUser') = 0
AND HAS_DBACCESS(name) = 1
AND state_desc = 'ONLINE'
AND CASE WHEN state_desc = 'ONLINE'
THEN OBJECT_ID(QUOTENAME(name) + '.[dbo].[CERTAIN_TABLE]', 'U')
END IS NOT NULL
However, what is still missing is a constraint that the query should only select databases where the table CERTAIN_TABLE has a specific column. How can this be achieved?
When i want to loop through all databases, i do a loop like the following. Its easy to follow:
DECLARE #dbs TABLE ( dbName NVARCHAR(100) )
DECLARE #results TABLE ( resultName NVARCHAR(100) )
INSERT INTO #dbs
SELECT name FROM sys.databases
DECLARE #current NVARCHAR(100)
WHILE (SELECT COUNT(*) FROM #dbs) > 0
BEGIN
SET #current = (SELECT TOP 1 dbName FROM #dbs)
INSERT INTO #results
EXEC
(
'IF EXISTS(SELECT 1 FROM "' + #current + '".INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = ''Target_Table_Name'' AND COLUMN_NAME = ''Target_Column_Name'')
BEGIN
--table and column exists, execute query here
SELECT ''' + #current + '''
END'
)
DELETE FROM #dbs
WHERE dbName = #current
END
SELECT * FROM #results
You are going to need either some looping or dynamic sql for this. I really dislike loops so here is how you could do this with dynamic sql.
declare #TableName sysname = 'CERTAIN_TABLE'
, #ColumnName sysname = 'CERTAIN_COLUMN'
declare #SQL nvarchar(max) = ''
select #SQL = #SQL + 'select DatabaseName = ''' + db.name + ''' from ' + QUOTENAME(db.name) + '.sys.tables t join ' + QUOTENAME(db.name) + '.sys.columns c on c.object_id = t.object_id where t.name = ''' + QUOTENAME(#TableName) + ''' and c.name = ''' + QUOTENAME(#ColumnName) + '''' + char(10) + 'UNION ALL '
from sys.databases db
where db.state_desc = 'ONLINE'
order by db.name
select #SQL = substring(#SQL, 0, len(#SQL) - 9)
select #SQL
--uncomment the line below when you are comfortable the query generated is correct
--exec sp_executesql #SQL

SQL - Search for table name across all databases on server

I thought this would be pretty straightforward, but I have about 80 databases in the server I am looking at, each database has 5-500 tables.
I am wondering how i can search for a TABLE NAME across everything. I tried a basic
SELECT
*
FROM sys.tables
but I only get 6 results.
This is a bit of a hack, but I think it should work:
sp_msforeachdb 'select ''?'' from ?.information_schema.tables where table_name=''YourTableName''';
It will output the names of the DBs that contain a table with the given name.
Here's a version using print that is a little better IMHO:
sp_msforeachdb '
if exists(select * from ?.information_schema.tables where table_name=''YourTableName'')
print ''?'' ';
The above queries are using ms_foreachdb, a stored procedure that runs a given query on all databases present on the current server.
This version uses FOR XML PATH('') instead of string concatenation, eliminates the default system databases, handles databases with non-standard names and supports a search pattern.
DECLARE #pattern NVARCHAR(128) = '%yourpattern%';
DECLARE #sql NVARCHAR(max) = STUFF((
SELECT 'union all select DatabaseName = name from ' + QUOTENAME(d.name) + '.sys.tables where name like ''' + #pattern + ''' '
FROM sys.databases d
WHERE d.database_id > 4
FOR XML path('')
), 1, 10, '');
EXEC sp_executesql #sql;
You might need to write:
select DatabaseName = name collate Latin1_General_CI_AS
I know I did.
Just because I really dislike loops I wanted to post an alternative to answers already posted that are using cursors.
This leverages dynamic sql and the sys.databases table.
declare #SQL nvarchar(max) = ''
select #SQL = #SQL + 'select DatabaseName = name from [' + name + '].sys.tables where name = ''YourTableName'' union all '
from sys.databases
set #SQL = stuff(#SQL, len(#SQL) - 9, 11, '') --removes the last UNION ALL
exec sp_executesql #SQL
Here's a bit of a simpler option using dynamic sql. This will get you the name of all tables in every database in your environment:
declare #table table (idx int identity, name varchar(max))
insert #table
select name from master.sys.databases
declare #dbname varchar(max)
declare #iterator int=1
while #iterator<=(select max(idx) from #table) begin
select #dbname=name from #table where idx=#iterator
exec('use ['+#dbname+'] select name from sys.tables')
set #iterator=#iterator+1
end
select * from #table
Dim sql As String = ("Select * from " & ComboboxDatabaseName.Text & ".sys.tables")
use this key

Resultset from Facts across different schemas

We have a DB which has several schemas and each schema has a Fact table. I need to prepare a result set with schema name, max(MTH_DT) from Fact and distinct MTH_DT counts from each Fact table.
SCHEMA_NAME MAX(MTH_DT) DISTINCT_COUNT(MTH_DT)
SCHM_1 11/30/2015 24
SCHM_2 10/31/2015 24
SCHM_3 11/30/2015 36
SCHM_4 10/31/2015 24
SCHM_5 11/30/2015 24
How can I get the resultset in this fashion?
Here it goes
Just uncomment the EXECUTE statement when you are sure of the query and run the query:
DECLARE #SQL VARCHAR(MAX) = ''
SELECT #SQL += 'SELECT '''+T.TABLE_NAME+''' AS [SCHEMA_NAME],MAX(MTH_DT) AS [MAX(MTH_DT)] ,COUNT(MTH_DT) AS [DISTINCT_COUNT(MTH_DT)] FROM '+T.TABLE_SCHEMA+'.'+T.TABLE_NAME + ' UNION ' FROM INFORMATION_SCHEMA.TABLES AS T WHERE T.TABLE_NAME like 'SCHM_%'
SELECT #SQL = SUBSTRING(#SQL,1,LEN(#SQL) - LEN('UNION'))
PRINT (#SQL)
--EXECUTE (#SQL)
Here is a safer way to build your dynamic sql than using INFORMATION_SCHEMA.TABLES. I left off the group by because it is a constant so you don't actually need a group by.
declare #SQL nvarchar(MAX) = ''
select #SQL = #SQL + 'select ''' + name + ''' as SchemaName, MAX(MTH_DT) as MaxMTH_DT, COUNT(distinct MTH_DT) as DISTINCT_COUNT_MTH_DT from ' + name + '.Fact union all '
from sys.schemas
where SCHEMA_ID > 4
and SCHEMA_ID < 16384
Select #SQL = LEFT(#SQL, LEN(#SQL) - 9) + ' order by SchemaName'
select #SQL

Union of multiple sp_MSforeachdb result sets

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'