SSMS Schema Compare: Find What Differs Between Two Databases

🛠️Part of the SSMS Complete Guide, installing, configuring and fixing SQL Server Management Studio.In: Server & Configuration

Test works and production does not, and the first question is what is actually different between the two databases. SSMS answers that itself now. Schema Compare takes two database definitions, a live database, a .dacpac file or a SQL project, and lists every difference as the change that would make the target match the source. It arrived in SSMS 22.7 and has been generally available since 22.10.2, so there is nothing extra to buy or install.

⚡Right-click the source database, Tasks, Schema Compare, pick the target, Compare. On production, use Generate Script rather than Apply, so the change goes through review like any other deployment.

Run a Comparison

  1. In Object Explorer, right-click the database you are comparing from, the one that is right, and choose Tasks → Schema Compare. On releases before 22.10.2 the menu item reads Schema Compare (Preview).
  2. That database is filled in as the source. Choose the target: another database, a .dacpac file, or a SQL database project.
  3. Select Compare in the toolbar.

You can also start from Tools → Schema Compare, which opens with neither side filled in. Microsoft’s Schema Compare page covers the third way in, from a SQL database project in Solution Explorer.

SSMS Schema Compare window with zz_sc_dev as source and zz_sc_prod as target, the yellow bar reading Comparing schemas while the comparison runs.

If it stops with Unexpected exception caught during population of source model: Execution Timeout Expired, the server could not read the schema in time, usually for lack of memory: Schema Compare: Execution Timeout Expired explains why and how to get it to run.

SSMS Schema Compare with dbo.Orders selected: the results list shows Table dbo.Orders as Change and Procedure dbo.GetOpenOrders as Add, and the pane underneath compares the 2 table definitions. Development, on the left, has OrderDate DATETIME2, Notes VARCHAR(500), a Status column and the index IX_Orders_CustomerId. Production, on the right, has OrderDate DATETIME and Notes VARCHAR(200).

Here the development copy is the source and production the target, and the compare found 2 differences. dbo.GetOpenOrders is an Add: the procedure only exists in development, so the script would create it on production. dbo.Orders is a Change, and that one row carries every difference on the table: OrderDate is datetime2 in development and datetime in production, Notes is varchar(500) against varchar(200), development has a Status column production does not, and development has an index, IX_Orders_CustomerId, that production lacks.

Columns, indexes and constraints roll up under their table, so a single Change row can be several changes. With dbo.Orders selected, as here, the pane underneath shows both definitions: development on the left, production on the right, each difference highlighted down to the 500 and the 200. What is missing is as useful: the 2 primary keys have different system-generated names, and Schema Compare does not report that as a difference.


Reading the Results

Every row is written from the target’s point of view. Add means the object exists in the source and would be created in the target. Delete means it exists only in the target and would be dropped. Change means it exists in both and the target’s version would be altered to match. Get source and target the wrong way round and every Add becomes a Delete, so check which side is which before you read further. Switch Direction in the toolbar swaps them without starting again.

  • Results can be grouped by action (Add, Change, Delete) or by object type, as in the screenshot. Group By in the toolbar switches between them, and type reads better on a large database.
  • Select a row to see the two definitions side by side, highlighted like a source control diff.
  • A renamed object, or one moved to another schema, shows the source name in bold.
  • Objects that are identical, or that cannot be updated such as built-in objects, are hidden by default. The filter buttons in the toolbar show them.

Apply or Generate Script

Apply asks you to confirm, then makes the changes to the target straight away. Generate Script writes the same changes as a T-SQL script and opens it in the query editor. For anything that matters, take the script: you can read it, run it in a change window, and keep it with the rest of your deployment scripts. Apply is fine for a scratch database you would happily rebuild.

To leave a difference out, clear the row’s Include box. A row that other rows depend on, such as a table under a view, stays included until you clear its dependants too.


Before You Run the Script

