Use SELECT results as a variable in a loop - sql

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.

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)

Executing SQL query on multiple databases

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.

iterative executing stored procedure with a set based approach

I have an issue where I am trying to replace the following code with a different solution. Currently I am using a cursor but it is running to slowly. I am under the understanding that iterative solutions can only be completed with cursors or while loops but I am trying to find a set based approach and running out of ideas. I was hoping that I could find some inspiration here. Thanks all.
--used to find a unique list of Some_ID
#Id1, #Id2, #Id3
DECLARE SomeCursor CURSOR FOR
SELECT SOME_ID FROM SomeTable
WHERE ID1=#Id1 AND ID2=#Id2 and ID3=#Id3
OPEN SomeCursor
FETCH NEXT FROM SomeCursor INTO #SomeID
WHILE ##Fetch_Status = 0
BEGIN
Print #SomeID
--simply populates a single table with values pulled from
--other tables in the database based on the give parameters.
EXEC SP_PART1 #SomeID, #parameters...
print 'part 2 starting'
EXEC SP_PART2 #SomeID, #parameters...
FETCH NEXT FROM SomeCursor INTO #SomeID
print getdate()
END
CLOSE SomeCursor;
DEALLOCATE SomeCursor;
Your only option to make this set-based is to rewrite the sps to make them set-based (using table-valed parameters intead of individual ones) or to write set based code in this proc instead of re-using procs designed for single record use. This is a case where code re-use is usually not appropriate.
I'm not too sure what you want, but why not use your select statement to create your sql scripts and execute them all at once with something like this.
DECLARE #sql VARCHAR(MAX);
SELECT #sql = COALESCE(#sql,'') + 'EXEC SP_Part1 ' + SOME_ID + '; EXEC SP_Part2 ' + SomeID + '; GO '
FROM SomeTable
WHERE ID1=#Id1 AND ID2=#Id2 and ID3=#Id3
EXEC (#sql)

Is there a better way to execute dynamic SQL that doesn't use a cursor?

I have dynamic SQL stored in a SQL table that I have to execute under certain conditions. Currently, we use cursors to handle that for us, but I was always told to avoid cursors when possible as they aren't the most efficient way of doing things. So, my question is: how do I execute dynamic SQL without them (if there's a way)? The entire system is built around this dynamic SQL mess, so there is no changing it.
For this, just assume the table has Id AS IDENTITY and SQL AS VARCHAR fields, where the SQL field contains the SQL to be executed (obviously).
EDIT:
Basically, I want to loop through the table and execute the SQL in the SQL column.
So, a row in the table will basically look like this:
ID SQL
-- ----------------------
1 SELECT * FROM RECORD
2 SELECT * FROM PERSON
3 SELECT * FROM LOCATION
I haven't written any code because what I'd write is a cursor to traverse through the table and execute it. I just don't know of any other ways of looping a table and executing that string as a SQL query other than something like:
DECLARE #sql VARCHAR(MAX)
DECLARE _cursor CURSOR
FOR
SELECT [SQL]
FROM #tmp2
OPEN _cursor
FETCH NEXT FROM _cursor INTO #sql
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT ( #sql )
END
CLOSE _cursor
DEALLOCATE _cursor
You can use any number of concatenation tricks to make one big batch without using a cursor, I personally use the FOR XML trick a lot.
Here's an overview:
http://www.simple-talk.com/sql/t-sql-programming/concatenating-row-values-in-transact-sql/
However, the cursor (while generally a code smell) isn't going to contribute a terrible amount to the non-performance of this. And you will have an opportunity to handle errors etc a lot easier than with a single batch.
In addition, if you have DDL in some of those statements which has to be the first statement in a batch, then you would need to submit them in separate batches. EXEC or sp_executesql doesn't implement any batch splitting like SSMS has the GO batch separator.
Ignoring the fundamental flaws in this whole schema....
declare #sql nvarchar(max)
select #sql = ''
select #sql = #sql + SQL + ';' from #tmp2
exec sp_executesql #sql
At least we've got rid of your cursor now :)
EDIT: Code that is working for me...
create table #tmp2 (sql nvarchar(100))
insert #tmp2 values ('select * from sysobjects')
insert #tmp2 values ('Select * from sysColumns')
declare #sql nvarchar(max)
select #sql = ''
select #sql = #sql + SQL + ';' from #tmp2
exec sp_executesql #sql
drop table #tmp2

SQL Server - A script to loop through all remote tables and perform "Select * into ...'

Here's what I'd like to do.
For each table in linkedserver.database whose tablename is like 'text%'
(inside loop)
A. If current_table exists locally, drop it
B. select * into table.name (local) from linkedserver.tablename (copy
schema + data)
C. Possibly check for errors and Print some text about it?
Next
Any idea if this script is possible? I'm very clueless about working with table names if it would be possible to
select * into #Variable_Storing_Table_Name
from LinkedServer.DB.#Variable_Storing_Table_Name
Well, here's how to do this using a cursor:
use database
go
declare #link_table nvarchar(255)
declare #local_table nvarchar(255)
declare table_list cursor for
select
tlink.name,
tlocal.name
from
linkedserver.database.sys.tables tlink
left outer join sys.tables tlocal on
tlink.name = tlocal.name
open table_list
fetch next from table_list into #link_table, #local_table
while ##FETCH_STATUS = 0
begin
begin try
if #local_table is not null
begin
sp_executesql N'drop table ' + quotename(#local_table)
end
sp_executesql N'select * into ' + quotename(#link_table) +
' from linkedserver.database..' + quotename(#link_table)
print #link_table + ' copied.'
end try
begin catch
print 'Error: ' + ERROR_MESSAGE()
end catch
fetch next from table_list into #link_table, #local_table
end
close table_list
deallocate table_list
While cursors should generally be avoided, here you're looking to do a lot of logic behind each and every row. So, here it is. What it does is grab all of the linked tables and match any of the local tables to those, or null if the local table doesn't exist. This places it in a cursor, which we can use to iterate through the rowset.
The fetch next command grabs the next row from our cursor and then applies your logic to it (drop it if the local table exists, then do a select * into...).
You can catch errors one of two ways. I used the try...catch block, but you can also check ##ERROR and see if it's not equal to zero. Really, whatever you feel most comfortable with.
As a disclaimer for the anti-cursor crowd: Cursors aren't evil, they're just often used improperly.
There is an undocumented SQL Server function called sp_foreachtable that might do what you want. I'm not sure if it works on linked databases though... a Web search might turn something up.