views:

227

answers:

3

Is it possible, without parsing source, to select a list of all sproc names that insert, update, or delete records? I need to create a TSQL utility script that will do this. Efficiency is not an issue because it will be run only a few times a year (Curse'rs I mean Cursors are ok). Ideally this script would not include updates to temp or local variable tables.

I tried the following query found on SO Question.

SELECT 
 so.name,
 so2.name,
 sd.is_updated
 from sysobjects so
 inner join sys.sql_dependencies sd on so.id = sd.object_id
 inner join sysobjects so2 on sd.referenced_major_id = so2.id
where so.xtype = 'p' -- procedure
 and 
is_updated = 1 -- proc updates table, or at least, I think that's what this means

But it produces false negatives.

+1  A: 

That is the problem with sys.sql_dependencies. SQL cannot accurately track dependencies in stored procedures (there are sound reasons why it can't, but lets no go there now). That is without even considering dynamic SQL or CLR procedures.

Visual Studio Database Edition has some better capabilities, but it can track dependencies in scipts, not in a live database. It can though reverse engineer live databases into scripts and analyse the resulted scripts, to some better accuracy than sys.sql_dependencies can. It cannot handle dynamic SQL.

Remus Rusanu
+3  A: 

Call sp_refreshsqlmodule on all non-schema bound stored procedures:

DECLARE @template AS varchar(max)
SET @template = 'PRINT ''{OBJECT_NAME}''
EXEC sp_refreshsqlmodule ''{OBJECT_NAME}''

'

DECLARE @sql AS varchar(max)

SELECT  @sql = ISNULL(@sql, '') + REPLACE(@template, '{OBJECT_NAME}',
                                          QUOTENAME(ROUTINE_SCHEMA) + '.'
                                          + QUOTENAME(ROUTINE_NAME))
FROM    INFORMATION_SCHEMA.ROUTINES
WHERE   OBJECTPROPERTY(OBJECT_ID(QUOTENAME(ROUTINE_SCHEMA) + '.'
                                 + QUOTENAME(ROUTINE_NAME)),
                       N'IsSchemaBound') IS NULL
        OR OBJECTPROPERTY(OBJECT_ID(QUOTENAME(ROUTINE_SCHEMA) + '.'
                                    + QUOTENAME(ROUTINE_NAME)),
                          N'IsSchemaBound') = 0

        EXEC (
              @sql
            )
Cade Roux
Thanks running this gets me closer. There were still some false negatives but just a few. I was able to use my original code along with yours to get close enough. I posted my code as an answer.
Tim Santeford
Did you find any SPs which had become invalid because of an underlying change? That's always fun ;-)
Cade Roux
A: 

Thanks to Cade Roux's answer i was able to get very close with this:

DECLARE @RoleName nvarchar(255)
SET @RoleName = 'READONLYUSER'

DECLARE @ROUTINE_NAME nvarchar(255)
DECLARE RoutineList Cursor FOR
SELECT ROUTINE_NAME
FROM    INFORMATION_SCHEMA.ROUTINES
WHERE   (OBJECTPROPERTY(OBJECT_ID(QUOTENAME(ROUTINE_SCHEMA) + '.' + QUOTENAME(ROUTINE_NAME)), N'IsSchemaBound') IS NULL OR OBJECTPROPERTY(OBJECT_ID(QUOTENAME(ROUTINE_SCHEMA) + '.' + QUOTENAME(ROUTINE_NAME)), N'IsSchemaBound') = 0)
    AND NOT ROUTINE_NAME like 'sel%'
    AND NOT ROUTINE_NAME like 'sp_upd%'
    AND NOT ROUTINE_NAME like 'sp_sel%'
    AND NOT ROUTINE_NAME like 'sp_ins%' 
OPEN RoutineList
FETCH NEXT FROM RoutineList 
INTO @ROUTINE_NAME
WHILE @@FETCH_STATUS = 0
BEGIN

    EXEC ('EXEC sp_refreshsqlmodule ''' + @ROUTINE_NAME + '''')

    FETCH NEXT FROM RoutineList INTO @ROUTINE_NAME
END
CLOSE RoutineList
DEALLOCATE RoutineList


DECLARE GrantList Cursor FOR
SELECT DISTINCT
    so.name AS ROUTINE_NAME
FROM sysobjects so
LEFT JOIN (
    SELECT 
     so.name,
     so2.name AS [table],
     sd.is_updated
     FROM sysobjects so
     INNER JOIN sys.sql_dependencies sd ON so.id = sd.object_id
     INNER JOIN sysobjects so2 ON sd.referenced_major_id = so2.id
     WHERE so.xtype = 'p'
     and is_updated = 1
) Updates ON so.name = Updates.name
WHERE 
    so.xtype = 'p' -- procedure
    AND Updates.name is null
    AND so.name NOT LIKE '%[_]%'
ORDER BY so.name

OPEN GrantList
FETCH NEXT FROM GrantList 
INTO @ROUTINE_NAME
WHILE @@FETCH_STATUS = 0
BEGIN

    print 'GRANT EXECUTE ON [dbo].['+@ROUTINE_NAME+'] TO ['+@RoleName +'] '  


    FETCH NEXT FROM GrantList INTO @ROUTINE_NAME
END
CLOSE GrantList
DEALLOCATE GrantList
Tim Santeford