Home > Net >  Get value from multiple tables in all databases on MSSQL server
Get value from multiple tables in all databases on MSSQL server

Time:10-13

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?

CodePudding user response:

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.

  • Related