MSSQL GenerateDatabaseScript

Creates an easily comparable database schema script.
Includes tables, views, constraints, indexes, stored procedures, functions, and triggers. Ignores creation date of objects to avoid false positives.

This stored procedure is useful for:

  • Creating database documentation
  • Comparing database schemas between environments
  • Version control of database structure
  • Database migration scripts
  • Backup and restore procedures

There are other tools that do the same, but this is for environments and situations where you cannot install tooling. It also creates a consistent output.

Install in database

CREATE OR ALTER PROCEDURE [dbo].[GenerateDatabaseScript]
AS
BEGIN
    SET NOCOUNT ON;
    
    -- Print header
    PRINT '-- =============================================';
    PRINT '-- Database Schema Scripts';
    PRINT '-- Generated: ' + CONVERT(VARCHAR, GETDATE(), 120);
    PRINT '-- Server: ' + @@SERVERNAME;
    PRINT '-- Database: ' + DB_NAME();
    PRINT '-- =============================================';
    PRINT '';
    
    -- =============================================
    -- Generate CREATE scripts for TABLES
    -- =============================================
    PRINT '-- =============================================';
    PRINT '-- TABLES';
    PRINT '-- =============================================';
    PRINT '';
    
    DECLARE @TableName NVARCHAR(128);
    DECLARE @Schema NVARCHAR(128);
    DECLARE @TableScript NVARCHAR(MAX);
    
    DECLARE table_cursor CURSOR FOR
    SELECT s.name, t.name
    FROM sys.tables t
    INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
    ORDER BY s.name, t.name;
    
    OPEN table_cursor;
    FETCH NEXT FROM table_cursor INTO @Schema, @TableName;
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
        -- Generate table script
        SET @TableScript = 'CREATE TABLE [' + @Schema + '].[' + @TableName + '] (' + CHAR(13) + CHAR(10);
        
        -- Add columns
        SELECT @TableScript = @TableScript + 
            '    [' + c.name + '] ' + 
            CASE 
                WHEN c.is_computed = 1 THEN 'AS ' + OBJECT_DEFINITION(c.object_id, c.column_id)
                ELSE 
                    tp.name + 
                    CASE 
                        WHEN tp.name IN ('varchar', 'char', 'nvarchar', 'nchar') 
                            THEN '(' + CASE WHEN c.max_length = -1 THEN 'MAX' ELSE CAST(c.max_length AS VARCHAR) END + ')'
                        WHEN tp.name IN ('decimal', 'numeric') 
                            THEN '(' + CAST(c.precision AS VARCHAR) + ',' + CAST(c.scale AS VARCHAR) + ')'
                        ELSE ''
                    END +
                    CASE WHEN c.is_nullable = 1 THEN ' NULL' ELSE ' NOT NULL' END +
                    CASE WHEN dc.definition IS NOT NULL THEN ' DEFAULT ' + dc.definition ELSE '' END
            END + ',' + CHAR(13) + CHAR(10)
        FROM sys.columns c
        INNER JOIN sys.types tp ON c.user_type_id = tp.user_type_id
        LEFT JOIN sys.default_constraints dc ON c.default_object_id = dc.object_id
        WHERE c.object_id = OBJECT_ID('[' + @Schema + '].[' + @TableName + ']')
        ORDER BY c.column_id;
        
        -- Remove trailing comma and add closing parenthesis
        SET @TableScript = LEFT(@TableScript, LEN(@TableScript) - 3) + CHAR(13) + CHAR(10) + ')';
        
        PRINT @TableScript + ' GO';
        PRINT '';
        
        FETCH NEXT FROM table_cursor INTO @Schema, @TableName;
    END
    
    CLOSE table_cursor;
    DEALLOCATE table_cursor;
    
    -- =============================================
    -- Generate CREATE scripts for VIEWS
    -- =============================================
    PRINT '-- =============================================';
    PRINT '-- VIEWS';
    PRINT '-- =============================================';
    PRINT '';
    
    DECLARE @ViewName NVARCHAR(128);
    DECLARE @ViewSchema NVARCHAR(128);
    DECLARE @ViewScript NVARCHAR(MAX);
    
    DECLARE view_cursor CURSOR FOR
    SELECT s.name, v.name
    FROM sys.views v
    INNER JOIN sys.schemas s ON v.schema_id = s.schema_id
    ORDER BY s.name, v.name;
    
    OPEN view_cursor;
    FETCH NEXT FROM view_cursor INTO @ViewSchema, @ViewName;
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @ViewScript = OBJECT_DEFINITION(OBJECT_ID('[' + @ViewSchema + '].[' + @ViewName + ']'));
        
        -- Replace CREATE VIEW with CREATE OR ALTER VIEW
        SET @ViewScript = REPLACE(@ViewScript, 'CREATE VIEW', 'CREATE OR ALTER VIEW');
        
        PRINT @ViewScript + ' GO';
        PRINT '';
        
        FETCH NEXT FROM view_cursor INTO @ViewSchema, @ViewName;
    END
    
    CLOSE view_cursor;
    DEALLOCATE view_cursor;
    
    -- =============================================
    -- Generate CREATE scripts for CONSTRAINTS
    -- =============================================
    PRINT '-- =============================================';
    PRINT '-- CONSTRAINTS';
    PRINT '-- =============================================';
    PRINT '';
    
    DECLARE @ConstraintName NVARCHAR(128);
    DECLARE @ConstraintTableName NVARCHAR(128);
    DECLARE @ConstraintSchema NVARCHAR(128);
    DECLARE @ConstraintType CHAR(2);
    DECLARE @ConstraintScript NVARCHAR(MAX);
    DECLARE @ConstraintDefinition NVARCHAR(MAX);
    
    DECLARE constraint_cursor CURSOR FOR
    SELECT s.name, t.name, c.name, c.type
    FROM sys.objects c
    INNER JOIN sys.tables t ON c.parent_object_id = t.object_id
    INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
    WHERE c.type IN ('PK', 'UQ', 'F', 'C', 'D')
    ORDER BY s.name, t.name, c.name;
    
    OPEN constraint_cursor;
    FETCH NEXT FROM constraint_cursor INTO @ConstraintSchema, @ConstraintTableName, @ConstraintName, @ConstraintType;
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @ConstraintScript = 'ALTER TABLE [' + @ConstraintSchema + '].[' + @ConstraintTableName + '] ADD CONSTRAINT [' + @ConstraintName + '] ';
        
        IF @ConstraintType = 'PK'
        BEGIN
            SET @ConstraintScript = @ConstraintScript + 'PRIMARY KEY CLUSTERED (' +
                STUFF((
                    SELECT ', [' + c.name + '] ' + CASE WHEN ic.is_descending_key = 1 THEN 'DESC' ELSE 'ASC' END
                    FROM sys.index_columns ic
                    INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
                    WHERE ic.object_id = OBJECT_ID('[' + @ConstraintSchema + '].[' + @ConstraintTableName + ']')
                    AND ic.index_id = (SELECT index_id FROM sys.indexes WHERE object_id = OBJECT_ID('[' + @ConstraintSchema + '].[' + @ConstraintTableName + ']') AND is_primary_key = 1)
                    AND ic.key_ordinal > 0
                    ORDER BY ic.key_ordinal
                    FOR XML PATH('')
                ), 1, 2, '') + ')';
        END
        ELSE IF @ConstraintType = 'UQ'
        BEGIN
            SET @ConstraintScript = @ConstraintScript + 'UNIQUE CLUSTERED (' +
                STUFF((
                    SELECT ', [' + c.name + '] ' + CASE WHEN ic.is_descending_key = 1 THEN 'DESC' ELSE 'ASC' END
                    FROM sys.index_columns ic
                    INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
                    WHERE ic.object_id = OBJECT_ID('[' + @ConstraintSchema + '].[' + @ConstraintTableName + ']')
                    AND ic.index_id = (SELECT index_id FROM sys.indexes WHERE object_id = OBJECT_ID('[' + @ConstraintSchema + '].[' + @ConstraintTableName + ']') AND name = @ConstraintName)
                    AND ic.key_ordinal > 0
                    ORDER BY ic.key_ordinal
                    FOR XML PATH('')
                ), 1, 2, '') + ')';
        END
        ELSE IF @ConstraintType = 'F'
        BEGIN
            DECLARE @ReferencedTable NVARCHAR(128);
            DECLARE @ReferencedSchema NVARCHAR(128);
            
            SELECT @ReferencedTable = OBJECT_NAME(fk.referenced_object_id),
                   @ReferencedSchema = OBJECT_SCHEMA_NAME(fk.referenced_object_id)
            FROM sys.foreign_keys fk
            WHERE fk.object_id = OBJECT_ID('[' + @ConstraintSchema + '].[' + @ConstraintName + ']');
            
            SET @ConstraintScript = @ConstraintScript + 'FOREIGN KEY (' +
                STUFF((
                    SELECT ', [' + c.name + ']'
                    FROM sys.foreign_key_columns fkc
                    INNER JOIN sys.columns c ON fkc.parent_object_id = c.object_id AND fkc.parent_column_id = c.column_id
                    WHERE fkc.constraint_object_id = OBJECT_ID('[' + @ConstraintSchema + '].[' + @ConstraintName + ']')
                    ORDER BY fkc.constraint_column_id
                    FOR XML PATH('')
                ), 1, 2, '') + ') REFERENCES [' + 
                @ReferencedSchema + '].[' + 
                @ReferencedTable + '] (' +
                STUFF((
                    SELECT ', [' + c.name + ']'
                    FROM sys.foreign_key_columns fkc
                    INNER JOIN sys.columns c ON fkc.referenced_object_id = c.object_id AND fkc.referenced_column_id = c.column_id
                    WHERE fkc.constraint_object_id = OBJECT_ID('[' + @ConstraintSchema + '].[' + @ConstraintName + ']')
                    ORDER BY fkc.constraint_column_id
                    FOR XML PATH('')
                ), 1, 2, '') + ')';
        END
        ELSE IF @ConstraintType = 'C'
        BEGIN
            SELECT @ConstraintDefinition = cc.definition
            FROM sys.check_constraints cc
            WHERE cc.object_id = OBJECT_ID('[' + @ConstraintSchema + '].[' + @ConstraintName + ']');
            
            SET @ConstraintScript = @ConstraintScript + 'CHECK (' + @ConstraintDefinition + ')';
        END
        ELSE IF @ConstraintType = 'D'
        BEGIN
            SELECT @ConstraintDefinition = dc.definition
            FROM sys.default_constraints dc
            WHERE dc.object_id = OBJECT_ID('[' + @ConstraintSchema + '].[' + @ConstraintName + ']');
            
            SET @ConstraintScript = @ConstraintScript + 'DEFAULT ' + @ConstraintDefinition + ' FOR [' + 
                (SELECT c.name FROM sys.columns c 
                 INNER JOIN sys.default_constraints dc ON c.object_id = dc.parent_object_id AND c.column_id = dc.parent_column_id
                 WHERE dc.object_id = OBJECT_ID('[' + @ConstraintSchema + '].[' + @ConstraintName + ']')) + ']';
        END
        
        PRINT @ConstraintScript + ' GO';
        PRINT '';
        
        FETCH NEXT FROM constraint_cursor INTO @ConstraintSchema, @ConstraintTableName, @ConstraintName, @ConstraintType;
    END
    
    CLOSE constraint_cursor;
    DEALLOCATE constraint_cursor;
    
    -- =============================================
    -- Generate CREATE scripts for INDEXES
    -- =============================================
    PRINT '-- =============================================';
    PRINT '-- INDEXES';
    PRINT '-- =============================================';
    PRINT '';
    
    DECLARE @IndexName NVARCHAR(128);
    DECLARE @IndexTableName NVARCHAR(128);
    DECLARE @IndexSchema NVARCHAR(128);
    DECLARE @IndexScript NVARCHAR(MAX);
    DECLARE @IndexUnique BIT;
    DECLARE @IndexType TINYINT;
    DECLARE @IndexFilter NVARCHAR(MAX);
    DECLARE @IndexPadded BIT;
    DECLARE @IndexRowLocks BIT;
    DECLARE @IndexPageLocks BIT;
    
    DECLARE index_cursor CURSOR FOR
    SELECT s.name, t.name, i.name, i.is_unique, i.type, i.filter_definition, i.is_padded, i.allow_row_locks, i.allow_page_locks
    FROM sys.indexes i
    INNER JOIN sys.tables t ON i.object_id = t.object_id
    INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
    WHERE i.is_primary_key = 0 AND i.is_unique_constraint = 0 -- Exclude primary keys and unique constraints
    AND i.type IN (1, 2) -- Clustered and Nonclustered indexes
    ORDER BY s.name, t.name, i.name;
    
    OPEN index_cursor;
    FETCH NEXT FROM index_cursor INTO @IndexSchema, @IndexTableName, @IndexName, @IndexUnique, @IndexType, @IndexFilter, @IndexPadded, @IndexRowLocks, @IndexPageLocks;
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @IndexScript = 'CREATE ' + 
            CASE WHEN @IndexUnique = 1 THEN 'UNIQUE ' ELSE '' END +
            CASE WHEN @IndexType = 1 THEN 'CLUSTERED ' WHEN @IndexType = 2 THEN 'NONCLUSTERED ' ELSE '' END +
            'INDEX [' + @IndexName + '] ON [' + @IndexSchema + '].[' + @IndexTableName + '] (' +
            STUFF((
                SELECT ', [' + c.name + '] ' + CASE WHEN ic.is_descending_key = 1 THEN 'DESC' ELSE 'ASC' END
                FROM sys.index_columns ic
                INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
                WHERE ic.object_id = OBJECT_ID('[' + @IndexSchema + '].[' + @IndexTableName + ']') 
                AND ic.index_id = (SELECT index_id FROM sys.indexes WHERE object_id = OBJECT_ID('[' + @IndexSchema + '].[' + @IndexTableName + ']') AND name = @IndexName)
                AND ic.key_ordinal > 0
                ORDER BY ic.key_ordinal
                FOR XML PATH('')
            ), 1, 2, '') + ')';
        
        -- Add INCLUDE clause if exists
        IF EXISTS (
            SELECT 1 FROM sys.index_columns ic 
            WHERE ic.object_id = OBJECT_ID('[' + @IndexSchema + '].[' + @IndexTableName + ']')
            AND ic.index_id = (SELECT index_id FROM sys.indexes WHERE object_id = OBJECT_ID('[' + @IndexSchema + '].[' + @IndexTableName + ']') AND name = @IndexName)
            AND ic.key_ordinal = 0
        )
        BEGIN
            SET @IndexScript = @IndexScript + ' INCLUDE (' +
                STUFF((
                    SELECT ', [' + c.name + ']'
                    FROM sys.index_columns ic
                    INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
                    WHERE ic.object_id = OBJECT_ID('[' + @IndexSchema + '].[' + @IndexTableName + ']')
                    AND ic.index_id = (SELECT index_id FROM sys.indexes WHERE object_id = OBJECT_ID('[' + @IndexSchema + '].[' + @IndexTableName + ']') AND name = @IndexName)
                    AND ic.key_ordinal = 0
                    ORDER BY ic.column_id
                    FOR XML PATH('')
                ), 1, 2, '') + ')';
        END
        
        -- Add filter if exists
        IF @IndexFilter IS NOT NULL
            SET @IndexScript = @IndexScript + ' WHERE ' + @IndexFilter;
        
        -- Add options
        SET @IndexScript = @IndexScript + ' WITH (' +
            'PAD_INDEX = ' + CASE WHEN @IndexPadded = 1 THEN 'ON' ELSE 'OFF' END + ', ' +
            'STATISTICS_NORECOMPUTE = OFF, ' +
            'SORT_IN_TEMPDB = OFF, ' +
            'DROP_EXISTING = OFF, ' +
            'ONLINE = OFF, ' +
            'ALLOW_ROW_LOCKS = ' + CASE WHEN @IndexRowLocks = 1 THEN 'ON' ELSE 'OFF' END + ', ' +
            'ALLOW_PAGE_LOCKS = ' + CASE WHEN @IndexPageLocks = 1 THEN 'ON' ELSE 'OFF' END + ')';
        
        PRINT @IndexScript + ' GO';
        PRINT '';
        
        FETCH NEXT FROM index_cursor INTO @IndexSchema, @IndexTableName, @IndexName, @IndexUnique, @IndexType, @IndexFilter, @IndexPadded, @IndexRowLocks, @IndexPageLocks;
    END
    
    CLOSE index_cursor;
    DEALLOCATE index_cursor;
    
    -- =============================================
    -- Generate CREATE scripts for STORED PROCEDURES
    -- =============================================
    PRINT '-- =============================================';
    PRINT '-- STORED PROCEDURES';
    PRINT '-- =============================================';
    PRINT '';
    
    DECLARE @ProcName NVARCHAR(128);
    DECLARE @ProcSchema NVARCHAR(128);
    DECLARE @ProcScript NVARCHAR(MAX);
    
    DECLARE proc_cursor CURSOR FOR
    SELECT s.name, p.name
    FROM sys.procedures p
    INNER JOIN sys.schemas s ON p.schema_id = s.schema_id
    ORDER BY s.name, p.name;
    
    OPEN proc_cursor;
    FETCH NEXT FROM proc_cursor INTO @ProcSchema, @ProcName;
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @ProcScript = OBJECT_DEFINITION(OBJECT_ID('[' + @ProcSchema + '].[' + @ProcName + ']'));
        
        -- Replace CREATE PROCEDURE with CREATE OR ALTER PROCEDURE
        SET @ProcScript = REPLACE(@ProcScript, 'CREATE PROCEDURE', 'CREATE OR ALTER PROCEDURE');
        
        PRINT @ProcScript + ' GO';
        PRINT '';
        
        FETCH NEXT FROM proc_cursor INTO @ProcSchema, @ProcName;
    END
    
    CLOSE proc_cursor;
    DEALLOCATE proc_cursor;
    
    -- =============================================
    -- Generate CREATE scripts for FUNCTIONS
    -- =============================================
    PRINT '-- =============================================';
    PRINT '-- FUNCTIONS';
    PRINT '-- =============================================';
    PRINT '';
    
    DECLARE @FuncName NVARCHAR(128);
    DECLARE @FuncSchema NVARCHAR(128);
    DECLARE @FuncScript NVARCHAR(MAX);
    
    DECLARE func_cursor CURSOR FOR
    SELECT s.name, f.name
    FROM sys.objects f
    INNER JOIN sys.schemas s ON f.schema_id = s.schema_id
    WHERE f.type IN ('FN', 'IF', 'TF') -- Scalar, Inline Table-Valued, Table-Valued Functions
    ORDER BY s.name, f.name;
    
    OPEN func_cursor;
    FETCH NEXT FROM func_cursor INTO @FuncSchema, @FuncName;
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @FuncScript = OBJECT_DEFINITION(OBJECT_ID('[' + @FuncSchema + '].[' + @FuncName + ']'));
        
        -- Replace CREATE FUNCTION with CREATE OR ALTER FUNCTION
        SET @FuncScript = REPLACE(@FuncScript, 'CREATE FUNCTION', 'CREATE OR ALTER FUNCTION');
        
        PRINT @FuncScript + ' GO';
        PRINT '';
        
        FETCH NEXT FROM func_cursor INTO @FuncSchema, @FuncName;
    END
    
    CLOSE func_cursor;
    DEALLOCATE func_cursor;
    
    -- =============================================
    -- Generate CREATE scripts for TRIGGERS
    -- =============================================
    PRINT '-- =============================================';
    PRINT '-- TRIGGERS';
    PRINT '-- =============================================';
    PRINT '';
    
    DECLARE @TriggerName NVARCHAR(128);
    DECLARE @TriggerTableName NVARCHAR(128);
    DECLARE @TriggerSchema NVARCHAR(128);
    DECLARE @TriggerScript NVARCHAR(MAX);
    
    DECLARE trigger_cursor CURSOR FOR
    SELECT s.name, t.name, tr.name
    FROM sys.triggers tr
    INNER JOIN sys.tables t ON tr.parent_id = t.object_id
    INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
    ORDER BY s.name, t.name, tr.name;
    
    OPEN trigger_cursor;
    FETCH NEXT FROM trigger_cursor INTO @TriggerSchema, @TriggerTableName, @TriggerName;
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @TriggerScript = OBJECT_DEFINITION(OBJECT_ID('[' + @TriggerSchema + '].[' + @TriggerName + ']'));
        
        -- Replace CREATE TRIGGER with CREATE OR ALTER TRIGGER
        SET @TriggerScript = REPLACE(@TriggerScript, 'CREATE TRIGGER', 'CREATE OR ALTER TRIGGER');
        
        PRINT @TriggerScript + ' GO';
        PRINT '';
        
        FETCH NEXT FROM trigger_cursor INTO @TriggerSchema, @TriggerTableName, @TriggerName;
    END
    
    CLOSE trigger_cursor;
    DEALLOCATE trigger_cursor;
    
    -- Footer
    PRINT '-- =============================================';
    PRINT '-- End of Scripts';
    PRINT '-- =============================================';
    
