'Query to return database, schema, table, column for all databases

EDIT: I have edited my original post to add some clarity.

1) Is it possible to write a query that will return database, schema, table, column, and column type, for all databases on the server? In particular, is it possible to join sys.databases with other system catalog views, such as sys.tables? I haven't been able to identify a PK/FK relationship between sys.databases and sys.schema, sys.tables, or other system catalog views.

2) Otherwise, is it possible to write a query that will return the above, where I supply the database name as a parameter, for example using DB_ID('my_database') as a filter for that query? If possible, I would prefer not to use dynamic SQL?

3) Otherwise, if I have to use dynamic SQL, can I load the results of that dynamic SQL into a cursor?

Background: I am writing a stored procedure that generates code for a view. For every character column in the source table (parameter to the SP), I need to call a function that removes garbage characters. I want to create that SP in a utility database, and support creating views across multiple other databases.



Solution 1:[1]

Based on the comments and answers provided, plus my own additional research:

1) No, it is not possible to join sys.databases to other system catalog views, at least the ones I need (sys.schema, sys.tables, sys.columns). Furthermore, from my research, the system catalog views are database specific (for the above views). IOW, sys.tables lists the tables in its database, sys.columns lists columns in its database, etc. This includes the master database.

2) Although @SMor stated it is possible, I have not discovered a way to pass the database name as a parameter, without using dynamic SQL. For example, this does not work:

SELECT * FROM @database.INFORMATION_SCHEMA.COLUMNS

3) However, it is possible to use dynamic SQL + sys.databases to generate the required query as a UNION ALL query. @Zhorov provided a great example. It is also possible to use dynamic SQL to load a cursor. Again, @Zhorov provided a great example, i.e. add the cursor declaration to the dynamic SQL, then loop over the cursor in downstream code. In the future, I may use @Zhorov's code to generate the code to build the below view.

However, after further thought, since I only have to generate views over 6 or so databases, I've decided to "manually" create a view that joins the databases I need to work with:

Version 1 (for my immediate needs):

CREATE   VIEW [util].[vwGetColumnInfoISC]
AS
/* Returns column information from selected databases using INFORMATION_SCHEMA views */
WITH cteDeaths
AS (
    SELECT *
      FROM Deaths.INFORMATION_SCHEMA.COLUMNS
),
cteRLDXDth
AS (
    SELECT *
      FROM RLDXDth.INFORMATION_SCHEMA.COLUMNS
),
cteRLDXED
AS (
    SELECT *
      FROM RLDXED.INFORMATION_SCHEMA.COLUMNS
),
cteRLDXHosp
AS (
    SELECT *
      FROM RLDXHosp.INFORMATION_SCHEMA.COLUMNS
),
cteCaped
AS (
    SELECT *
      FROM caped.INFORMATION_SCHEMA.COLUMNS
),
cteHierep
AS (
    SELECT *
      FROM hierep.INFORMATION_SCHEMA.COLUMNS
),
cteUnion
AS (
    SELECT *
      FROM cteDeaths
    UNION ALL
    SELECT *
      FROM cteRLDXDth
    UNION ALL
    SELECT *
      FROM cteRLDXED
    UNION ALL
    SELECT *
      FROM cteRLDXHosp
    UNION ALL
    SELECT *
      FROM cteCaped
    UNION ALL
    SELECT *
      FROM cteHierep
)
SELECT TOP 999999999999999999 *
  FROM cteUnion
ORDER BY
       TABLE_CATALOG
      ,TABLE_SCHEMA
      ,TABLE_NAME
      ,ORDINAL_POSITION
GO

Version 2 (for future use, esp. index information - I hope to improve this view in the future to include more information):

