5

I have a SQL script I need to run on about 20 different databases.

I basically just need to be able to run some SQL, then have it load and run a file from the disk, do more SQL, run that same script again, etc.

I was hoping to make a SQL script that would basically look something like this:

use database1
go
exec c:\release.sql
go

use database2
go
exec c:\release.sql
go

use database3
go
exec c:\release.sql
go

--etc....

I've looked online a bunch and found a way to do something similar in a batch file with sqlcmd but it isn't working and I don't see how to switch databases that way, either.

Thanks a ton!

Ben

Funkymonk
  • 61
  • 1
  • 1
  • 5
  • 3
    isn't working? Is it because your screen isn't turned on? – Nick.Mc Feb 04 '15 at 21:12
  • Possible duplicate of [TransactSQL to run another TransactSQL script](http://stackoverflow.com/questions/5237198/transactsql-to-run-another-transactsql-script) – Bacon Bits Feb 04 '15 at 22:03

4 Answers4

6

You can switch management studio to sqlcmd mode (query menu) and then run a script with :r script.sql

To do this on a dynamically generated list of databases you have to do some sqlcmd trickery:

  • set output to file
  • generate the command to execute
  • set output to stdout
  • execute the file
  • delete the temp file

I assume in this example that the file script.sql exists in c:\temp. Note that the GO statements are important in the script or the sqlcmd parser will get confused.

:OUT $(TEMP)\db.sql
declare @script nvarchar(max)
select @script = isnull(@script, '') 
               + 'use ' + name + char(13) + char(10) 
               + ':r c:\temp\script.sql' + char(13) + char(10) 
  from sys.databases
 where name like N'%[_]db'
print @script

GO

:OUT stdout
:r $(TEMP)\db.sql

GO

!!del $(TEMP)\db.sql /s /q
Filip De Vos
  • 11,568
  • 1
  • 48
  • 60
  • I ended up combining 2 things. I made a SQL script that creates a cursor to find the databases and then prints a list of commands for a CMD prompt. I then run that in the command prompt. – Funkymonk Feb 05 '15 at 19:19
  • This method is unfortunately invalid given the updated requirement that the database names be derived from a query. . – Solomon Rutzky Feb 06 '15 at 05:56
  • I updated the answer so it matches the new question. – Filip De Vos Feb 06 '15 at 09:08
2

You don't need to do this in SSMS. You just need to create a CMD script.

IF you have a static set of databases to run on, then use the following:

@ECHO OFF

SET MyServer="(local)"
SET MyScript="c:\release.sql"

SQLCMD -S %MyServer% -E -i %MyScript% -d database1
SQLCMD -S %MyServer% -E -i %MyScript% -d database2
...
SQLCMD -S %MyServer% -E -i %MyScript% -d database20

IF you have a dynamic set of databases that can be queried for, then use the following:

@ECHO OFF

SET MyServer="(local)"
SET MyScript="c:\release.sql"
SET MyQuery="SET NOCOUNT ON; SELECT [Name] FROM [sys].[databases] sd WHERE sd.[name] LIKE N'%%[_]db' ORDER BY sd.[name];"

FOR /F %%B IN ('SQLCMD -h -1 -S %MyServer% -E -Q %MyQuery%') DO (
    REM remove the "echo" from the next line to run the scripts
    echo SQLCMD -S %MyServer% -E -i %MyScript% -d %%B -o results-%%B.txt
)

Using the %%B in the output filename will give you a different output file per database, as in:

results-database1_db.txt
results-database2_db.txt
...


Other notes:

  • Use (local) instead of localhost when connecting to the local, default instance as it uses shared memory while localhost forces a TCP connection.

  • If you are searching for an underscore in a LIKE statement, enclose it in square brackets else it is a single-character wild card (which still technically works sometimes, but could also match other characters): [_]

Solomon Rutzky
  • 46,688
  • 9
  • 128
  • 171
  • @user1588449 Well, that wasn't the question you asked. Dynamically discovering the databases to run on is very different from having a static list. This needs to be mentioned in the question as it entirely invalidates Filip De Vos's answer as this is not possible in SQLCMD mode. I have updated my answer showing how to do this with a dynamic set of databases. – Solomon Rutzky Feb 05 '15 at 16:45
1

Thanks everyone who pitched in! The following seems like it might work (based on @srutzky's answer)

sqlcmd -S "localhost" -E -i "c:\release.sql" -d database1 -o results.txt

The thing I am missing by using a cmd prompt instead of SSMS is that I don't think I can write cursor to loop through each database that ends with "_db" and then execute against that... Here's the SQL I have but I just need to be able to put the link to the SQL file to execute.

link

If I put the release script SQL into this file into the @text variable it doesn't work because it blows up on each GO statement I have in my release.sql file.

declare @text as nvarchar(max)
set @text = N'
-- GET AND RUN SCRIPT FROM DISK!
'

declare C_CURSOR CURSOR FOR 
    select  [Name] from sys.databases 
    where   name like '%_db' 
    order by name

declare @runtext as nvarchar(max)
declare @DB_Name as nvarchar(200)

OPEN C_CURSOR
fetch next from C_CURSOR INTO @DB_Name
WHILE(@@FETCH_STATUS = 0)
BEGIN

    print @DB_Name
    set @runtext = 'select ''' + @DB_Name + ''' as DatabaseName
        use ' + @DB_Name + N'
    ' + @text

    exec sp_executesql  @runtext

    fetch next from C_CURSOR INTO @DB_Name
END

CLOSE C_CURSOR
DEALLOCATE C_CURSOR

Thanks again!

Funkymonk
  • 61
  • 1
  • 1
  • 5
  • Please post this as an update to the question. It is not an answer; it is just more detail on the question. It should be placed there and this answer should be deleted. – Solomon Rutzky Feb 04 '15 at 22:27
0

I ended up combining 2 things. I made a SQL script that creates a cursor to find the databases and then prints a list of commands for a CMD prompt. I then run that in the command prompt. Below is what we output with our sql script and then save as a .bat file that we run. It's working great!

That script is essentially created with the following SQL script:

/*** GET DATABASES IN THE CURSOR QUERY BELOW! */
declare C_CURSOR CURSOR FOR 
        select  [Name] from sys.databases 
        where   name like '%_db' 
        order by name

/* THIS IS WHERE THE CURSOR STARTS*/
declare @DB_Name as nvarchar(200)

OPEN C_CURSOR
fetch next from C_CURSOR INTO @DB_Name
WHILE(@@FETCH_STATUS = 0)
BEGIN

    print 'SQLCMD -S "localhost" -E -i "C:\release.sql" -d ' +  @DB_Name + ' -o ' +  @DB_Name + '_results.txt'

    fetch next from C_CURSOR INTO @DB_Name
END

CLOSE C_CURSOR
DEALLOCATE C_CURSOR

That outputs the following which we then run in a .bat file

SQLCMD -S "localhost" -E -i "C:\release.sql" -d database1 -o database1_results.txt
SQLCMD -S "localhost" -E -i "C:\release.sql" -d database2 -o database2_results.txt
SQLCMD -S "localhost" -E -i "C:\release.sql" -d database3 -o database3_results.txt

Thanks everyone!

Funkymonk
  • 61
  • 1
  • 1
  • 5
  • Why would you do this? ... it is 3 steps, 2 of them requiring manual execution, compared to the 1 step I suggested that can be fully automated. ..what is the benefit of this method? ..did you see my updated answer? ..I explain why you should use `(local)` instead of `localhost` . – Solomon Rutzky Feb 06 '15 at 05:54