Search

Comparing database structures in MSSQL server

0 views

Why Structural Consistency Matters in Multi‑Database Environments

When a team builds a client‑server application, the database layer rarely stays in a single place.

The development environment contains a schema that mirrors the production one, while the testing

environment has its own copy that receives patches, rollbacks and experimental changes.

Because each of these copies lives on the same SQL Server instance or on different instances, the

structure of tables, columns, indexes, views, triggers and other objects can diverge quickly.

A missing column in the test database might let a regression slip into the build, while an

extra index in development could slow down the same queries that run in production, breaking

performance expectations.

The problem is amplified when the database is under continuous change: developers keep adding

new objects, renaming columns or dropping obsolete ones. If the changes are not propagated

consistently, automated processes such as deployment scripts, automated tests or backup

strategies may fail or produce misleading results.

In practice, a developer often spends a significant amount of time hunting down a broken

stored procedure or a failed query caused by a mismatched schema. That time could be saved if

the team had a simple, repeatable way to confirm that the structures of the various

databases are in sync before each build or before running a test suite.

A simple comparison script is an inexpensive safety net. By running it as part of the build

or test pipeline, the team can catch structural discrepancies early, before they surface as

runtime errors. The comparison does not need to be elaborate; a few targeted checks that

focus on the most fragile objects - tables, columns and indexes - often suffice. However, a

comprehensive comparison that also covers constraints, defaults, views, triggers and

scripts gives the most confidence that the environments are truly aligned.

In a typical development workflow, the check runs against a reference database - the

“etalon” or baseline that reflects the intended schema. The target database could be the

latest build of the test environment or a production snapshot that needs validation. By

generating a clear, human‑readable report of any differences, developers can decide whether

to adjust the test database, correct the production schema, or update the comparison

logic itself. The key is that the comparison logic is part of the version control

process, so any change to the schema is immediately reflected in the comparison scripts.

SQL Server Tools for Structural Comparison: From sysobjects to Heterogeneous Queries

SQL Server ships with a rich set of system tables that expose metadata about every object in

the database. The most common of these is sysobjects, which stores a row for each

table, view, index, trigger, stored procedure and so on. Because the metadata is stored in

a relational format, it is natural to compare two databases by writing a query that joins

the sysobjects table from the reference database with the one in the target database.

The join uses the name column, because the numeric id values are unique only within

their own database. A typical comparison query looks like this:

Prompt
<strong>select * from etalondb.dbo.sysobjects etalon</p> <p>left join sysobjects test on test. = etalon.</p> <p>where etalon.[xtype] = 'U'</strong>
Running the script against the reference database and then against the target database

produces two result sets that show which user tables exist in one database but not the

other. While this is a quick way to spot missing or extra tables, the raw result set is

hard to interpret. A better approach is to walk through the rows with a cursor and print

human‑friendly messages that indicate the presence, absence or inconsistency of each

object. The following cursor‑based procedure demonstrates this idea. It uses the

PRINT statement to emit messages, so that the output is visible in the query

results window or the log of an automated job.

Prompt
<strong>declare @o_name varchar(50), @o_id int, @o_type char(2)</p> <p>declare @c_name varchar(50), @c_id int</p> <p>declare o_sysobjects cursor fast_forward for</p> <p> select [id], [type] from etalondb..sysobjects</p> <p> where (type in ('F', 'U', 'V')) order by </p> <p>open o_sysobjects</p> <p>fetch o_sysobjects into @o_name, @o_id, @o_type</p> <p>while @@fetch_status = 0</p> <p>begin</p> <p> select @c_name = null, @c_id = null</p> <p> select @c_name = @c_id = [id] from sysobjects</p> <p> where = @o_name and isnull(objectproperty([id], 'ISMSShipped'), 1) 1</p> <p> if (@c_name is not null)</p> <p> begin</p> <p> if (@o_type = 'U')</p> <p> begin</p> <p> exec sp_check_columns @c_name</p> <p> exec sp_check_indexes @c_name</p> <p> end</p> <p> else if (@o_type = 'V')</p> <p> begin</p> <p> end</p> <p> else if (@o_type = 'F')</p> <p> begin</p> <p> exec sp_check_foreignkey @c_name</p> <p> end</p> <p> select 'OK! ' + @o_type + '-' + @c_name</p> <p> end</p> <p> else if (isnull(objectproperty(@c_id, 'ISMSShipped'), 1) 1)</p> <p> begin</p> <p> select 'ERROR! etalondb.' + @o_type + '-' + @c_name</p> <p> end</p> <p> fetch o_sysobjects into @o_name, @o_id, @o_type</p> <p>end</p> <p>close o_sysobjects</p> <p>deallocate o_sysobjects</strong>
The cursor iterates over every table, view or foreign key in the reference database. For

