Blog Post

Scripting dependencies of User-defined Table Types

,

I was using user-defined tables types (UDTT) quite intensively despite of their current disadvantages especially their forced READONLY behavior when used as stored procedures parameter. Another disadvantage which I didn’t realize impact for is that UDTT cannot be altered. I hit maintenance nightmare when they became spread across my T-SQL code.

What I did is that I encapsulated  few business objects as UDTT and started to use them consistently in my T-SQL stored procedures and functions (directly in code and as input parameters as well). It works great, your code looks “strongly-typed” and it is definitely big step forward in T-SQL programming. But whenever I wanted to add or alter field in my UDTT, I had to drop/create all my procedures or functions which this UDTT depends on. It looks peaceful but it’s totally annoying because you have no built-in option to script references for UDTT and you have to do it manually or through “script database” task. If you are in development phase when everything changes from minute to minute, it’s unusable.

I wrote helper stored procedures which take UDTT name as only parameter and returns DROP and CREATE scripts of all references. It basically do what I’ve mentioned, it scripts all UDTT’s references and saves my time.

First is ap_FindReferences which finds all references and returns them as single result set. It would be enough to display this result set as text in SSMS (CTRL+T) but it’s not because SSMS cannot display strings long more than few thousands characters in results pane. That’s why there is second procedure ap_WriteReferences which do quite nasty job of writing result from ap_FindReferences into .sql file you can display in SSMS.

Usage is following:

EXEC ap_WriteReferences '<your user-defined table type>'

…result is text file defined within ap_WriteReferences with scripted references to your UDDT.

Good option is to bind this command to keyboard shortcut in SSMS.

Here are those two procedures (some notes follow):

-- Find all referencing objects to user-defined table type in @fullObjectName parameter
-- and generate DROP scripts and CREATE scripts for them
CREATE PROC ap_FindReferences (@fullObjectName VARCHAR(200))
AS
BEGIN
    SET NOCOUNT ON
    IF (TYPE_ID (@fullObjectName) IS NULL)
    BEGIN
        RAISERROR ('User-defined table type ''%s'' does not exists. Include full object name with schema.', 16,1, @fullObjectName)
        RETURN
    END;
    WITH sources
    AS
    (
        SELECT ROW_NUMBER() OVER (ORDER BY OBJECT_NAME(m.object_id)) RowId, definition
        FROM sys.sql_expression_dependencies d
        JOIN sys.sql_modules m ON m.object_id = d.referencing_id
        JOIN sys.objects o ON o.object_id = m.object_id
        WHERE referenced_id = TYPE_ID(@fullObjectName)
    )
    SELECT 
        'DROP ' +
            CASE OBJECTPROPERTY(referencing_id, 'IsProcedure')
            WHEN 1 THEN 'PROC '
            ELSE
                CASE
                    WHEN OBJECTPROPERTY(referencing_id, 'IsScalarFunction') = 1 OR OBJECTPROPERTY(referencing_id, 'IsTableFunction') = 1 OR OBJECTPROPERTY(referencing_id, 'IsInlineFunction') = 1 THEN 'FUNCTION '
                    ELSE ''
                END
            END
        + SCHEMA_NAME(o.schema_id) + '.' +
        + OBJECT_NAME(m.object_id)    
    FROM sys.sql_expression_dependencies d
    JOIN sys.sql_modules m ON m.object_id = d.referencing_id
    JOIN sys.objects o ON o.object_id = m.object_id
    WHERE referenced_id = TYPE_ID(@fullObjectName)
    UNION  ALL
    SELECT  'GO'
    UNION  ALL
    SELECT
        CASE
            WHEN number = RowId    THEN DEFINITION
            ELSE 'GO'
        END
     FROM sources s
    JOIN (SELECT DISTINCT number FROM master.dbo.spt_values) n ON n.number BETWEEN RowId AND RowId+1
END
GO
-- Invokes ap_FindReferences procedure and writes scripted result to .sql file 
CREATE PROC ap_WriteReferences
@typeToFind VARCHAR(200)
AS
BEGIN
    DECLARE @sqlCmd VARCHAR(500)
    DECLARE @database VARCHAR(200) = 'test'
    DECLARE @outputFile VARCHAR(500) = 'c:\refences.sql'
    SET @sqlCmd = 'sqlcmd.exe -d '+@database+' -q "EXEC ap_FindReferences '''+ @typeToFind +'''" -o '+ @outputFile +' -h-1 -y0'
    EXEC xp_cmdshell @sqlCmd
END

Please pay attention to these notes if you are going to use it:

  • you must change your database name and file name in ap_WriteReferences procedure
  • it uses sys.sql_modules view to find references, hence it may not have most current information. It worked for me so far but still…
  • please use it carefully, it drops objects and it is intended to dedicated development databases only (and at your own risk :) )
  • it works only on SQL Server 2008 and higher
  • extended properties are not scripted, it will be forgotten related objects are dropped

If you know any better way how to avoid this maintenance issue with UDDT, surely let me know! Also let me know here if you want me to include it as feature to my “pocket” SSMS productivity add-in.

 

Rate

You rated this post out of 5. Change rating

Share

Share

Rate

You rated this post out of 5. Change rating