CREATE   VIEW [util].[vwGetColumnInfoSYS]
AS
/* Returns column information from selected databases using system catalog views */
WITH cteDeaths
AS (
    SELECT 'Deaths'       AS TABLE_CATALOG
          ,sch.name       AS TABLE_SCHEMA
          ,tbl.name       AS TABLE_NAME
          ,col.name       AS COLUMN_NAME
          ,col.column_id  AS ORDINAL_POSITION
          ,typ.name       AS COLUMN_TYPE
          ,col.max_length AS MAX_LENGTH
          ,col.column_id
          ,col.precision
          ,col.scale
          ,col.collation_name
          ,col.is_nullable
          ,col.is_rowguidcol
          ,col.is_identity
          ,col.is_computed
          ,idx.name       AS index_name
          ,idx.type       AS index_type
          ,idx.type_desc  AS index_description
          ,idx.is_unique
          ,idx.data_space_id
          ,idx.ignore_dup_key
          ,idx.is_primary_key
          ,idx.is_unique_constraint
          ,idx.fill_factor
          ,idx.is_padded
          ,idx.is_disabled
          ,idx.is_hypothetical
          ,idx.allow_row_locks
          ,idx.allow_page_locks
          ,idx.has_filter
          ,idx.filter_definition
          ,idx.compression_delay
          ,ixc.key_ordinal
          ,ixc.partition_ordinal
          ,ixc.is_descending_key
          ,ixc.is_included_column
      FROM Deaths.sys.schemas sch
      JOIN Deaths.sys.tables tbl
        ON sch.schema_id = tbl.schema_id
      JOIN Deaths.sys.columns col
        ON col.object_id = tbl.object_id
      JOIN Deaths.sys.types typ
        ON typ.system_type_id = col.system_type_id
      LEFT JOIN Deaths.sys.index_columns ixc
        ON col.object_id = ixc.object_id
        AND col.column_id = ixc.column_id
      LEFT JOIN Deaths.sys.indexes idx
        ON ixc.object_id = idx.object_id
        AND ixc.index_id = idx.index_id
),
cteRLDXDth
AS (
    SELECT 'RLDXDth'     AS TABLE_CATALOG
          ,sch.name       AS TABLE_SCHEMA
          ,tbl.name       AS TABLE_NAME
          ,col.name       AS COLUMN_NAME
          ,col.column_id  AS ORDINAL_POSITION
          ,typ.name       AS COLUMN_TYPE
          ,col.max_length AS MAX_LENGTH
          ,col.column_id
          ,col.precision
          ,col.scale
          ,col.collation_name
          ,col.is_nullable
          ,col.is_rowguidcol
          ,col.is_identity
          ,col.is_computed
          ,idx.name       AS index_name
          ,idx.type       AS index_type
          ,idx.type_desc  AS index_description
          ,idx.is_unique
          ,idx.data_space_id
          ,idx.ignore_dup_key
          ,idx.is_primary_key
          ,idx.is_unique_constraint
          ,idx.fill_factor
          ,idx.is_padded
          ,idx.is_disabled
          ,idx.is_hypothetical
          ,idx.allow_row_locks
          ,idx.allow_page_locks
          ,idx.has_filter
          ,idx.filter_definition
          ,idx.compression_delay
          ,ixc.key_ordinal
          ,ixc.partition_ordinal
          ,ixc.is_descending_key
          ,ixc.is_included_column
      FROM RLDXDth.sys.schemas sch
      JOIN RLDXDth.sys.tables tbl
        ON sch.schema_id = tbl.schema_id
      JOIN RLDXDth.sys.columns col
        ON col.object_id = tbl.object_id
      JOIN RLDXDth.sys.types typ
        ON typ.system_type_id = col.system_type_id
      LEFT JOIN RLDXDth.sys.index_columns ixc
        ON col.object_id = ixc.object_id
        AND col.column_id = ixc.column_id
      LEFT JOIN RLDXDth.sys.indexes idx
        ON ixc.object_id = idx.object_id
        AND ixc.index_id = idx.index_id
),
cteRLDXED
AS (
    SELECT 'RLDXED'      AS TABLE_CATALOG
          ,sch.name       AS TABLE_SCHEMA
          ,tbl.name       AS TABLE_NAME
          ,col.name       AS COLUMN_NAME
          ,col.column_id  AS ORDINAL_POSITION
          ,typ.name       AS COLUMN_TYPE
          ,col.max_length AS MAX_LENGTH
          ,col.column_id
          ,col.precision
          ,col.scale
          ,col.collation_name
          ,col.is_nullable
          ,col.is_rowguidcol
          ,col.is_identity
          ,col.is_computed
          ,idx.name       AS index_name
          ,idx.type       AS index_type
          ,idx.type_desc  AS index_description
          ,idx.is_unique
          ,idx.data_space_id
          ,idx.ignore_dup_key
          ,idx.is_primary_key
          ,idx.is_unique_constraint
          ,idx.fill_factor
          ,idx.is_padded
          ,idx.is_disabled
          ,idx.is_hypothetical
          ,idx.allow_row_locks
          ,idx.allow_page_locks
          ,idx.has_filter
          ,idx.filter_definition
          ,idx.compression_delay
          ,ixc.key_ordinal
          ,ixc.partition_ordinal
          ,ixc.is_descending_key
          ,ixc.is_included_column
      FROM RLDXED.sys.schemas sch
      JOIN RLDXED.sys.tables tbl
        ON sch.schema_id = tbl.schema_id
      JOIN RLDXED.sys.columns col
        ON col.object_id = tbl.object_id
      JOIN RLDXED.sys.types typ
        ON typ.system_type_id = col.system_type_id
      LEFT JOIN RLDXED.sys.index_columns ixc
        ON col.object_id = ixc.object_id
        AND col.column_id = ixc.column_id
      LEFT JOIN RLDXED.sys.indexes idx
        ON ixc.object_id = idx.object_id
        AND ixc.index_id = idx.index_id
),
cteRLDXHosp
AS (
    SELECT 'RLDXHosp'    AS TABLE_CATALOG
          ,sch.name       AS TABLE_SCHEMA
          ,tbl.name       AS TABLE_NAME
          ,col.name       AS COLUMN_NAME
          ,col.column_id  AS ORDINAL_POSITION
          ,typ.name       AS COLUMN_TYPE
          ,col.max_length AS MAX_LENGTH
          ,col.column_id
          ,col.precision
          ,col.scale
          ,col.collation_name
          ,col.is_nullable
          ,col.is_rowguidcol
          ,col.is_identity
          ,col.is_computed
          ,idx.name       AS index_name
          ,idx.type       AS index_type
          ,idx.type_desc  AS index_description
          ,idx.is_unique
          ,idx.data_space_id
          ,idx.ignore_dup_key
          ,idx.is_primary_key
          ,idx.is_unique_constraint
          ,idx.fill_factor
          ,idx.is_padded
          ,idx.is_disabled
          ,idx.is_hypothetical
          ,idx.allow_row_locks
          ,idx.allow_page_locks
          ,idx.has_filter
          ,idx.filter_definition
          ,idx.compression_delay
          ,ixc.key_ordinal
          ,ixc.partition_ordinal
          ,ixc.is_descending_key
          ,ixc.is_included_column
      FROM RLDXHosp.sys.schemas sch
      JOIN RLDXHosp.sys.tables tbl
        ON sch.schema_id = tbl.schema_id
      JOIN RLDXHosp.sys.columns col
        ON col.object_id = tbl.object_id
      JOIN RLDXHosp.sys.types typ
        ON typ.system_type_id = col.system_type_id
      LEFT JOIN RLDXHosp.sys.index_columns ixc
        ON col.object_id = ixc.object_id
        AND col.column_id = ixc.column_id
      LEFT JOIN RLDXHosp.sys.indexes idx
        ON ixc.object_id = idx.object_id
        AND ixc.index_id = idx.index_id
),
cteCaped
AS (
    SELECT 'caped'       AS TABLE_CATALOG
          ,sch.name       AS TABLE_SCHEMA
          ,tbl.name       AS TABLE_NAME
          ,col.name       AS COLUMN_NAME
          ,col.column_id  AS ORDINAL_POSITION
          ,typ.name       AS COLUMN_TYPE
          ,col.max_length AS MAX_LENGTH
          ,col.column_id
          ,col.precision
          ,col.scale
          ,col.collation_name
          ,col.is_nullable
          ,col.is_rowguidcol
          ,col.is_identity
          ,col.is_computed
          ,idx.name       AS index_name
          ,idx.type       AS index_type
          ,idx.type_desc  AS index_description
          ,idx.is_unique
          ,idx.data_space_id
          ,idx.ignore_dup_key
          ,idx.is_primary_key
          ,idx.is_unique_constraint
          ,idx.fill_factor
          ,idx.is_padded
          ,idx.is_disabled
          ,idx.is_hypothetical
          ,idx.allow_row_locks
          ,idx.allow_page_locks
          ,idx.has_filter
          ,idx.filter_definition
          ,idx.compression_delay
          ,ixc.key_ordinal
          ,ixc.partition_ordinal
          ,ixc.is_descending_key
          ,ixc.is_included_column
      FROM caped.sys.schemas sch
      JOIN caped.sys.tables tbl
        ON sch.schema_id = tbl.schema_id
      JOIN caped.sys.columns col
        ON col.object_id = tbl.object_id
      JOIN caped.sys.types typ
        ON typ.system_type_id = col.system_type_id
      LEFT JOIN caped.sys.index_columns ixc
        ON col.object_id = ixc.object_id
        AND col.column_id = ixc.column_id
      LEFT JOIN caped.sys.indexes idx
        ON ixc.object_id = idx.object_id
        AND ixc.index_id = idx.index_id
),
cteHierep
AS (
    SELECT 'hierep'      AS TABLE_CATALOG
          ,sch.name       AS TABLE_SCHEMA
          ,tbl.name       AS TABLE_NAME
          ,col.name       AS COLUMN_NAME
          ,col.column_id  AS ORDINAL_POSITION
          ,typ.name       AS COLUMN_TYPE
          ,col.max_length AS MAX_LENGTH
          ,col.column_id
          ,col.precision
          ,col.scale
          ,col.collation_name
          ,col.is_nullable
          ,col.is_rowguidcol
          ,col.is_identity
          ,col.is_computed
          ,idx.name       AS index_name
          ,idx.type       AS index_type
          ,idx.type_desc  AS index_description
          ,idx.is_unique
          ,idx.data_space_id
          ,idx.ignore_dup_key
          ,idx.is_primary_key
          ,idx.is_unique_constraint
          ,idx.fill_factor
          ,idx.is_padded
          ,idx.is_disabled
          ,idx.is_hypothetical
          ,idx.allow_row_locks
          ,idx.allow_page_locks
          ,idx.has_filter
          ,idx.filter_definition
          ,idx.compression_delay
          ,ixc.key_ordinal
          ,ixc.partition_ordinal
          ,ixc.is_descending_key
          ,ixc.is_included_column
      FROM hierep.sys.schemas sch
      JOIN hierep.sys.tables tbl
        ON sch.schema_id = tbl.schema_id
      JOIN hierep.sys.columns col
        ON col.object_id = tbl.object_id
      JOIN hierep.sys.types typ
        ON typ.system_type_id = col.system_type_id
      LEFT JOIN hierep.sys.index_columns ixc
        ON col.object_id = ixc.object_id
        AND col.column_id = ixc.column_id
      LEFT JOIN hierep.sys.indexes idx
        ON ixc.object_id = idx.object_id
        AND ixc.index_id = idx.index_id
),
cteUnion
AS (
    SELECT *
      FROM cteDeaths
    UNION ALL
    SELECT *
      FROM cteRLDXDth
    UNION ALL
    SELECT *
      FROM cteRLDXED
    UNION ALL
    SELECT *
      FROM cteRLDXHosp
    UNION ALL
    SELECT *
      FROM cteCaped
    UNION ALL
    SELECT *
      FROM cteHierep
)
SELECT TOP 999999999999999999 *
  FROM cteUnion
ORDER BY
       TABLE_CATALOG
      ,TABLE_SCHEMA
      ,TABLE_NAME
      ,ORDINAL_POSITION
GO

I do wish SQL Server provided such an overarching view out-of-the-box, which was the gist of my original question(s).

Finally, I wrote a code generator to generate the views I require:

(See http://www.sqlservercentral.com/scripts/T-SQL/117890/ for PatExclude8K):

CREATE PROCEDURE [dbo].[spGenerateCleanViews2]
    @srcTableName SYSNAME  -- Must be a 3-level table name, case-insensitive
   ,@tgtTableName SYSNAME = 'cln.vw{srcTable}'
   ,@debug BIT = 1
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    -- Declare variables
    DECLARE @srcDatabase SYSNAME
           ,@srcSchema SYSNAME
           ,@srcTable SYSNAME
           ,@tgtDatabase SYSNAME
           ,@tgtSchema SYSNAME
           ,@tgtTable SYSNAME
    ;

    DECLARE @columnName SYSNAME
           ,@columnType SYSNAME
           ,@sql NVARCHAR(MAX) = ''
           ,@DBExec NVARCHAR(100)
           ,@line NVARCHAR(256)
           ,@n INT
           ,@pattern VARCHAR(100) = '%[^ -~]%'  -- Low order ASCII print characters CHAR(32) - CHAR(126)
    ;

    -- Parse source table
    SET @srcTable = PARSENAME(@srcTableName,1);
    SET @srcSchema = PARSENAME(@srcTableName,2);
    SET @srcDatabase = PARSENAME(@srcTableName,3);

    -- Don't try to set defaults - if it's not a three level name then abort
    IF @srcTable IS NULL
        OR @srcSchema IS NULL
        OR @srcDatabase IS NULL
    BEGIN
        RAISERROR ('A three-level table name (server.schema.table) is required.',16,1);
        RETURN;
    END

    -- Parse target table
    SET @tgtTable = PARSENAME(@tgtTableName,1);
    SET @tgtSchema = PARSENAME(@tgtTableName,2);
    SET @tgtDatabase = PARSENAME(@tgtTableName,3);

    -- Set defaults if NULL
    IF @tgtDatabase IS NULL
        SET @tgtDatabase = DB_NAME()

    IF @tgtSchema IS NULL
        SET @tgtSchema = 'cln'

    IF @tgtTable IS NULL
        SET @tgtTable = 'vw{srcTable}'

    -- Replace tokens in the target table name
    SET @tgtTable = REPLACE(@tgtTable,'{srcTable}',@srcTable);

    -- Create scrollable cursor
    BEGIN TRY
        DECLARE cursorColumns SCROLL CURSOR FOR SELECT COLUMN_NAME
                                                      ,DATA_TYPE
                                                      ,ORDINAL_POSITION
          FROM util.vwGetColumnInfoISC
         WHERE TABLE_CATALOG = @srcDatabase COLLATE Latin1_General_100_CI_AI
           AND TABLE_SCHEMA = @srcSchema COLLATE Latin1_General_100_CI_AI
           AND TABLE_NAME = @srcTable COLLATE Latin1_General_100_CI_AI
        ORDER BY
               ORDINAL_POSITION
    END TRY
    BEGIN CATCH
        DEALLOCATE cursorColumns;
        DECLARE cursorColumns SCROLL CURSOR FOR SELECT COLUMN_NAME
                                                      ,DATA_TYPE
                                                      ,ORDINAL_POSITION
          FROM util.vwGetColumnInfoISC
         WHERE TABLE_CATALOG = @srcDatabase COLLATE Latin1_General_100_CI_AI
           AND TABLE_SCHEMA = @srcSchema COLLATE Latin1_General_100_CI_AI
           AND TABLE_NAME = @srcTable COLLATE Latin1_General_100_CI_AI
        ORDER BY
               ORDINAL_POSITION
    END CATCH

    OPEN cursorColumns;

    FETCH FIRST FROM cursorColumns INTO @columnName,@columnType,@n;
    WHILE @@fetch_status = 0
    BEGIN
    SET @line = REPLICATE(' ',256);
    IF @columnType LIKE '%char'
    BEGIN
        SET @line = STUFF(@line,7,50,',LTRIM(RTRIM([t{alias}].[NewString]))');  -- strip leading and trailing spaces
        SET @line = REPLACE(@line,'{alias}',FORMAT(@n,'00'));
        SET @line = STUFF(@line,100,50,'AS {columnName}');
        SET @line = REPLACE(@line,'{columnName}',QUOTENAME(@columnName,'['))
    END
    ELSE
    BEGIN
        SET @line = STUFF(@line,7,50,',[src].{columnName}');
        SET @line = REPLACE(@line,'{columnName}',QUOTENAME(@columnName,'['))
        SET @line = STUFF(@line,100,50,'AS {columnName}');
        SET @line = REPLACE(@line,'{columnName}',QUOTENAME(@columnName,'['))
    END
    SET @sql += RTRIM(@line) + CHAR(13);
    FETCH NEXT FROM cursorColumns INTO @columnName,@columnType,@n;
    END

    -- Source Table
    -- Note: If the source table is in a different database than the target database then a synonym must be created!
    SET @line = REPLICATE(' ',256);
    SET @line = STUFF(@line,3,50,'FROM {srcSchema}.{srcTable} src');
    SET @sql += RTRIM(@line) + CHAR(13);

    -- Cross Apply
    FETCH FIRST FROM cursorColumns INTO @columnName,@columnType,@n;
    WHILE @@fetch_status = 0
    BEGIN
    SET @line = REPLICATE(' ',256);
    IF @columnType LIKE '%char'
    BEGIN
        SET @line = STUFF(@line,3,60,'CROSS APPLY dbo.fnPatExclude8K_Table([src].{columnName},''{pattern}'') t{alias}');
        SET @line = REPLACE(REPLACE(REPLACE(@line,'{columnName}',QUOTENAME(@columnName,'[')),'{alias}',FORMAT(@n,'00')),'{pattern}',@pattern);
        SET @sql += RTRIM(@line) + CHAR(13);
    END
    FETCH NEXT FROM cursorColumns INTO @columnName,@columnType,@n;
    END

    CLOSE cursorColumns;
    DEALLOCATE cursorColumns;

    SET @sql = STUFF(@sql,1,7,'SELECT ');

    -- Do not indent the following code block
    SET @sql =
'CREATE OR ALTER VIEW {tgtSchema}.{tgtTable}
AS
' + @sql
;

    SET @sql = REPLACE(@sql,'{srcSchema}',QUOTENAME(@srcSchema,'['));
    SET @sql = REPLACE(@sql,'{srcTable}',QUOTENAME(@srcTable,'['));
    SET @sql = REPLACE(@sql,'{tgtDatabase}',QUOTENAME(@tgtDatabase,'['));
    SET @sql = REPLACE(@sql,'{tgtSchema}',QUOTENAME(@tgtSchema,'['));
    SET @sql = REPLACE(@sql,'{tgtTable}',QUOTENAME(@tgtTable,'['));

    -- If debugging display generated code
    -- XML is used to overcome the 4000 character limit of PRINT
    IF @debug = 1
        SELECT CAST('<![CDATA[' + @sql + ']]>' AS XML);
    ELSE
    BEGIN
        -- Set execution context
        SET @DBExec = @tgtDatabase + N'.sys.sp_executesql';
        EXEC @DBExec @sql;
    END
END
GO

Solution 2:[2]

  1. For all databases: select * from sys.databases
  2. For all tables: Select * from INFORMATION_SCHEMA.TABLES Where TABLE_TYPE ='BASE TABLE'
  3. From all Views Select * from INFORMATION_SCHEMA.TABLES Where TABLE_TYPE ='VIEW'
  4. Fro all columns: Select * from INFORMATION_SCHEMA.COLUMNS please use table_name as filter.
  5. In the INFORMATION_SCHEMA.COLUMNS table you will get the DATA_TYPE for column type

First load the databases from the SQL server database, then get the database names and connect to that database, after that use all queries above to get what you need.

Solution 3:[3]

I have created a stored procedure that can be return the column info if you pass the database name.

Create PROCEDURE SP_GetColumnInfo   
    @DatabaseName varchar(max)
AS
BEGIN   
    SET NOCOUNT ON;
    EXEC('USE ' + @DatabaseName + '; Select DatabaseName = '''+ @DatabaseName +''', Table_Schema, Table_Name, Column_Name, Data_Type from INFORMATION_SCHEMA.COLUMNS as ColumnNames');     
END
GO

Solution 4:[4]

This is an answer to your third question and next approach generates one dynamic T-SQL statement, that returns your expected result. You may try to execute it directly or create a cursor.

-- Declarations
DECLARE @stm nvarchar(max)
SET @stm = N''

-- Dynamic SQL
SELECT @stm = (
   SELECT CONCAT(
        N'UNION ALL ',
        N'SELECT N''',
        name,
        N''' AS DATABASE_NAME, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE '+
        N'FROM ', 
        QUOTENAME([name]), 
        N'.INFORMATION_SCHEMA.COLUMNS '
    )
   FROM sys.databases
   WHERE [name] NOT IN ('master', 'model', 'msdb', 'tempdb')
   FOR XML PATH('')
)
SET @stm = STUFF(@stm, 1, 10, N'')
PRINT @stm

-- Execution without cursor
EXEC sp_executesql @stm

-- Execution with cursor
SET @stm = CONCAT(
    N'DECLARE info CURSOR FORWARD_ONLY READ_ONLY FOR ', 
    @stm
)
EXEC sp_executesql @stm
OPEN info
-- Fetch operations
CLOSE info
DEALLOCATE info

Solution 5:[5]

I was having a similar issue to you. Here is what I ended up with, though in a perfect world I'd like to add row_count per table and column data_type. The answer to adding db to schema, table, column info is the function DB_NAME()

select 
s.name schema_name_,
t.name table_name,
c.name col_name_,
idx.name index_type,
DB_NAME()
from sys.schemas s
join sys.tables t on s.schema_id = t.schema_id
join sys.columns c on t.object_id = c.object_id
left join sys.index_columns ic on c.object_id = ic.object_id and c.column_id = ic.column_id
left join sys.indexes idx on ic.index_id = idx.index_id and ic.object_id = idx.object_id
; 

Solution 6:[6]

The question as asked makes no reference to any prior efforts, no sample sql that did not work. What is missing is any sharing of that effort, or describing what is the problem or what error messages are being received.

Take a look at the sixth (or so) item returned from the google

  sqlserver get database, schema, table, column, column type for all databases 

https://www.red-gate.com/simple-talk/sql/database-administration/exploring-your-sql-server-databases-with-t-sql/

Look at the section sp_msforeachtable where they cite function EXEC sp_MSforeachdb '...'

and the section Exploring your Data Model in Depth, with Listing 23: Exploring columns and their data types.

Tailor these resources to your question as asked.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Scott
Solution 2 Hasan Mahmood
Solution 3 Prahalad Gaggar
Solution 4
Solution 5 Dale K
Solution 6 donPablo