How to Stop SSDT / Database Projects / SQLPackage from Modifying Database Options
SQL Server’s free state-based version control tooling was introduced under the ‘Data Dude’ brand, then became known as ‘SQL …
Read MoreBy Kendra Little on • 6 min read
Database migration scripts are a popular and effective way to check database code into version control.
In this post I describe the most common features of popular migration script runners for database code deployment, along with the top gotchas that folks hit when versioning their database code with migrations.

Migration scripts enable checking database code into version control in a human readable format. This provides a centralized, standard way to record all changes.
Migration scripts are also useful to:
Together, these benefits bring database code into line with processes and standards for application code.
Database migration scripts for change deployment are used by tools like Flyway and Liquibase. Some companies write custom implementations of migration script runners.
If you are evaluating building or buying one of these toolsets, this post will help you understand essential functionality and which workflows to tackle first.
Core functionality to consider when adopting or writing a database migration script runner are versioned migration scripts that can be run in a defined order, a way to track migration script status against a specific target database to run scripts only when needed, baseline scripts, and re-runnable migrations.
A database migration script changes the state of your database, most commonly by adding, modifying, or deleting objects in the database.
Migration scripts can also add or modify data in objects in the database. An example:
CREATE TABLE
dbo.bananas
(
banana_id integer,
banana_name nvarchar(50) UNIQUE,
banana_color nvarchar(20),
CONSTRAINT PK_bananas
PRIMARY KEY CLUSTERED (banana_id)
);
INSERT INTO
dbo.bananas
(
banana_id,
banana_name,
banana_color
)
VALUES
(1, N'Cavendish', N'Yellow'),
(2, N'Plantain', N'Green'),
(3, N'Red Dacca', N'Red'),
(4, N'Manzano', N'Yellow'),
(5, N'Burro', N'Yellow');
The main reason I show this code is: it’s just that simple! Migration scripts can be written in a variety of languages, depending on what the migration script runner supports, but generally these are scripts written in plain SQL that are easy for humans to review.
Our second migration creates a table for banana ratings. This table uses banana_id values that are defined in dbo.bananas.
CREATE TABLE
dbo.banana_ratings
(
rating_id integer,
banana_id integer,
rating_value integer,
rating_date datetime,
CONSTRAINT FK_BananaRating_Banana
FOREIGN KEY (banana_id)
REFERENCES dbo.bananas (banana_id)
);
The second migration script has a foreign key dependency on the first migration. They need to always run in a specific order.
Migration runners commonly provide a way to number scripts and execute them in the desired order.
The log tracking which migration scripts have been run with their status is often kept in a table in the target database.
The migration script runner generally reads from this table prior to a deployment, and updates the table as the deployment progresses.
What if you need to start with a database with existing objects?
A “baseline” migration script contains all of the definitions of objects in the database, ordered so that dependencies can be met. This is generally contained in a single migration script with a special status to differentiate it from “normal” migrations.
Migration script runners usually only deploy the baseline script to an empty target database. This allows validation of database code for continuous integration.
Sometimes you want a migration to run every time you deploy changes. This might ensure the target database is configured for the appropriate environment (dev, prod, etc), create objects like stored procedures, or ensure that an object or data is in a specific state.
Migration runners sometimes allow specific naming and ordering conventions for re-runnable migration scripts.
There’s a lot that can get complex when you get into the weeds. The top places where folks get confused are related to rollbacks, shared database environments, and dealing with changes in deployment order/numbering challenges.
Some tools have options for Rollback scripts, but managing them is complex. In production, you usually want every migration recorded as a “roll forward” because it’s a real deployment, and you want your future builds to play out the same sequence.
Also, everyone hates writing rollbacks manually. Everyone.

Mistakes or necessary changes to scripts are often identified in shared testing or pre-production environments.
If there is no automated, fast way to reset the database (including all its data), then it can be time consuming to validate that the target environment is in the desired state once code is rolled back and scripts are modified.
Code is often not deployed to prod in the same order it is completed.
Let’s say Ravi writes migration script 42, which needs a lot of testing. Sandy releases an important hotfix in migration 43. Now Ravi’s script is out of order. Should they renumber it? What does that mean for all environments where it has already been deployed? What does this mean for future “from scratch” builds?
Database “state” capture and comparison tools can augment migration script approaches with cool functionality. Vendors often provide this as part of their value proposition.
Those writing rollback scripts will want to use generative AI to help draft and validate changes. Both humans and AI can write bad SQL, so be wise how you use both.
Thinking about the gotchas can be daunting. There is a lot to gain even if some portions of your process remain manual, or if you implement automation in stages. A recap of the benefits of database migrations is worth it at this point.
These will give you the ability for many good things:
Copyright (c) 2025, Catalyze SQL, LLC; all rights reserved. Opinions expressed on this site are solely those of Kendra Little of Catalyze SQL, LLC. Content policy: Short excerpts of blog posts (3 sentences) may be republished, but longer excerpts and artwork cannot be shared without explicit permission.