SQL Server - Skip querying a database if it is in RESTORING state - sql-server

I have this script that I found online and have adapted to my database instance. What it does is looks to see if a specific table exists in all the SQL Server databases within the instance. If the table is found and if not empty then it will list the name of the database where the table was found. Its all good so far however, if a database is in Restoring state then my script gets stuck at that database. I tried a different options such as STATE = 0 (which is ONLINE) or state_desc != 'RESTORING' in the 'Where' clause but it still fails. The databases in Restoring state isn't the worry here as sometimes someone may be genuinely trying to restore. All I want to do is not to query that database if its not ONLINE. I wanted to somehow make use of SELECT DATABASEPROPERTYEX ('DatabaseName', 'Status') but can't figure out how. I am not a T-SQL master, so really hoping if someone can tweak this query just to skip the DBs in Restoring state. This will be much appreciated, thank you..
SET NOCOUNT ON;
IF OBJECT_ID (N'tempdb.dbo.#temptbl') IS NOT NULL DROP TABLE #temptbl
CREATE TABLE #temptbl ([COUNT] INT , SiteDBName VARCHAR(50) )
DECLARE #TableName NVARCHAR(50)
SELECT #TableName = '[dbo].[PRJMGTLocation]'
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = STUFF(
( SELECT CHAR(13) + 'SELECT ''' + name + ''', COUNT(1) FROM [' + name + '].' + #TableName
FROM sys.databases
WHERE OBJECT_ID(name + '.' + #TableName) IS NOT NULL
-- and STATE = 0 and state_desc != 'RESTORING'
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
INSERT INTO #temptbl (SiteDBName, [COUNT])
EXEC sys.sp_executesql #SQL
SELECT * FROM #temptbl t where COUNT >=1 ORDER BY SiteDBName asc

Can you try the following slight tweak:
select SQL = Stuff(
(select Char(13) + 'SELECT ''' + name + ''', COUNT(1) FROM ' + QuoteName(name) + '.' + #TableName
from (select top(1000) name from sys.databases where state=0 )d
where Object_Id(d.name + '.' + #TableName) is not null
for xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
The issue is that the filtering out of rows from sys.databases is happening after the object_id() lookup, using a row-goal in a sub-query can coerce SQL Server to do it in reverse.

Related

Get value from multiple tables in all databases on MSSQL server

Given a Database server on which I only have read access to the Master DB, I need to scan all databases on the server for tables that contain "SMTP Mail Setup" in their name. I also need to know the value of the field "SMTP Server" within each of those tables.
I've been able to cobble together the following which lists the Database and Table names where the data I need is stored.
EXEC sp_MSforeachdb 'USE [?] SELECT TABLE_CATALOG as DB_Name, Table_Name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE ''%SMTP Mail Setup%'''
I'm stuck now as I can't seem to figure out how to pull the field "SMTP Server" from the given tables. Is there a better way to approach this?
You will need to generate and execute dynamic SQL based on the results of the first query.
Try the following (somewhat over-engineered) code:
DECLARE #TableNamePattern sysname = '%SMTP Mail Setup%'
DECLARE #ColumnName sysname = 'SMTP Server'
IF OBJECT_ID('TempDb..#SelectedTables') IS NOT NULL DROP TABLE #SelectedTables
CREATE TABLE #SelectedTables (DB_Name sysname, Table_Name sysname)
DECLARE #SqlTemplate1 VARCHAR(MAX) = '
USE [?]
INSERT #SelectedTables
SELECT T.TABLE_CATALOG as DB_Name, T.TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES T
JOIN INFORMATION_SCHEMA.COLUMNS C
ON C.TABLE_CATALOG = T.TABLE_CATALOG
AND C.TABLE_SCHEMA = T.TABLE_SCHEMA
AND C.TABLE_NAME = T.TABLE_NAME
WHERE T.TABLE_TYPE = ''BASE TABLE''
AND T.TABLE_NAME LIKE <TableNamePattern>
AND C.COLUMN_NAME = <ColumnName>
'
DECLARE #Sql1 VARCHAR(MAX) =
REPLACE(REPLACE(
#SqlTemplate1
, '<TableNamePattern>', QUOTENAME(#TableNamePattern, ''''))
, '<ColumnName>', QUOTENAME(#ColumnName, ''''))
EXEC sp_MSforeachdb #Sql1
SELECT * FROM #SelectedTables ORDER BY DB_Name, Table_Name
DECLARE #SqlTemplate2 VARCHAR(MAX) = 'UNION ALL
SELECT <DB_NAME_Text> AS DB_NAME, <Table_Name_Text> AS Table_Name, <Column_Name>
FROM <DB_NAME>..<Table_Name>
'
DECLARE #Sql2 VARCHAR(MAX) = STUFF((
SELECT REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
#SqlTemplate2
, '<DB_NAME_Text>', QUOTENAME(T.DB_NAME, ''''))
, '<Table_Name_Text>', QUOTENAME(T.Table_Name, ''''))
, '<DB_NAME>', QUOTENAME(T.DB_NAME))
, '<Table_Name>', QUOTENAME(T.Table_Name))
, '<Column_Name>', QUOTENAME(#ColumnName))
FROM #SelectedTables T
ORDER BY T.DB_NAME, T.Table_Name
FOR XML PATH(''),TYPE
).value('text()[1]','nvarchar(max)')
, 1, 9, '') -- Remove initial UNION ALL
SET #Sql2 = #Sql2 + '
ORDER BY 1, 2, 3' -- Lazy way of referencing columns
PRINT #Sql2 -- Might be truncated
EXEC (#Sql2)
DROP TABLE #SelectedTables
I added checks to ensure that the column is defined in the selected table and that the table is a true table ('BASE TABLE') and not a view. The sql templates are run through a series a replace functions that insert the properly quoted and escaped object names. The first template is an expanded version of your original executed sql. The second is used to generate a series or selects for each table.
The FOR XML PATH(''),TYPE concatenates all of the generated selects into a single XML string, and the .value() at the end reliably extracts that text avoiding any XML encoding artifacts. Newer SQL Server versions support a STRING_AGG() function that can be used instead, but the code I had on hand was already using FOR XML.
Each query starts with UNION ALL so that all results display in a combined grid. The STUFF(..., 1, 11, '') strips off the leading UNION ALL.
Finally the resulting SQL is printed and executed.

Create view that selects from identical tables from all databases on the server and can handle any time a new database is added to the server

I have a system that takes in Revit models and loads all the data in the model to a 2016 SQL Server. Unfortunately, the way the system works it created a new database for each model that is loaded. All the databases start with an identical schema because there is a template database that the system uses to build any new ones.
I need to build a view that can query data from all databases on the server but can automatically add new databases as they are created. The table names and associated columns will be identical across all databases, including data types.
Is there a way to pull a list of current database names using:
SELECT [name] FROM sys.databases
and then use the results to UNION the results from a basic SELECT query like this:
SELECT
[col1]
,[col2]
,[col3]
FROM [database].[dbo].[table]
Somehow replace the [database] part with the results of the sys.databases query?
The goal would be for the results to look as if I did this:
SELECT
[col1]
,[col2]
,[col3]
FROM [database1].[dbo].[table]
UNION
SELECT
[col1]
,[col2]
,[col3]
FROM [database2].[dbo].[table]
but dynamically for all databases on the server and without future management from me.
Thanks in advance for the assistance!
***Added Info: A couple suggestions using STRING_AGG have been made, but that function is not available in 2016.
Try this. It will automatically detect and include new databases with the specified table name. If a database is dropped it will automatically exclude it.
I updated the TSQL. STRING_AGG concatenates the string with each database. Without it it only returns the last database. STRING_AGG is more secure than += which also concatenates. I changed the code so it generates and executes the query. In SQL 2019 the query is all in one line using +=. I don't have SQL 2016. It may format it better in SQL 2016. You can uncomment --SELECT #SQL3 to see what the query looks like. Please mark as answer if this is what you need.
DECLARE #TblName TABLE
(
TblName VARCHAR(100)
)
Declare #SQL VARCHAR(MAX),
#SQL3 VARCHAR(MAX),
#DBName VARCHAR(50),
#Count Int,
#LoopCount Int
Declare #SQL2 VARCHAR(MAX) = ''
Select Identity(int,1,1) ID, name AS DBName into #Temp from sys.databases
Select #Count = ##RowCount
Set #LoopCount = 1
While #LoopCount <= #Count
Begin
SET #DBName = (SELECT DBName FROM #Temp Where ID = #LoopCount)
SET #SQL =
' USE ' + #DBName +
' SELECT TABLE_CATALOG FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ''table'''
INSERT INTO #TblName (TblName)
EXEC (#SQL)
Set #LoopCount=#LoopCount + 1
End
SELECT #SQL2 +=
' SELECT ' + char(10) + 
' [col1] ' + char(10) + 
' ,[col2] ' + char(10) + 
' ,[col3] ' + char(10) + 
' FROM [' + TblName + '].[dbo].[table] ' + char(10) + 
' UNION '
FROM #TblName
DROP TABLE #Temp
SET #SQL3 = (SELECT SUBSTRING(#SQL2, 1, LEN(#SQL2) - 5))
--SELECT #SQL3
EXEC (#SQL3)

Deadlock MS-SQL when Inserting Into a Table

I am using the following query to insert in the respective historical table changes occurred to a given table. I am executing the same query simultaneously for multiple tables in python (changing the table name and database). None of the historical tables have foreign keys. But some of the executions end up in deadlock. Each table have assign a unique historical table. I am not sure how to solve the issue. Is it because I use a variable table with the same name in all the procedures?
declare #name_tab table (name_column varchar(200),
dtype varchar(200))
declare #columns varchar(max)
declare #query varchar(max)
declare #database varchar(200)
declare #table_name varchar(200)
set #database = '%s'
set #table_name = '%s'
insert into #name_tab
select c.name as name_column,
t.name as dtype
from sys.all_columns c
INNER JOIN sys.types t
ON t.system_type_id = c.system_type_id
where OBJECT_NAME(c.object_id) = #table_name
set #columns= stuff((select ','+name_column from #name_tab FOR XML PATH('')),1, 1, '')
set #query= 'insert into ' +#database+'..'+'HISTORY_'+#table_name+' select super_q.* from' +
'(select cast (GETDATE() as smalldatetime) as TIME_MODIFIED, new_info.* from '+
'(SELECT ' + #columns + ' From '+#database+'..'+#table_name +
' except ' +
'SELECT ' + #columns + ' From '+#database+'..'+'HISTORY_'+#table_name + ') new_info) as super_q'
execute(#query)
I got this sample from system_health
It appears that some concurrent process is altering or creating a table at the same time. The deadlock XML should contain additional details about what's going on.
But whatever the actual cause, the solution is simple. Use your scripting above to generate the trigger bodies in static SQL so you don't have to query the catalog for every insert.
Create a procedure in your database called, say, admin.GenerateHistoryTables and one called admin.GenerateHistoryTriggers and run those ahead of time to install the history tables and wire up the triggers.
Or stop re-inventing the wheel and use Change Data Capture or Temporal Tables.

Get all stored procedures where table is being used in SQL Server

I need the query or code in SQL Server to get all the stored procedures in which table is being used in the server (need all stored procedures in all databases on that server).
SELECT *
FROM sys.procedures
WHERE OBJECT_DEFINITION(OBJECT_ID) LIKE '%[dbo].[Batch]%.
Above query will give in the current database but I need the query to get from all the databases in that server.
Thanks. Sandeep
Try this:
select * from sysobjects where id in
(select id from syscomments where text like '%myquery%')
order by [name]
where "myquery" is the name of the table. You need to run this on each database on the server individually.
EXECUTE master.sys.sp_MSforeachdb
"USE [?]; SELECT * FROM sys.procedures WHERE OBJECT_DEFINITION(OBJECT_ID) LIKE '%[dbo].[Batch]%'"
Just consider your permutations of the code that references the Batch object (dbo.batch, [dbo].[batch], dbo.[batch], etc)
Thanks for the help. I got that below query will give all Stored Procedures that are dependent on any table or view for all the databases in the server.
DECLARE #SQL NVARCHAR(max)
SELECT #SQL = STUFF((SELECT ' UNION ALL SELECT ' + quotename(NAME, '''') + ' AS Db_Name, Routine_Name collate SQL_Latin1_General_CP1_CI_AS as SP_Name FROM ' + quotename(NAME) + '.INFORMATION_SCHEMA.Routines WHERE ROUTINE_Definition like ''%Replace_Table_Name_Here%'' AND ROUTINE_TYPE = ''PROCEDURE''' FROM sys.databases ORDER BY NAME FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 11, '')
EXECUTE sp_executeSQL #SQL

How to search all instance for regex value in SQL Server?

I work in a secure environment and need a way of running a script against our SQL instances i.e. all dbs, tables etc. to search for the use of certain values and to show where they are... is there any way of doing this? I've scoured the net but can't seem to find this!
I've put together this script with help from various sources (vyaskn) but need help in expanding the code to include db's and regex functionality. I just don't have enough experience in using the system views and dynamic SQL to do it myself. It's more important that I get the regex stuff working if searching through all dbs is more difficult.
sp_configure 'clr enabled',1
reconfigure
DECLARE #SearchStr NVARCHAR(100)
-- search for uk phone number for example
SET #SearchStr = '(((\+44)? ?(\(0\))? ?)|(0))( ?[0-9]{3,4}){3}'
CREATE TABLE #Results
(
ColumnName NVARCHAR(370) ,
ColumnValue NVARCHAR(3630)
)
SET NOCOUNT ON
DECLARE #TableName NVARCHAR(256) ,
#ColumnName NVARCHAR(128) ,
#SearchStr2 NVARCHAR(110)
SET #TableName = ''
SET #SearchStr2 = QUOTENAME('%' + #SearchStr + '%', '''')
WHILE #TableName IS NOT NULL
BEGIN
SET #ColumnName = ''
SET #TableName = ( SELECT MIN(QUOTENAME(TABLE_SCHEMA) + '.'
+ QUOTENAME(TABLE_NAME))
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND QUOTENAME(TABLE_SCHEMA) + '.'
+ QUOTENAME(TABLE_NAME) > #TableName
AND OBJECTPROPERTY(OBJECT_ID(QUOTENAME(TABLE_SCHEMA)
+ '.'
+ QUOTENAME(TABLE_NAME)),
'IsMSShipped') = 0
)
WHILE ( #TableName IS NOT NULL )
AND ( #ColumnName IS NOT NULL )
BEGIN
SET #ColumnName = ( SELECT MIN(QUOTENAME(COLUMN_NAME))
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = PARSENAME(#TableName,
2)
AND TABLE_NAME = PARSENAME(#TableName,
1)
AND DATA_TYPE IN ( 'char',
'varchar',
'nchar',
'nvarchar',
'int', 'decimal' )
AND QUOTENAME(COLUMN_NAME) > #ColumnName
)
IF #ColumnName IS NOT NULL
BEGIN
INSERT INTO #Results
EXEC
( 'SELECT ''' + #TableName + '.'
+ #ColumnName + ''', LEFT('
+ #ColumnName + ', 3630) FROM '
+ #TableName + ' (NOLOCK) ' + ' WHERE '
+ #ColumnName + ' LIKE ' + #SearchStr2
)
END
END
END
SELECT ColumnName ,
ColumnValue
FROM #Results
DROP TABLE #Results
--sp_configure 'clr enabled',0
--reconfigure
First you need to find all available SQL instance within the particular network. You can checkout this example for sample code. Once you receive the information about all SQL instances, such as server name, instance name, version, and databases. Now you must have id and password to connect with each database to retrieve any information from any Table present any database. Next create connection with each database and do required search operation.
You can take help of Full-Text Indexing while searching a particular regular expression.
I've had success using one of the tools from EMS to do a regex replace on an Oracle DB schema. By now, I forget which tool I used, but I was able to export the schema to an SQL file and manipulate it as plain text.
You might want to try out the SQL Manager and the DB Extract products. They both have free trials, and both support a wide range of databases.
Once you get your schema, data, and stored procedures into a single SQL file, running a search should be no problem.

Resources