each object it looks up the corresponding row in the target database, ignoring objects

that SQL Server creates automatically (statistics, system indexes, etc.) by checking the

ISMSShipped property. When an object is found, the procedure calls specialized

routines - sp_check_columns, sp_check_indexes, sp_check_foreignkey - to validate

the internal structure. If the object is missing, a message marks it as an error. This

pattern keeps the comparison logic clean and makes it easy to add new checks or modify

existing ones without rewriting the whole script.

Custom Procedures to Spot Differences: Columns, Indexes, and Scripts

Once the cursor identifies an object that exists in both databases, the next step is to

compare its internal components. Column definitions are the simplest: a column exists

only if its name, data type, length, nullability and status match. The stored procedure

sp_check_columns encapsulates this logic. It retrieves the metadata for the target

table from the reference database’s syscolumns and systypes tables, then

cross‑references it with the same tables in the test database. If any attribute differs,

an error message is printed. The procedure is deliberately minimal; it stops after the

first discrepancy, which keeps the output focused and easy to read. The code looks like

this:

Prompt
<strong>create procedure sp_check_columns @table_name varchar(50) as</p> <p>begin</p> <p> declare @o_name varchar(50), @o_table_id int, @o_type_name varchar(50),</p> <p> @o_length int, @o_isnullable int, @o_status int</p> <p> declare @c_name varchar(50), @c_table_id int, @c_type_name varchar(50),</p> <p> @c_length int, @c_isnullable int, @c_status int</p> <p> set @o_table_id = object_id('etalondb..' + @table_name)</p> <p> set @c_table_id = object_id(@table_name)</p> <p> declare o_syscolumns cursor fast_forward for</p> <p> select c. t. c.length , c.isnullable, c.status</p> <p> from etalondb..syscolumns c, etalondb..systypes t</p> <p> where c.usertype = t.usertype and c.[id] = @o_table_id</p> <p> open o_syscolumns</p> <p> fetch o_syscolumns into @o_name, @o_type_name, @o_length, @o_isnullable, @o_status</p> <p> while @@fetch_status = 0</p> <p> begin</p> <p> select @c_name = null</p> <p> select @c_name = c. @c_type_name = t.</p> <p> @c_length = c.length, @c_isnullable = c.isnullable, @c_status = c.status</p> <p> from syscolumns c, systypes t</p> <p> where (c.usertype = t.usertype) and (c.[id] = @c_table_id) and (c. = @o_name)</p> <p> if (@c_name is null) or (@o_type_name <p> or (@o_length <p> or (@o_isnullable @c_status)</p> <p> begin</p> <p> select 'ERROR! etalondb.' + @table_name</p> <p> end</p> <p> fetch o_syscolumns into @o_name, @o_type_name, @o_length,</p> <p> @o_isnullable, @o_status</p> <p> end</p> <p> close o_syscolumns</p> <p> deallocate o_syscolumns</p> <p>end</strong>
Index comparison is more involved because the system tables that describe indexes are

nested and contain multiple columns for a single index. A full solution typically relies on

a second query that extracts index properties such as the index name, the columns involved,

the sort order, and whether the index is clustered or unique. The procedure that follows

has been crafted to compare these properties across databases. The script is large and

complex, but it is available for download from the project’s repository, which lets

developers tweak it for their own needs. The download link is

Prompt
<strong>create procedure sp_check_object_script @object_name varchar(50) as</p> <p>begin</p> <p> declare @o_len int, @c_len int</p> <p> select @o_len = len([ctext]) from etalondb..syscomments</p> <p> where [id] = object_id('etalondb..' + @object_name)</p> <p> select @c_len = len([ctext]) from syscomments where [id] = object_id(@object_name)</p> <p> if (@o_len is null) or (@c_len is null)</p> <p> begin</p> <p> select 'ERROR! in calling of sp_check_object_script for object: ' + @object_name</p> <p> return</p> <p> end</p> <p> if (@o_len <p> begin</p> <p> select 'ERROR! the definitions of ' + @object_name + ' are differ'</p> <p> end</p> <p>end</strong>
When the whole suite of procedures is executed, the output is a concise set of messages

that point to the exact objects that differ, along with a short status report for each

object that matches. By integrating this process into a CI pipeline or running it manually

before each deployment, developers can eliminate the risk of a structural mismatch from

their release cycle. The performance cost is modest; most scripts run in a few seconds

even on databases with hundreds of tables, and the overhead is negligible compared to

the time saved by catching schema drift early.

Suggest a Correction

Found an error or have a suggestion? Let us know and we'll review it.

Share this article

Comments (0)

Please sign in to leave a comment.

No comments yet. Be the first to comment!

Related Articles