END
GO

Usage

EXEC dbo.GenerateDatabaseScript

Output

The procedure generates a complete database schema script with the following sections:

  1. TABLES - CREATE TABLE statements with all columns, data types, nullability, and defaults
  2. VIEWS - CREATE OR ALTER VIEW statements
  3. CONSTRAINTS - Primary keys, unique constraints, foreign keys, check constraints, and default constraints
  4. INDEXES - All non-primary key indexes with their properties
  5. STORED PROCEDURES - CREATE OR ALTER PROCEDURE statements
  6. FUNCTIONS - CREATE OR ALTER FUNCTION statements (scalar, inline, and table-valued)
  7. TRIGGERS - CREATE OR ALTER TRIGGER statements

Features

  • Generates scripts in dependency order (tables first, then constraints, then indexes)
  • Uses CREATE OR ALTER for views, procedures, functions, and triggers
  • Includes all index properties (clustering, uniqueness, included columns, filters)
  • Handles computed columns and default constraints
  • Supports multiple schemas
  • Includes metadata header with generation timestamp and server information

Notes

  • The script excludes system objects (MSShipped = 0)
  • Indexes exclude primary keys and unique constraints as they're handled in the constraints section
  • All generated scripts use proper bracketing for object names
  • The output can be redirected to a file for version control or documentation