# 5. Repeatable migrations [← 4. Out-of-order migrations](04-out-of-order-migrations.md) · [Index](../README.md) · Next: [6. Baselining an existing database →](06-baselining-an-existing-database.md) Versioned migrations (`V1__...`) are one-shot: apply once, never again, ever, on this database. Views, stored procedures and seed-reference-data scripts don't fit that model — you want them to re-apply whenever their *content* changes, regardless of what version number anything else is at. That's what a repeatable migration, prefixed `R__` instead of `V__`, is for. [`FlywayRepeatableTest`](../src/test/java/com/ankurm/dbmigrations/flyway/FlywayRepeatableTest.java) ships one versioned migration (`V1__init.sql`, creating and seeding an `invoice` table) alongside [`R__invoice_summary_view.sql`](../src/test/java/com/ankurm/dbmigrations/flyway/FlywayRepeatableTest.java), a view definition. On the first startup it runs once, like anything else: ``` installed_rank | version | description | type | checksum ---------------+---------+-------------------------------------------+-------+------------ -1 | NULL | << Flyway Schema History table created >> | TABLE | NULL 1 | 1 | init | SQL | -192856793 2 | NULL | invoice summary view | SQL | -2025099931 ``` Notice the `version` column is `NULL` — that's how the history table distinguishes a repeatable migration from a versioned one; there is no version number to have. The test then widens the view to also sum `amount_cents`, **without touching a filename or any version number**, and starts a second context against the same database: ``` installed_rank | version | description | type | checksum ---------------+---------+-------------------------------------------+-------+------------ -1 | NULL | << Flyway Schema History table created >> | TABLE | NULL 1 | 1 | init | SQL | -192856793 2 | NULL | invoice summary view | SQL | -2025099931 3 | NULL | invoice summary view | SQL | 750256380 ``` (both tables from [`docs/output/04-flyway-repeatable.txt`](output/04-flyway-repeatable.txt)) A brand new row — same description, new checksum — and the view really was redefined: querying `invoice_summary` afterward returns the new `total_cents` column. Compare this against chapter [3](03-checksum-validation.md): for a *versioned* migration, a changed checksum is a hard failure. For a *repeatable* one, it's the trigger to rerun. Same mechanism (a checksum comparison against the history table), opposite consequence, and the only thing that decides which applies is the filename prefix. V1__init.sql checksum changes → FAILS R__invoice_summary_view.sql checksum changes → RERUNS Same comparison against flyway_schema_history's checksum column — the V/R prefix alone decides whether a mismatch is a failure or a re-run. ## Going deeper - **Repeatable migrations run last, after every pending versioned one**, in the order they appear on the classpath (alphabetically, by default) — not interleaved by when they were last changed. - [Flyway's repeatable migration docs](https://documentation.red-gate.com/fd/repeatable-migrations-273973335.html) (`rel="nofollow"`) cover ordering and the `installedOn`/checksum comparison in full. - A view is the textbook use case, but the same mechanism works for anything idempotent — a stored procedure body, or a `MERGE`/`upsert` of reference data that should reflect whatever the file currently says, not whatever it said the first time it ran.