← Blog

schemachange alternative: declarative schema management for Snowflake

July 7, 2026 · 7 min read

schemachange is the tool most Snowflake teams reach for first, and for good reason: it's from Snowflake Labs, it's tiny, and it does exactly one thing — run your SQL scripts against Snowflake in order, once each. If you've used Flyway, you already know the model. It works. For a five-object schema and one engineer, it's hard to beat.

This post is about what happens after that — when the schema is 400 objects and six people are merging to it — and why a state-based model with a safety classifier ends up being less work and far less dangerous. It's written by the people who build SDT, so read it as a competitor's argument — but the tradeoffs below are real, and there's an honest "keep schemachange if…" section at the end.

The migration model, and where it strains

schemachange is migration-based: the source of truth is an ordered chain of scripts you hand-write, each applied exactly once and tracked in a CHANGE_HISTORY table.

-- V1.1.1__create_orders.sql
CREATE TABLE orders (id NUMBER, customer_id NUMBER, total NUMBER(18,2));

-- V1.1.2__add_status.sql
ALTER TABLE orders ADD COLUMN status STRING;

-- V1.1.3__widen_total.sql
ALTER TABLE orders ALTER COLUMN total SET DATA TYPE NUMBER(38,2);

Three things get harder as the team and the schema grow:

  1. You maintain history, not state. To know what orders looks like today, you replay the chain in your head — or query production. The current shape of an object lives nowhere in the repo; it's the sum of every migration that ever touched it.
  2. Ordering is a merge conflict waiting to happen. Two engineers branch, both add a V1.2.x script, both merge. Now the apply order depends on filename sorting, not on what actually depends on what. schemachange will happily run them in an order neither author intended.
  3. There is no preview and no safety net. schemachange executes the SQL you wrote. It does not tell you that V1.4.7__cleanup.sql contains a DROP TABLE, or that an ALTER … SET DATA TYPE is a narrowing conversion that will fail — or silently truncate — against real data. And there is no down-migration: rollback means you write and test the reverse script yourself, under incident pressure, which is exactly when you don't want to be writing new SQL.

State-based: declare the object, let the tool compute the change

SDT inverts the model. You don't write migrations at all. You write one .sql file per object that describes its desired state, and SDT diffs that against the live account to compute the delta:

-- Warehouse/SALES/PUBLIC/Tables/orders.sql  — the whole object, always current
CREATE OR ALTER TABLE orders (
  id           NUMBER,
  customer_id  NUMBER,
  total        NUMBER(38,2),
  status       STRING
);

That one file is the three migrations above, collapsed into the current truth. To change the schema you edit the file — add a column, widen a type — and SDT figures out the ALTER needed to get production there. The repo now answers "what does orders look like?" by just… showing you the file. And because there's no ordered chain, two people editing two different objects don't conflict on sequence numbers — they conflict only if they edit the same object, which is a real conflict git already knows how to surface.

$ sdt compare --project ./Warehouse --target prod

  ~ TABLE  SALES.PUBLIC.ORDERS   add column STATUS STRING        [SAFE]
  ~ TABLE  SALES.PUBLIC.ORDERS   widen TOTAL NUMBER(18,2)→(38,2) [SAFE]

  2 changes, 0 blocked. Run `sdt publish` to apply.

The part schemachange has no equivalent for: a safety verdict

The bigger difference isn't ergonomics — it's that SDT classifies every proposed change before anything runs. Each delta gets one of four verdicts, with a reason attached: UNRECOVERABLE, DESTRUCTIVE, EXPENSIVE, or WARNING (and SAFE when nothing fires). This is not a lint rule matching a banned string — it's derived from what the operation actually does to data on Snowflake.

The narrowing change from the migration example is the tell. schemachange runs it. SDT stops it:

$ sdt compare --project ./Warehouse --target prod

  ~ TABLE  SALES.PUBLIC.ORDERS   narrow TOTAL NUMBER(38,2)→(18,2)   [DESTRUCTIVE]
       reason: narrowing a numeric type can truncate or fail on existing rows
  - STREAM SALES.PUBLIC.ORDERS_CDC   drop stream                    [UNRECOVERABLE]
       reason: a stream's offset cannot be reconstructed once dropped

  1 UNRECOVERABLE, 1 DESTRUCTIVE change — publish refused.
  To proceed: --allowNarrowingTypes and --allowUnrecoverableDrop (explicit opt-in)

Note the two verdicts are different. A narrowing type change is destructive but recoverable — you can widen it back, and Time Travel has the old rows. Dropping a stream is unrecoverable — the offset existed in exactly one place. Treating those two as the same risk is how pipelines break. SDT refuses both by default and makes you pass a named gate to override, so the dangerous path is opt-in and auditable in the diff, not a line buried in a script.

Switching is one command, not a rewrite

The reason not to switch is usually "I already have 300 migration scripts." You don't have to throw them away. SDT ships a native schemachange importer that reads your existing migration folder and materializes the current state as a project tree of .sql files:

npm i -g @sdt-tools/cli
sdt import --from schemachange ./migrations --out ./Warehouse

From there your existing scripts stay as history; new changes are declarative. You can adopt the compare + safety layer without a big-bang migration — run sdt compare in CI alongside schemachange for a while and only cut over when you trust it.

When to keep schemachange

This isn't a tool that wins every situation, and pretending otherwise would be dishonest:

If, on the other hand, you've ever shipped a DROP you meant to gate, spent an afternoon reconstructing "what does prod actually look like," or hit a merge where migration ordering bit you — that's the shape of problem the state-based model plus a safety classifier is built to remove.

The deterministic core — compare, safe deploy, extract, lint, lineage — is free, runs offline, and is Apache-2.0. Same tool covers Databricks Unity Catalog as DDT. Try it against a scratch schema before you trust it with prod: npm i -g @sdt-tools/cli.


Independent tool, not affiliated with or endorsed by Snowflake Inc. “Snowflake” and “schemachange” (Snowflake-Labs) are trademarks of their respective owners.


Ship schema changes against Snowflake or Databricks?

SDT and DDT put a safety classifier on every change — data-loss operations refuse by default. Free tier, runs local-only.

Install SDT Install DDT See pricing