The script is a DacFx publish script, the same kind SSDT and SqlPackage write, and 5 things about it are worth knowing before you press Execute:

  • It needs SQLCMD Mode. The database name is a :setvar variable, so turn on Query → SQLCMD Mode before you run it. Without it, the script notices and switches execution off with SET NOEXEC ON, so nothing runs. After turning the mode on, run SET NOEXEC OFF and start again.
  • It is not one transaction. It stops at the first error, and everything before that error stays done. Take a backup or know your way back first.
  • It is written for one database. The header names the target, and DatabaseName is a variable. Change that one line and the same script runs against a restored copy of production first, which is the cheapest rehearsal there is.
  • Dropping a table checks for rows first. If the table has data, the script stops with Rows were detected. The schema update is terminating because data loss might occur.
  • Dropping an index does not. An index that only exists on production, maybe one added there on purpose for a slow query, is a Delete row and goes without a warning. Clear its Include box if it should stay.
Top of a script from Schema Compare Generate Script: Deployment script for zz_sc_prod, the :setvar lines for DatabaseName, DefaultFilePrefix, DefaultDataPath and DefaultLogPath, :on error exit, and the block that detects SQLCMD mode and disables execution when it is off.

The top of the script from the compare above. Lines 16 to 19 are the :setvar variables, line 22 is the stop-on-error, and lines 24 to 28 are the check that switches execution off when SQLCMD Mode is not on.


Options Worth Setting

Options in the toolbar opens 2 tabs. Include Object Types decides what is compared: leave out permissions, users or user-defined types when you only care about tables and code. General Options decides what counts as a difference and how the change script behaves, and the search box at the top finds an option by name.

SSMS Schema Compare Options window, General Options tab: a search box, then deployment options including Allow table recreation in Publish, Block on possible data loss and Disable and reenable DDL triggers ticked, and Backup database before changes and Deploy database in single user mode clear. A second tab, Include Object Types, picks what is compared.

The ones to know before you generate a script for production:

  • Block on possible data loss is the guard that stops the script when a table it would drop or narrow still has rows. Leave it ticked.
  • Allow table recreation in Publish lets a change that cannot be made with ALTER, such as a new column in the middle of a table, rebuild the whole table and copy the rows across. On a big table that is a long, logged operation, so look for it in the script.
  • Backup database before changes is off. Tick it for a scratch run, or take your own backup first on anything that matters.
  • Disable and reenable DDL triggers is on, so an audit trigger on the target will not see the change. Untick it if your change process relies on one.

Ignore whitespace and Ignore column order are further down the same list, and they stop cosmetic noise from burying the real changes.

Save the comparison as an .scmp file and the source, target and options come back with it. Rerun it before every release, or hand it to a colleague to run the same check.


Frequently Asked Questions

Does Schema Compare compare the data too?

No. It compares object definitions: tables, columns, indexes, procedures, permissions and the rest. Rows that differ between the two databases do not show up here.

Does it work with Azure SQL Database and Managed Instance?

Yes. SQL Server, Azure SQL Database, Azure SQL Managed Instance and Azure Synapse Analytics are all supported, as a source or a target.

Do I need the Database DevOps workload?

Only to compare against a SQL database project. Database to database, and database to .dacpac, work without it.

What permissions does it need?

Reading a database’s schema is the same read as extracting a .dacpac. Microsoft’s minimum for that is VIEW DEFINITION on the database, ALTER ANY LOGIN on the server and SELECT on sys.sql_expression_dependencies. Applying changes needs the rights to make them on the target.

Which SSMS version do I need?

22.7 or later. It was a preview until 22.10.2, when it became generally available. Install and Update SSMS covers getting onto the current release.


Where To Go Next

Summary

Schema Compare answers “what is different between these 2 databases” without leaving SSMS. Make the database that is right the source, read every row as a change to the target, and select a row to see exactly what differs.

For production, take Generate Script over Apply, run it in SQLCMD Mode against a restored copy first, and read it for dropped indexes and table rebuilds. Save the comparison as an .scmp to rerun before each release. If it times out before showing anything, the server is short of memory, not the database: Schema Compare: Execution Timeout Expired.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *