sp_MSForEachDB Invalid Use of Side-Effecting Operator Within Function

落花浮王杯 提交于 2019-12-10 23:36:42

问题


Is there anyway to put the below code into a Table-Valued (inline or multi-statement) or even a View. I'm able to use it in a stored procedure but do to the inability to do joins to the results of stored procedures I'd rather put it in a Table-Valued function or View.

Below is the SQL that gives me the results I'm looking for

DECLARE @ColumnInformation TABLE
(
    DatabaseName NVARCHAR(255),
    TableSchema NVARCHAR(255),
    TableName NVARCHAR(255),
    ColumnName NVARCHAR(255),
    TableType NVARCHAR(255),
    FullyQualifiedTableName NVARCHAR(255),
    FullyQualifiedColumnName NVARCHAR(255)
)
INSERT INTO @ColumnInformation
EXECUTE master.sys.sp_MSForEachDB  '
IF ''?'' NOT IN (''master'', ''tempdb'', ''msdb'', ''model'', ''ReportServer'', ''ReportServerTempDB'' )
BEGIN
    USE [?];
    PRINT ''?''
    SELECT 
         T.TABLE_CATALOG AS DatabaseName
        ,T.TABLE_SCHEMA as TableSchema  
        ,T.TABLE_NAME AS TableName
        ,C.COLUMN_NAME AS ColumnName
        ,T.TABLE_TYPE AS TableType
        ,''['' + T.TABLE_CATALOG + ''].['' + T.TABLE_SCHEMA + ''].['' + T.TABLE_NAME + '']'' AS FullyQualifiedTableName
        ,''['' + T.TABLE_CATALOG + ''].['' + T.TABLE_SCHEMA + ''].['' + T.TABLE_NAME + ''].['' + C.COLUMN_NAME + '']'' FullyQualifiedColumnName
    FROM INFORMATION_SCHEMA.TABLES as t inner join
        INFORMATION_SCHEMA.COLUMNS as c on c.table_name = t.table_name
END
'

SELECT * 
FROM @ColumnInformation
ORDER BY DatabaseName, TableSchema, TableName, ColumnName

however when I try to put it in a table valued function I'm getting the error

Msg 443, Level 16, State 14, Procedure fGetAllColumnInformation, Line 17
Invalid use of a side-effecting operator 'INSERT EXEC' within a function.

I believe that sp_MSForEachDb is generating dynamic SQL but not real sure. I vaguely remember hearing that dynamic SQL can not be used in table-valued functions. If this is the case is there anyway of bypassing that restriction so that I can use the above code in a table-valued function or view.

If the answer to the above is no...is there any way of rewriting the statement so that it does not use dynamic SQL?

I'm basically trying to union the results of all of the INFORMATION_SCHEMA.COLUMNS from every database on a SQL Server.


回答1:


You can't do this dynamically (as in without hard-coding all the database names in advance) without dynamic SQL, and you can't use dynamic SQL in a function. You shouldn't be using sp_MSForEachDB in any case or, IMHO, using INFORMATION_SCHEMA.

Write a stored procedure that returns the result set. If you absolutely need to join the output to other stuff (why not code that into the stored procedure?), then insert the output into a #temp table.

Try this:

CREATE PROCEDURE dbo.AllMyColumnsEverywhereForReals
AS
BEGIN
  SET NOCOUNT ON;

  DECLARE @sql NVARCHAR(MAX) = N'';

  SELECT @sql += '
  UNION ALL SELECT 
      [database]  = N''' + d.name + ''' COLLATE SQL_Latin1_General_CP1_CI_AI,
      [schema]    = s.name COLLATE SQL_Latin1_General_CP1_CI_AI,
      [object]    = o.name COLLATE SQL_Latin1_General_CP1_CI_AI,
      [column]    = c.name COLLATE SQL_Latin1_General_CP1_CI_AI,
      [qualified] = QUOTENAME(''' + d.name + ''') 
        + ''.'' + QUOTENAME(s.name) 
        + ''.'' + QUOTENAME(c.name) COLLATE SQL_Latin1_General_CP1_CI_AS,
      [type] = CASE o.type WHEN ''U'' THEN ''Table'' ELSE ''View'' END
    FROM ' + QUOTENAME(d.name) + '.sys.columns AS c
      INNER JOIN ' + QUOTENAME(d.name) + '.sys.objects AS o
      ON c.[object_id] = o.[object_id]
      INNER JOIN ' + QUOTENAME(d.name) + '.sys.schemas AS s
      ON o.[schema_id] = s.[schema_id]
      WHERE o.type IN (''U'', ''V'')'
  FROM sys.databases AS d WHERE [state] = 0 AND name NOT IN 
    (N'master',N'tempdb',N'msdb',N'model',N'ReportServer',N'ReportServerTempDB');

  SET @sql = STUFF(@sql, 1, 13, '');

  EXEC sp_executesql @sql;
END
GO

Usage:

CREATE TABLE #x
(
  db     SYSNAME, 
  sch    SYSNAME, 
  obj    SYSNAME, 
  col    SYSNAME, 
  qual   NVARCHAR(390),
  [type] CHAR(5)
);

INSERT #x EXEC dbo.AllMyColumnsEverywhereForReals;

SELECT cols FROM #x AS x -- INNER JOIN something else ON x.whatever...


来源:https://stackoverflow.com/questions/17840506/sp-msforeachdb-invalid-use-of-side-effecting-operator-within-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!