Executing SQL query on multiple databases - sql

I know my post has a very similar title to other ones in this forum, but I really couldn't find the answer I need.
Here is my problem, I have a SQL Server running on my Windows Server. Inside my SQL Server, I have around 30 databases. All of them have the same tables, and the same stored procedures.
Now, here is the problem, I have this huge script that I need to run in all of these databases. I wish I could do it just once against all my databases.
I tried a couple things like go to "view" >> registered servers >> local server groups >> new server registration. But this solution is for many servers, not many databases.
I know I could do it by typing the database name, but the query is really huge, so it would take too long to run in all databases.
Does anybody have any idea if that is possible?

You can use WHILE loop over all database names and inside loop execute query with EXECUTE. I think that statement SET #dbname = ... could be better, but this works too.
DECLARE #rn INT = 1, #dbname varchar(MAX) = '';
WHILE #dbname IS NOT NULL
BEGIN
SET #dbname = (SELECT name FROM (SELECT name, ROW_NUMBER() OVER (ORDER BY name) rn
FROM sys.databases WHERE name NOT IN('master','tempdb')) t WHERE rn = #rn);
IF #dbname <> '' AND #dbname IS NOT NULL
EXECUTE ('use '+QUOTENAME(#dbname)+';
/* Your script code here */
UPDATE some_table SET ... ;
');
SET #rn = #rn + 1;
END;

Consider running the script in SQLCMD Mode from SSMS (Query--SQLCMD Mode). This way, you can save the script to a file and run it in the context of each of the desired databases easily:
USE DB1;
:r C:\SqlScript\YourLargeScript.sql
GO
USE DB2;
:r C:\SqlScript\YourLargeScript.sql
GO
USE DB3;
:r C:\SqlScript\YourLargeScript.sql
GO
This technique can also be used to run the script against databases on other servers with the addition of a :CONNECT command. The connection reverts back to initial server/database after execution of the entire script:
:CONNECT SomeServer
USE DB4;
:r C:\SqlScript\YourLargeScript.sql
GO
:CONNECT SomeOtherServer
USE DB5;
:r C:\SqlScript\YourLargeScript.sql
GO
Important gotcha: Note GO batch separators are needed for :CONNECT to work as expected. I recommend including GO in the the invoking script like the above example but GO as the last line in the :r script file will also provide the desired results. Without GO in this example (or at the end of the script file), the script would run twice on SomeServer and not run against SomeOtherServer at all.

ApexSQL Propagate is the tool which can help in this situation. It is used for executing single or multiple scripts on multiple databases, even multiple servers. What you should do is simply select that script, then select all databases against which you want to execute that script:
When you load scripts and databases you should just click the “Execute” button and wait for the results:

You can write script like this
DECLARE CURSOR_ALLDB_NAMES CURSOR FOR
SELECT name
FROM Sys.Databases
WHERE name NOT IN('master', 'tempdb')
OPEN CURSOR_ALLDB_NAMES
FETCH CURSOR_ALLDB_NAMES INTO #DB_NAME
WHILE ##Fetch_Status = 0
BEGIN
EXEC('UPDATE '+ #DB_NAME + '..SameTableNameAllDb SET Status=1')
FETCH CURSOR_ALLDB_NAMESINTO INTO #DB_NAME
END
CLOSE CURSOR_ALLDB_NAMES

this is the normal way of doing this :
suppose you want to do a select on database DBOther than it would be :
select * from DBOther..TableName
Also check if the table or view is on the dbo schema, if not you should add the schema also : Please notice I use only one dot now after the database name
select * from DBOther.dbo.ViewName
If any of the databases is on another server on another machine, than make sure the Database is in the Linked Server.
Then you can access the table or view on that database via:
SELECT * FROM [AnotherServerName].[DB].[dbo].[Table]
Here is another way that does not requires typing the database name :
use DB1
go
select * from table1
go
use DB2
go
select * from table1
go
Note that this will only work if the tables and fields are exact the same on each database

You can use the following script to run the same script on a set of databases. Just change the filter in the insert line.
declare #dbs table (
dbName varchar(100),
done bit default 0
)
insert #dbs select [name], 0 FROM master.dbo.sysdatabases WHERE [Name] like 'targets_%'
while (exists(select 1 from #dbs where done = 0))
begin
declare #db varchar(100);
select top 1 #db = dbName from #dbs where done = 0;
exec ('
use [' + #db + '];
update table1 set
col1 = '''',
col2 = 1
where id = ''45b6facb-510d-422f-a48c-687449f08821''
');
print #db + ' updated!';
update #dbs set done = 1 where dbName = #db;
end
If your SQL Server version does not support table variables, just use Temp Tables but don`t forget to drop them at the end of the script.

Depending on the requirement, you can do this:
declare #dbName nvarchar(100)
declare #script nvarchar(max)
declare #dbIndex bigint = 0
declare #dbCount bigint = (
select count(*) from
sys.databases
)
declare crs_databases cursor for
(
select
[name]
from
sys.databases
)
open crs_databases
fetch next from crs_databases into #dbName
while ##FETCH_STATUS = 0
begin
set #dbIndex = #dbIndex+1
set #script = concat(#script,
' select Id from ['+#dbName+']..YourTableName ',
case
when #dbIndex = #dbCount then ''
else 'union'
end)
fetch next from crs_databases into #dbName
end
select #script
close crs_databases
deallocate crs_databases
Please note that the double dotted notation assumes that the schema is dbo. Otherwise, you need to explicitly write down the schema.
select Id from ['+#dbName+'].schema.YourTableName
When you need to execute stored procedures on each server, the #script variable will have another content.

Related

How to find, across multiple databases, a specific table (common to most/all) that is not empty

I am working in an environment where many users have the same (or almost identical) test database set up on a common MSSQL server. We are talking about well over 100 databases for testing purposes. And at the very least, 95+% of them will contain the table I am trying to target.
These test databases are only filled with junk data - I will not be impacting anyone by doing any kind of a search. I am looking at one table, specifically, and I need to determine if any test database has that table actually containing any data at all. It doesn’t matter what the data is, I just need to find a table actually containing any data, so I can determine why that data exists in the first place. (This DB is quite old - almost two decades, so sometimes no-one has a clear answer why something in it exists).
I have been trying to build an SQL statement that iterates through all the databases, and checks that particular table specifically to see if it has any content, to bring back a list of databases that have that table containing data.
So to be specific: I need to find all databases where a specific table has any content at all (COUNT(*) > 0). Right now totally stuck with not much of any clues as to how to proceed.
In both methods replace <tablename> with the table name
Using sp_foreachdb
You can use sp_foreachDb
CREATE TABLE ##TBLTEMP(dbname varchar(100), rowscount int)
DECLARE #command varchar(4000)
SELECT #command =
'if exists(select 1 from [?].INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME =''<TABLE NAME>'') insert into ##TBLTEMP(dbname,rowscount) select ''[?]'',count(*) from [?].dbo.<tablename>'
EXEC sp_MSforeachdb #command
SELECT * FROM ##TBLTEMP WHERE rowscount > 0
DROP TABLE ##TBLTEMP
Using CURSOR
CREATE TABLE ##TBLTEMP(dbname varchar(100), rowscount int)
DECLARE #dbname Varchar(100), #strQuery varchar(4000)
DECLARE csr CURSOR FOR SELECT [name] FROM sys.databases
FETCH NEXT FROM csr INTO #dbname
WHILE ##FETCH_STATUS = 0
BEGIN
SET #strQuery = 'if exists(select 1 from [' + #dbname +'].INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME =''<TABLE NAME>'') INSERT INTO ##TBLTEMP(dbname,rowscount) SELECT ''' + #dbname + '' ', COUNT(*) FROM [' + #dbname + '].[dbo].<table name>'
EXEC(#strQuery)
FETCH NEXT FROM csr INTO #dbname
END
CLOSE csr
DEALLOCATE csr
SELECT * FROM ##TBLTEMP where rowscount > 0
References
Sp MSforeachDB
Run same command on all SQL Server databases without cursors
DECLARE CURSOR (Transact-SQL)

MS-SQL: Changing the FileGrowth parameters of a database generically

In our software the user can create databases as well as connect to databases that were not created by our software. The DBMS is Microsoft SQL-Server.
Now I need to update the databases that we use and set the FileGrowth parameter of all the files of all the databases to a certain value.
I know how to get the logical file names of the files of the current database from a query:
SELECT file_id, name as [logical_file_name], physical_name FROM sys.database_files
And I know how to set the desired FileGrowth value, once I know the logical file name:
ALTER DATABASE MyDB MODIFY FILE (Name='<logical file name>', FileGrowth=10%)
But I don't know how to combine these to steps into one script.
Since there are various databases I can't hard code the logical file names into the script.
And for the update process (right now) we only have the possibility to get the connection of a database and execute sql scripts on this connection, so a "pure" script solution would be best, if that's possible.
The following script receives a database name as parameter and uses 2 dynamic SQL: one for a cursor to cycle database files of chosen database and another to apply the proper ALTER TABLE command, since you can't use a variable for the file name on MODIFY FILE.
The EXEC is commented on both occasions and there's a PRINT instead, so you can review before executing. I've just tested it on my sandbox and it's working as expected.
DECLARE #DatabaseName VARCHAR(100) = 'DBName'
DECLARE #DynamicSQLCursor VARCHAR(MAX) = '
USE ' + #DatabaseName + ';
DECLARE #FileName VARCHAR(100)
DECLARE FileCursor CURSOR FOR
SELECT S.name FROM sys.database_files AS S
OPEN FileCursor
FETCH NEXT FROM FileCursor INTO #FileName
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #DynamicSQLAlterDatabase VARCHAR(MAX) = ''
ALTER DATABASE ' + #DatabaseName + ' MODIFY FILE (Name = '''''' + #FileName + '''''', FileGrowth = 10%)''
-- EXEC (#DynamicSQLAlterDatabase)
PRINT (#DynamicSQLAlterDatabase)
FETCH NEXT FROM FileCursor INTO #FileName
END
CLOSE FileCursor
DEALLOCATE FileCursor '
-- EXEC (#DynamicSQLCursor)
PRINT (#DynamicSQLCursor)
You might want to check for the usual dynamic SQL caveats like making sure the values being concatenated won't break the SQL and also add error handling.
As for how to apply this to several databases, you can create an SP and execute it several times, or wrap a database name cursor / while loop over this.

Transfer or Stage a Linked Server Database in SQL Server in SSIS?

A customer has a SQL 2016 Server on an Azure VM.
There's a linked server pointing to a MYSQL Database in the US.
They need to transfer ALL data from the Linked Server (MYSQL) to the local SQL Server in Europe to access the data.
I was suggesting using SSIS, but I can't find how to make a connection work using a Linked Server (looks like the "Transfer Database" task doesn't accept it)
Any idea on how to be done?
As a last resort I will have to write some SQL Script, one table at a time, like :
select * into sql_local.dbo.table 1 from openquery ([MYSQL], 'Select * from remote.Table1')
but of course I would prefer to have it automated.
EDIT: so far I manged to run a script which drops the DB and recreates it, then a cursor that scrolls the "information_schema.tables" and makes a SELECT INTO on every table.
It works quite well and takes about 7 minutes to finish (198 tables , 400MB total)
/**Cursor to scroll ALL tables**/
SET NOCOUNT ON;
DECLARE #table_name varchar(255)
/**Reads the list of tables from information_schema.tables**/
DECLARE tables_cursor CURSOR FOR
select * from openquery ([MYSQL], 'Select TABLE_NAME from information_schema.tables WHERE TABLE_SCHEMA=''MyDatabase'' ');
OPEN tables_cursor
FETCH NEXT FROM tables_cursor
INTO #table_name
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT 'Exporting ' +#table_name+ '...'
declare #sql varchar(4000)
SET #sql='select * into MyDatabase_local.dbo.'+#table_name +' from openquery ([MYSQL], ''Select * from MyDatabase.' + #table_name+''')'
EXEC (#sql)
FETCH NEXT FROM tables_cursor
INTO #table_name
END
CLOSE tables_cursor ;
DEALLOCATE tables_cursor;
PRINT 'Export completed!'

Use SELECT results as a variable in a loop

I've searched here and elsewhere, and haven't found an answer yet. Hope I didn't miss it.
Using SQL Server Management Studio 2008 R2.
I have n specific databases on my server (there are other DBs as well, but I'm only interested in some of them)
Each of these databases has a table within it, which all have the same name. The only difference is the DB name. I want to aggregate these tables together to make one big table on a different database (different to the other DBs).
I can get the db names from the results of a query.
N is unknown.
Is a loop the way to go about this?
I was thinking something along the lines of the following pseudocode:
Set #dbnames = SELECT DISTINCT dbname FROM MyServer.dbo.MyTable
For each #name in #dbnames
INSERT INTO ADifferentDB.dbo.MyOtherTable
SELECT * FROM #name.dbo.table
Next name
(Clearly I'm new to using SQL variable as well, as you can see)
Your first problem is about iterating the databases: you cand do that with a cursor
Then you have another problem, executing a query where part of it is variable (database's name). You can do that with execute function.
All that is something similar to this:
DECLARE #query VARCHAR(max)
DECLARE #dbname VARCHAR(100)
DECLARE my_db_cursor CURSOR
FOR SELECT DISTINCT dbname FROM MyServer.dbo.MyTable
OPEN my_db_cursor
FETCH NEXT FROM my_db_cursor
INTO #dbname
WHILE ##FETCH_STATUS = 0
BEGIN
SET #query = 'INSERT INTO ADifferentDB.dbo.MyOtherTable
SELECT * FROM ' + #dbname + '.dbo.table'
EXECUTE(#query)
FETCH NEXT FROM my_db_cursor
INTO #dbname
END
CLOSE my_db_cursor
DEALLOCATE my_db_cursor
what you want to do is define a CURSOR for row-level operation. here is some doc
I would suggest using sp_MSForEachDB:
EXEC sp_MSForEachDB '
-- Include only the databases you care about.
IF NOT EXISTS (
SELECT *
FROM MySever.dbo.MyTable
WHERE dbname = ''?''
)
-- Exit if the database is not in your table.
RETURN
-- Otherwise, perform your insert.
INSERT INTO ADifferentDB.dbo.MyOtherTable
SELECT * FROM ?.dbo.table
'
In this case, ? is a token that is replaced with each database on the server.

How to execute T-SQL for several databases whose names are stored in a table?

I have several databases (SqlServer 2005) on the same server with the same schema but different data.
I have one extra database which has one table storing the names of the mentioned databases.
So what I need to do is to iterate over those databases name and actually "switch" to each one (use [dbname]) and execute a T-SQL script. Am I clear?
Let me give you an example (simplified from the real one):
CREATE TABLE DatabaseNames
(
Id int,
Name varchar(50)
)
INSERT INTO DatabaseNames SELECT 'DatabaseA'
INSERT INTO DatabaseNames SELECT 'DatabaseB'
INSERT INTO DatabaseNames SELECT 'DatabaseC'
Assume that DatabaseA, DatabaseB and DatabaseC are real existing databases.
So let's say I need to create a new SP on those DBs. I need some script that loops over those databases and executes the T-SQL script I specify (maybe stored on a varchar variable or wherever).
Any ideas?
The simplest way is this:
DECLARE #stmt nvarchar(200)
DECLARE c CURSOR LOCAL FORWARD_ONLY FOR SELECT 'USE [' + Name + ']' FROM DatabaseNames
OPEN c
WHILE 1 <> 0 BEGIN
FETCH c INTO #stmt
IF ##fetch_status <> 0 BREAK
SET #stmt = #stmt + ' ' + #what_you_want_to_do
EXEC(#stmt)
END
CLOSE c
DEALLOCATE c
However, obviously it will not work for statements that need to be the first statement in a batch, like CREATE PROCEDURE. For that you can use SQLCLR. Create and deploy a class like this:
public class StoredProcedures {
[SqlProcedure(Name="exec_in_db")]
public static void ExecInDb(string dbname, string sql) {
using (SqlConnection conn = new SqlConnection("context connection=true")) {
conn.Open();
using (SqlCommand cmd = conn.CreateCommand()) {
cmd.CommandText = "USE [" + dbname + "]";
cmd.ExecuteNonQuery();
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
}
}
}
}
Then you can do
DECLARE #db_name nvarchar(200)
DECLARE c CURSOR LOCAL FORWARD_ONLY FOR SELECT Name FROM DatabaseNames
OPEN c
WHILE 1 <> 0 BEGIN
FETCH c INTO ##db_name
IF ##fetch_status <> 0 BREAK
EXEC exec_in_db #db_name, #what_you_want_to_do
END
CLOSE c
DEALLOCATE c
I guess this will generally not be possible in TSQL, since, as others pointed out,
you first need as USE statement to change the database,
followed by the statement you want to execute, which is, although not specified, a DDL statement which must be first in a batch.
Moreover, you cannot have a GO in a string to be EXECuted.
I found a command-line solution invoking sqlcmd:
for /f "usebackq" %i in
(`sqlcmd -h -1 -Q
"set nocount on select name from master..sysdatabases where status=16"`)
do
sqlcmd -d %i -Q "print db_name()"
Sample code uses current Windows login to query all active databases from Master (replace with your own connection and query for databases), and executes a literal TSQL command on each database thus found. (line breaks for clarity only)
Have a look at the command-line parameters of sqlcmd. You can pass it a TSQL file as well.
If you want to allow manual selection of databases, have a look at SSMS Tools Pack.
You should be able to do this with the sp_MSforeachdb undocumented stored procedure.
This method requires you to put your SQL script to be executed on each DB in a variable, but should work.
DECLARE #SQLcmd varchar(MAX)
SET #SQLcmd ='Your SQL Commands here'
DECLARE #dbName nvarchar(200)
DECLARE c CURSOR LOCAL FORWARD_ONLY FOR SELECT dbName FROM DatabaseNames
OPEN c
WHILE 1 <> 0 BEGIN
FETCH c INTO #dbName
IF ##fetch_status <> 0 BREAK
EXEC('USE [' + #dbName + '] ' + #SQLcmd )
END
CLOSE c
Also, as some have pointed out. This approach is problematic if you want to run a command that needs to be the only thing in a batch.
Here is an alternative for that situation, but it requires more permissions than many DBA's might want you to have and requires you to put your SQL into a separate text file.
DECLARE c CURSOR LOCAL FORWARD_ONLY FOR SELECT dbName FROM DatabaseNames
OPEN c
WHILE 1 <> 0 BEGIN
FETCH c INTO #dbName
IF ##fetch_status <> 0 BREAK
exec master.dbo.xp_cmdshell 'osql -E -S '+ ##SERVERNAME + ' -d ' + #dbName + ' -i c:\test.sql'
END
CLOSE c
DEALLOCATE c
Use the USE command and repeat your commands
Ps. Have a look at how to use USE with a parameter here
I know this question is 5 years old, but I found this via Google, so others may as well.
I recommend sp_msforeachdb system stored procedure. You do not need to create any other stored procedures or cursors.
Given your table of database names is already created:
EXECUTE sp_msforeachdb '
USE ?
IF DB_NAME()
IN( SELECT name DatabaseNames )
BEGIN
SELECT
''?'' as 'Database Name'
, COUNT(*)
FROM
MyTableName
;
END
'
I do this to summarize counts in many databases I have restored from several different sites with the same installed database schema.
Example:
-- Repeat the execution of SQL Commands across all site archived databases.
PRINT 'Database Name'
+ ',' + 'Site Name'
+ ',' + 'Site Code'
+ ',' + '# Users'
+ ',' + '# Seats'
+ ',' + '# Rooms'
... and so on...
+ ',' + '# of days worked'
;
EXECUTE sp_msforeachdb 'USE ?
IF DB_NAME()
IN( SELECT name FROM sys.databases WHERE name LIKE ''Site_Archive_%'' )
BEGIN
DECLARE #SiteName As Varchar(100);
DECLARE #SiteCode As Varchar(8);
DECLARE #NumUsers As Int
DECLARE #NumSeats As Int
DECLARE #NumRooms As Int
... and so on ...
SELECT #SiteName = OfficeBuildingName FROM Office
...
SELECT #NumUsers = COUNT(*) FROM NetworkUsers
...
PRINT ''?''
+ '','' + #SiteName
+ '','' + #SiteCode
+ '','' + str(#NumUsers)
...
+ '','' + str(#NumDaysWorked) ;
END
'
The trickiest part are the single quotes '