Is it possible to define full SELECT clause in USE GO clause? - sql-server

I'm trying to define full SELECT clause in USE for a list of databases.
I'm trying to query across all databases in a server to list all available functions. There are about 80 databases so I'm going to use variable table to pass the list of databases to USE clause to be able to switch between every database and run simple query to list functions in there, all through a WHILE loop.
Is it possible to do something like,
USE
(SELECT dBname
From dBList
where rowID=#currentRow)
GO
IF not, any suggestion to do it less painfully than querying databases one by one?
This is what I have done in MS SQL Server 2014 but no joy,
declare #dBaseList Table
( RowID int not null primary key identity(1,1), dBname varchar(100)
)
declare #SeldBname varchar(100)
declare #rowsdBaseList int
declare #currentRow int
insert into #dBaseList (dBname)
SELECT name FROM sys.databases
set #rowsdBaseList=##ROWCOUNT
set #currentRow=0
while #currentRow<#rowsdBaseList
begin
set #currentRow+=1
use
(select #SeldBname=dBname
from #dBaseList
where RowID=#currentRow)
Go
select * from sys.objects where type='FN'
end
It should look something like,
USE
dBname1
GO
select * from sys.objects where type='FN'
USE
dBname2
GO
select * from sys.objects where type='FN'
USE
dBname3
GO
select * from sys.objects where type='FN'
.
.
.
.
.

You should just be able to use a 3 part name in your query:
select * from dBname1.sys.objects where type='FN'
select * from dBname2.sys.objects where type='FN'
select * from dBname3.sys.objects where type='FN'
If you'd prefer to do it programmatically, you can use the (unsupported and undocumented) sp_MSforeachdb proc:
EXEC sp_MSforeachdb 'IF DB_ID(''?'') > 4 BEGIN select * from ?.sys.objects where type=''FN'' END'
The IF DB_ID(''?'') > 4 conditional excludes system databases, which you presumably don't want.
With regard to using a SELECT statement in a USE statement, you can't do it as you have specified in your question. However, you can incorporate it into the sp_MSforeachdb call, should you need to:
EXEC sp_MSforeachdb 'IF DB_ID(''?'') > 4 BEGIN USE ?; select DB_NAME() AS [DatabaseName], * from sys.objects where type=''FN'' END'

Have you tried Dynamic-SQL? Something like the code below, this is just a sample as I don't have right now my machine to actually test it.
SELECT name AS DatabaseName INTO #TEMP FROM master.dbo.sysdatabases
DECLARE #Count INT, #Database VARCHAR(MAX), #Query
SET #Count = (SELECT COUNT(*) FROM #TEMP)
WHILE (#Count) > 0
BEGIN
SET #Database = (SELECT TOP(1) DatabaseName FROM #TEMP)
SET #Query = 'SELECT * FROM ' + #Database + '.sys.objects WHERE type = ''FN'''
EXEC (#sqlCommand)
DELETE FROM #TEMP WHERE DatabaseName = #Database
SET #Count = (SELECT COUNT(*) FROM #TEMP)
END

Below is a method to return the query results from all databases as a single result set and include the database name.
DECLARE #SQL nvarchar(MAX) =
STUFF((
SELECT ' UNION ALL SELECT N''' + name + N''' AS DatabaseName, * FROM ' + QUOTENAME(name) + N'.sys.objects where type=''FN'''
FROM sys.databases
FOR XML PATH(''), TYPE).value('.','nvarchar(MAX)'), 1, 11,'') + N';';
EXEC(#SQL);

use sp_MSforeachdb
use [yourdatabase_in_which_you_store_the_results]
go
--create table functions_in_alldb(db nvarchar(100),fn_name nvarchar(200))
declare #functions_in_alldb as table(db nvarchar(100),fn_name nvarchar(200))
go
--truncate table functions_in_alldb
go
DECLARE #command varchar(1000)
SELECT #command = 'USE ?
if ''?'' not in (''master'',''msdb'',''tempdb'',''model'',''msdb'')
insert into #functions_in_alldb(db,fn_name) SELECT ''?'',name FROM sysobjects WHERE xtype = ''FN'' ORDER BY name'
EXEC sp_MSforeachdb #command
select * from #functions_in_alldb

Related

Creating table of DB and table info on SQL Server Instance using while loop

I was wondering why i can't substitute the DB name for the variable #DBNAME in the following while loop:
I can use the variable as the DB_NAME but it wont let me substitute the DB name for sys.tables
it is giving me the following error: Incorrect syntax near '.'.
DROP TABLE #TABLE_INFO;
CREATE TABLE #TABLE_INFO
(
DB_NAME VARCHAR(100),
TABLE_NAME VARCHAR(100),
CREATE_DATE DATE
);
DECLARE #COUNT AS INT = 1
DECLARE #DBNAME AS VARCHAR(100)
WHILE #COUNT < (SELECT MAX(DATABASE_ID) +1 FROM SYS.DATABASES)
BEGIN
SET #DBNAME = (SELECT NAME FROM SYS.DATABASES WHERE DATABASE_ID = #COUNT)
INSERT INTO #TABLE_INFO
SELECT
#DBNAME AS DB_NAME,
A1.NAME AS TABLE_NAME,
A1.CREATE_DATE
FROM #DBNAME.SYS.TABLES A1
SET #COUNT = #COUNT+1
END
SELECT * FROM #TABLE_INFO;
T-SQL does not allow database object-identifiers to be parameteried.
...So you have to use Dynamic SQL (i.e. building up a query inside a string value and passing it to sp_executesql).
Obviously this is dangerous due to the risk of SQL injection, or simply forgetting to escape names correctly (use QUOTENAME) which may cause inadvertent data-loss (e.g. if someone actually named a table as [DELETE FROM Users]).
So it's important to use Dynamic SQL as little as possible.
What you can do is use INSERT INTO #tableVariable EXECUTE sp_executesql #query which allows you to store the results of a stored procedure - or any dynamic SQL - into a table-variable or temporary-table without them being output directly to your client connection and then process the results using non-dynamic SQL.
You can also use CROSS APPLY too if you can move all of your dynamic-SQL logic to a stored-procedure.
Try this:
BTW, I changed your WHILE loop to use a STATIC READ_ONNLY CURSOR which avoids your assumptions about how sys.databases's database_id work (e.g. what if sys.databases lists only these 4 database_id values 1, 2, 99997, 99998? Your WHILE loop would be wasting time checking for 3, 4, 5, 6, ..., 99996).
I also use a table-variable rather than a temporary-table because the scope and lifetime of a table-variable is easier to reason about compared to a temporary-table. That said, there aren't really any performance benefits of TVs over TTs (though you can pass TVs as table-valued-parameters, which is nice and generally much better than passing data to sprocs using TTs).
DECLARE #results TABLE (
DatabaseName nvarchar(100) NOT NULL,
SchemaName nvarchar(50) NOT NULL,
TableName nvarchar(100) NOT NULL,
Created datetime NOT NULL
);
DECLARE #dbName nvarchar(100);
DECLARE c CURSOR STATIC READ_ONLY FOR
SELECT QUOTENAME( [name] ) FROM sys.databases;
OPEN c;
FETCH NEXT FROM c INTO #dbName;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #dbQuery nvarchar(1000) = N'
SELECT
''' + #dbName + N''' AS DatabaseName,
s.[name] AS SchemaName,
t.[name] AS TableName,
t.create_date AS Created
FROM
' + #dbName + N'.sys.tables AS t
INNER JOIN sys.schemas AS s ON t.schema_id = s.schema_id
ORDER BY
s.[name],
t.[name]
';
INSERT INTO #results
( DatabaseName, SchemaName, TableName, Created )
EXECUTE sp_executesql #dbQuery;
FETCH NEXT FROM c INTO #dbName;
END;
CLOSE c;
DEALLOCATE c;
--------------------------
SELECT
*
FROM
#results
ORDER BY
DatabaseName,
SchemaName,
TableName;
I am able to copy+paste this query into SSMS and run it against my SQL Server 2017 box without modifications and it returns the expected results.

How to join sys.databases, sys.tables and sys.columns

I have to check for value existence in a subset of tables in a subset of databases of a sql server instance. Beware I need to do this because I have 30 databases with same schema name and similar structure. Querying all databases separately is a waste of time.
The query generates correctly code for existing tables, but the additional check for column existence in table fails.
The column in some tables does not exist so the generated code must not include queries on tables without this column.
To solve this I need to realiably find a way to join sys.databases with sys.tables and then sys.columns. Or an alternative way to query all the required databases in a time saving manner.
SET NOCOUNT ON;
IF OBJECT_ID (N'tempdb.dbo.#temp') IS NOT NULL
DROP TABLE #temp
CREATE TABLE #temp
(
exist INT
, DB VARCHAR(50)
, tbname VARCHAR(500)
)
/*tables common root,
all tables i need to query start with this prefix and a number between 1 and 50
and some resulting tables do not exist
ex: dbo.Z_WBL_ASCHEDA23 exist in wbcto, while dbo.Z_WBL_ASCHEDA23 does not exist in db wbgtg
*/
DECLARE #TableName NVARCHAR(200)
SELECT #TableName = 'dbo.Z_WBL_ASCHEDA'
DECLARE #SQL NVARCHAR(MAX)
;WITH n(n) AS
(
SELECT 1
UNION ALL
SELECT n+1 FROM n WHERE n < 50
)
SELECT #SQL = STUFF((
SELECT CHAR(13)+'SELECT COUNT(1), ''' + db.name + ''', '''+
#TableName+CONVERT(VARCHAR, n.n)+''' FROM ' +#TableName+CONVERT(VARCHAR, n.n)
+ ' WHERE COALESCE(s_dettagli,'''') = ''CONTROLLATO'' '
+CHAR(13)
FROM sys.databases db
INNER JOIN n ON 1=1
INNER JOIN sys.tables t ON OBJECT_ID(db.name + '.' + #TableName+CONVERT(VARCHAR, n.n)) IS NOT NULL
INNER JOIN sys.columns c ON t.OBJECT_ID = c.OBJECT_ID and c.name = 's_dettagli'
/*join on columns not working, generates sql for tables without 's_dettagli' column and query fails*/
WHERE db.name like 'wb%' --check only databases starting with 'wb'
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
select #SQL
INSERT INTO #temp (exist, DB, tbname)
EXEC sys.sp_executesql #SQL
SELECT *
FROM #temp t
where exist <> 0
EDIT: adding some sql generated from query
SELECT COUNT(1), 'wb360', 'dbo.Z_WBL_ASCHEDA23' FROM wb360.dbo.Z_WBL_ASCHEDA23 WHERE COALESCE(s_dettagli,'') = 'CONTROLLATO'
SELECT COUNT(1), 'Wbbim', 'dbo.Z_WBL_ASCHEDA32' FROM Wbbim.dbo.Z_WBL_ASCHEDA32 WHERE COALESCE(s_dettagli,'') = 'CONTROLLATO'
the table of first query doesn't contain 's_dettagli' column
EDIT2: SOLUTION
EXEC sp_MSforeachdb '
IF ''?'' not like ''wb%''
RETURN
USE [?]
EXEC sp_MSforeachtable
#replacechar = ''!'',
#command1 = ''SELECT ''''?'''' AS db_name, ''''!'''' AS table_name, COUNT(*) FROM ! '',
#whereand = '' And Object_id In (
Select t.Object_id
From sys.objects t
INNER JOIN sys.columns c on c.Object_id = t.Object_id
Where t.name like ''''Z_WBL_ASCHEDA%''''
AND c.name = ''''s_dettagli'''' )'' '
Sys.columns can be joined to sys.tables using the object_id field (the object_id is the representation of the table itself).
sys.tables is run in the context of the database you are querying, hence you cannot see a table contained in another database. sys.databases can be run on any database on an instance and allow you to view other databases on the same instance. As such you don't need to join the table to the database (also the reason why there is no database_id field within sys.tables).
I hope that helps. Any clarification please let me know.
I would suggest alternative ways:
use registered Servers in SSMS and run the script on each database here
use exec sys.sp_MSforeachdb here
use sqlcmd and powershell to switch databases
I believe this script can help you :
SET NOCOUNT ON;
IF OBJECT_ID (N'tempdb.dbo.#Temp') IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Temp
(
exist INT
, DB VARCHAR(50)
, tbname VARCHAR(500)
)
DECLARE #SchemaName NVARCHAR(200)
DECLARE #TableName NVARCHAR(200)
DECLARE #ColumnName NVARCHAR(200)
DECLARE #SearchText NVARCHAR(200)
DECLARE #DBNameStartWith NVARCHAR(200)
DECLARE #SQL NVARCHAR(MAX)
SET #DBNameStartWith = 'wb'
SET #SchemaName = 'dbo'
SET #TableName = 'Z_WBL_ASCHEDA'
SET #ColumnName = 's_dettagli'
SET #SearchText = 'CONTROLLATO'
DECLARE #DatabaseName varchar(100)
DECLARE Crsr CURSOR FOR
SELECT name
FROM MASTER.sys.sysdatabases
WHERE name LIKE ''+#DBNameStartWith+'%'
OPEN Crsr
FETCH NEXT FROM Crsr INTO #DatabaseName
WHILE ##FETCH_STATUS = 0
BEGIN
IF ISNULL((SELECT COUNT(1) FROM SYS.TABLES T,SYS.COLUMNS C WHERE T.object_id=C.object_id AND T.name=#TableName AND C.name=#ColumnName),0)>0
BEGIN
SET #SQL = '
IF EXISTS (SELECT 1 FROM '+#DatabaseName+'.SYS.TABLES T,'+#DatabaseName+'.SYS.COLUMNS C WHERE T.object_id=C.object_id AND T.name='''+#TableName+''' AND C.name='''+#ColumnName+''')
BEGIN
SELECT COUNT(1),'''+#DatabaseName+''','''+#TableName+'''
FROM '+#DatabaseName+'.'+#SchemaName+'.'+#TableName+'
WHERE '+#ColumnName+'=''' +#SearchText+'''
END'
PRINT(#SQL)
INSERT INTO #Temp
EXEC sp_executesql #SQL
END
FETCH NEXT FROM Crsr INTO #DatabaseName
END
CLOSE Crsr
DEALLOCATE Crsr
SELECT * FROM #Temp

SQL Update Join Multiple Tables (if field exists)

This is my first post to StackOverflow. I've been using this amazing resource for a number of years to answer hundreds of SQL and PowerShell questions, however this one has me stumped for a number of days.
I am using SQL Server 2014 SP2 and I am trying to do an update to DATABASE1, FIELD1, then FIELD2 then FIELD3 from multiple other database.
FIELD1 may exist in one of multiple other databases.
FIELD1 may not exist in ALL databases - which is where I have the problem.
Database Design Link
I have the following (anonymised) query and it appears to be working:
EXEC sp_MSforeachdb 'IF ''?'' IN (''DATABASE2'',''DATABASE3'',''DATABASE4'')
BEGIN
UPDATE DATABASE1.PARAMETER
SET B.[VALUE] = A.[FIELD1]
FROM DATABASE1.TABLE1 B
INNER JOIN ?.dbo.[TABLE2] A
ON A.JOINVALUE = B.JOINVALUE
WHERE B.COLUMN2 = ''SOMETHING''
AND COLUMN3= ''PF.T.FIELD1''
END ;'
Until I get to say FIELD8, as it exists in DATABASE1 but not in DATABASE2, DATABASE3 or DATABASE4. I then get the following error:
Msg 207, Level 16, State 1, Line 30
Invalid column name 'FIELD8'.
From my Google and StackOverflow searches, I've tried to use (for the first time) a:
IF EXISTS (SELECT COLUMN1 FROM Database2.Table2 WHERE Column1='Field8')
EXEC .......
But that's where I started to really struggle.
Hope the above makes sense.
Any tips or assistance would be greatly appreciated.
N.B. I have about 3,000 fields in Database1 which require updating. I've so-far built all my UPDATE statements dynamically.
You can create stored proc, that will search for columns and tables in system tables:
ALTER PROCEDURE dbo.check_table_exists
-- Add the parameters for the stored procedure here
#table_name nvarchar(255),
#column_name nvarchar(255)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #SQLString nvarchar(max),
#ParmDefinition nvarchar(500) = N'#table_name nvarchar(255), #column_name nvarchar(255)';
IF OBJECT_ID(N'tempdb..#check_column_exists') is not null DROP TABLE #check_column_exists
CREATE TABLE #check_column_exists (
db nvarchar(500) NULL,
column_exists bit NULL
)
SELECT #SQLString =
(
SELECT N'USE '+QUOTENAME([name]) +'; '+
'INSERT INTO #check_column_exists '+
'SELECT '''+[name]+''' as [db], '+
' COUNT(*) as column_exists ' +
'FROM sys.tables t ' +
'INNER JOIN sys.columns c ' +
' ON t.[object_id] = c.[object_id] ' +
'WHERE t.[name] = #table_name and c.[name] = #column_name; '
FROM sys.databases
WHERE [name] NOT IN (
'msdb',
'model',
'tempdb',
'master'
)
FOR XML PATH('')
) + 'SELECT [db] FROM #check_column_exists WHERE column_exists = 1; DROP TABLE #check_column_exists;'
EXEC sp_executesql #SQLString, #ParmDefinition, #table_name = #table_name, #column_name = #column_name
END
GO
You can change it to search only for columns and output the database and table name or whatever.
The output is:
db
-----------
DATABASE1
DATABASE4
...
etc
After that you can write this to table and use for dynamic SQL update query:
DECLARE #table_name nvarchar(255) = 'SomeTable',
#column_name nvarchar(255) = 'SomeField'
DECLARE #results TABLE (
db nvarchar(500)
)
INSERT INTO #results
EXEC dbo.check_table_exists #table_name, #column_name
--...Here goes building of dynamic SQL query to update data
First, sp_MSforeachdb is not reliable. For a working alternative, check here: Making a more reliable and flexible sp_MSforeachdb - Aaron Bertrand
Second, you can use system views to check if a column exists in a given table using sys.columns like so:
if exists (
select 1
from sys.columns c
where c.name = 'pilots_id' /* column name */
and c.object_id = object_id(N'pilots') /* table name */
)
begin
select 'Pilots_Id exists' /* do stuff */
end
rextester demo: http://rextester.com/UUXCB18567

SQL Server: executing part of a stored procedure that is in one database against all other databases

I am trying to run this procedure in order to select a bunch of results from multiple tables in another databases and put everything into one temp table. I can only use this one stored procedure and can't put it into another database
I tried using this here, but it didn't work and gave me the error
'Must declare the scalar variable "#lcsqlcmd"'. #lcsqlcmd
I already declared though any help?
EXEC master..sp_MSforeachdb
IF DB_ID(''?'') > 4
BEGIN
INSERT INTO #TempTable
EXECUTE (#lcsqlcmd)
END
The easiest way is to build the actual T-SQL string you want to run on each database and execute that. The other databases have no knowledge of the stored procedure you are trying to run.
The proper syntax in your case would be something like this.
CREATE TABLE #TempTable( [DBID] INT, DBName SYSNAME )
DECLARE #ForEachCommand VARCHAR( MAX ), #lcsqlcmd VARCHAR( MAX )
SET #lcsqlcmd = 'USE [?]; SELECT DB_ID() AS [DBID], DB_NAME( DB_ID()) AS DBName'
SET #ForEachCommand =
'IF DB_ID(''?'') > 4
BEGIN
insert into #TempTable
EXEC sp_executesql N''' + #lcsqlcmd + '''
end'
print #ForEachCommand
EXECUTE master.sys.sp_MSforeachdb #ForEachCommand
SELECT * FROM #TempTable
See: http://weblogs.sqlteam.com/joew/archive/2008/08/27/60700.aspx
Note: generally it is not a good idea to build your code around undocumented procedures.
The code that does not use undocumented procedure is below:
SELECT database_id, name AS DBName
INTO #Databases
FROM sys.databases WHERE database_id > 4
CREATE TABLE #TempTable( [DBID] INT, DBName SYSNAME )
DECLARE #lcsqlcmd NVARCHAR( MAX ), #Command NVARCHAR( MAX )
SET #lcsqlcmd = 'USE [?]; SELECT DB_ID() AS [DBID], DB_NAME( DB_ID()) AS DBName'
DECLARE #DBName SYSNAME
SET #DBName = ( SELECT TOP 1 DBName FROM #Databases ORDER BY DBName )
WHILE NOT #DBName IS NULL
BEGIN
SET #Command = REPLACE( #lcsqlcmd, '?', #DBName )
insert into #TempTable
EXEC sp_executesql #Command
SET #DBName = ( SELECT TOP 1 DBName FROM #Databases WHERE DBName > #DBName ORDER BY DBName )
END
SELECT * FROM #TempTable
DROP TABLE #Databases

Find out which database a stored procedure is in

I have a list of stored procedure ids and names and i need to find out which database in the server they are located. Is there an easy way to do this? Like a system table that stores this information?
Thanks
Generally for this type of query you would need to loop through the list of databases (perhaps using sp_MSforeachdb) and query the individual system tables in each database.
The below might work for you though which avoids this. The first method checks object_id and name but doesn't do any validation that the objects are actually stored procedures.
The second one just uses name as requested in the comments, does also validate object type, but only checks the default schema.
WITH objects(name, id)
AS (SELECT 'uspGetBillOfMaterials', 23671132 UNION ALL
SELECT 'uspPrintError', 37575172) SELECT 'Using Id and Name',
sys.databases.name,
objects.name
FROM sys.databases,
objects
WHERE OBJECT_NAME(id, database_id) = objects.name
UNION ALL
SELECT 'Using Name (assumes default schema)',
sys.databases.name,
objects.name
FROM sys.databases,
objects
WHERE OBJECT_ID(databases.name + '..uspGetBillOfMaterials', 'P') IS NOT NULL
Give this a whirl.
DROP TABLE #Databases
CREATE TABLE #Databases (ID INTEGER IDENTITY (0,1), DbName NVARCHAR(128))
INSERT INTO #Databases(DbName)
SELECT name FROM sys.databases
DECLARE #CurrentID INTEGER = 0,
#MaxID INTEGER = (SELECT MAX(ID) FROM #Databases)
DECLARE #DbName NVARCHAR(128) = (SELECT DbName FROM #Databases WHERE ID = #CurrentID),
#SqlCommand VARCHAR(MAX)
WHILE (#CurrentID <= #MaxID)
BEGIN
SET #SqlCommand = 'SELECT name,ROUTINES.SPECIFIC_SCHEMA,ROUTINES.SPECIFIC_NAME
FROM sys.databases AS DatabaseNames
INNER JOIN ' + #DbName + '.INFORMATION_SCHEMA.ROUTINES AS ROUTINES
ON ROUTINES.SPECIFIC_CATALOG = DatabaseNames.name
WHERE ROUTINES.ROUTINE_TYPE = ''PROCEDURE''
AND ROUTINES.SPECIFIC_NAME IN (''X'',''Y'')'
EXEC (#SqlCommand)
SET #CurrentID = #CurrentID + 1
SELECT #DbName = DbName
FROM #Databases
WHERE ID = #CurrentID
END

Resources