Data migration
Version 2 has three more fields. Your customer has a year of data.
This is the part nobody demonstrates and everybody runs into — usually at nine on a Monday, at a customer's, with the old version already uninstalled. It is the reason the schema is drawn rather than typed.
Your application carries the schema it was built with. On the first start after an update it compares that against the new one and does what is needed — add a column, add a table, add an index — before a single window opens. Nobody writes a migration script, because there is nothing a script would know that the comparison does not.
Tables and fields are matched by identity, not by name. Rename
kunde to auftraggeber and the column keeps its data. Match
by name and the same change reads as “one field vanished and an empty one appeared” —
which is a year of somebody's work, gone, with the update reporting success.
Change netto = menge * preis to take a discount off, and every
existing row is worked out again on the next start — in the right order, so a total
is never built from figures that have not been updated yet. Only the tables whose
formulas actually moved: a release that shifted a button does not recompute a million
rows.
Before any of it runs, the database is copied. If a migration cannot be finished, nothing has happened — the copy is the answer, not an apology.
An application starts empty, which is right for invoices and wrong for the postcode table, the VAT rates, the standard categories and the first administrator. In the build dialog you tick which tables travel with it — so a fresh installation is empty, not broken.
The tick is on the table itself, not on its name — rename it and it still ships. “Everything” means everything, including tables you add next year; with “only these”, a new table starts unticked and the build sheet says so rather than silently leaving it out.