Use ALTER TABLE … ADD COLUMN … DEFAULT …: ALTER TABLE users ADD COLUMN status TEXT NOT NULL DEFAULT 'active';. Every existing row gets 'active' at once, and so does every new row that leaves the column out. On SQL Server, drop the word COLUMN: ALTER TABLE users ADD status VARCHAR(20) NOT NULL DEFAULT 'active';.
ALTER TABLE changes a table that already holds data, and adding a column is the change you make most often. The default does two jobs: it fills the new column in the rows that are already there, and it fills it in later inserts that don't mention it. The sections below add columns with and without NOT NULL, rename and drop columns, change whether a column accepts NULL, and rebuild a table for the changes SQLite can't make in place. The examples run on SQLite; the table in section 7 gives the PostgreSQL, MySQL and SQL Server forms, which differ more here than for most statements. Each example runs on this page: hit Run, then edit the code and run it again.
1ALTER TABLE … ADD COLUMN … DEFAULTRecommended
Name the table, the new column, its type and the default. The rows already in the table get the default as soon as the statement runs, so there is no separate UPDATE to write. After that the default works like any other: an INSERT that leaves the column out gets it, and one that names the column keeps its own value, even when that value is NULL.
Output
Ana, Ben and Cleo were inserted before either column existed, yet they show active and 10. Dev left both columns out and got the defaults. Eli gave his own values, so he is invited with 0 credits. Fay passed an explicit NULL and kept it: a default only applies when the column is missing from the insert, not when its value is NULL. SQLite adds one column per statement; to fill a new column from another table instead of a constant, add it without a default and see update from another table.
2A NOT NULL column needs a default
A new nullable column without a default is easy: the existing rows get NULL. A NOT NULL column is different, because the rows already in the table need some value that isn't NULL, and the default is where it comes from. Leave it out and SQLite refuses the change. The last statement below shows the error.
Output
Both orders show NULL in note and EUR in currency. The paid column fails with Cannot add a NOT NULL column with default value NULL. PostgreSQL and SQL Server also refuse a NOT NULL column without a default once the table has rows; MySQL accepts it and fills the old rows with the type's implicit value (0 for an INT). If you want a required column with no default, add it as nullable, fill it with an UPDATE, then make it NOT NULL as in section 5. SQLite also rejects a UNIQUE or PRIMARY KEY column in ADD COLUMN, and a non-constant default such as CURRENT_TIMESTAMP once the table has rows.
3Rename a column: RENAME COLUMN
ALTER TABLE … RENAME COLUMN old TO new is the standard form, and SQLite (3.25+), PostgreSQL and MySQL 8.0+ all accept it. SQLite rewrites every index, view and trigger that mentions the column, so nothing that depended on the old name breaks. The code below renames a column that both an index and a view use, then reads from the view.
Output
The table reads back with a name column. The view still works and its column header is now name too, and pragma_index_info shows the index covers name. Code outside the database that still says nm is another matter: rename it there in the same release. SQL Server has no RENAME COLUMN; it uses the sp_rename procedure (see section 7). To rename the table itself, use ALTER TABLE products RENAME TO items.
4Drop a column: DROP COLUMN
ALTER TABLE … DROP COLUMN removes a column and its data. SQLite has supported it since 3.35 (March 2021), with a few limits: it refuses a column that is part of an index until you drop the index, and it never drops a PRIMARY KEY or UNIQUE column. The last statement below hits that second rule.
Output
After the two drops, SELECT * shows only id, name and email. Dropping email fails with cannot drop UNIQUE column: "email". Had the index still been there, dropping legacy_code would have failed with error in index idx_users_legacy after drop column: no such column: legacy_code. To drop a UNIQUE or key column in SQLite, rebuild the table without it (section 6).
5Make a column nullable, or NOT NULL
PostgreSQL spells this ALTER COLUMN … DROP NOT NULL and ALTER COLUMN … SET NOT NULL, and recent SQLite accepts the same two statements (this page runs 3.53.4; older SQLite has no ALTER COLUMN at all and needs the rebuild in section 6). SET NOT NULL checks the existing rows, so fill in any NULL values first. The final insert shows the new rule being enforced.
Output
Row 3 has a NULL name, which the original table would have rejected. Ben's missing phone became none, so SET NOT NULL went through, and Cleo's insert then fails with NOT NULL constraint failed: contacts.phone. Without that UPDATE, SET NOT NULL itself fails with a plain constraint failed. SQLite still has no ALTER COLUMN … TYPE or ALTER COLUMN … SET DEFAULT; both are syntax errors here. MySQL and SQL Server change nullability by restating the column's type, as the table in section 7 shows.
6Change a column type in SQLite: rebuild the table
For anything ALTER TABLE can't do in SQLite (a new type, a new default, adding or removing a constraint), build the table you want under a temporary name, copy the rows across, drop the old table and rename the new one into its place. Inside one transaction, nobody sees the table half-built. The copy is an ordinary INSERT … SELECT, so it can convert values on the way. Here price goes from TEXT NOT NULL to a nullable REAL.
Output
The text prices '2.50' and '9' come back as the real numbers 2.5 and 9.0, and the new row accepts a NULL price. Copying id keeps the keys, so rows in other tables still point at the right items. Foreign keys are switched off around the transaction (the pragma does nothing inside one) so that dropping the old table doesn't touch rows that reference it. Indexes and triggers go with the old table, so create them again afterwards. Drop any view that reads the table before you start: otherwise the rename fails with error in view v: no such table: main.items. Recreate the view at the end.
7Other databases: SQL Server, MySQL, PostgreSQL
Adding a column is close to standard everywhere, but renaming and changing a column are not. Each form below was run on PostgreSQL 18, MySQL 8.4 and SQL Server's T-SQL (through Babelfish), except where the note says otherwise.
| Syntax | Database | Notes |
|---|---|---|
| ADD COLUMN status TEXT NOT NULL DEFAULT 'active' | SQLite, PostgreSQL | Existing rows get the default. Without a default, PostgreSQL refuses NOT NULL on a table with rows. |
| ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'active' | MySQL | Same result. Without a default, MySQL fills old rows with the implicit value (0, empty string). |
| ADD status VARCHAR(20) NOT NULL CONSTRAINT df_users_status DEFAULT 'active' | SQL Server | No COLUMN keyword (ADD COLUMN is a syntax error). Naming the constraint makes it easy to drop later. |
| ADD credits INT NULL CONSTRAINT df_users_credits DEFAULT 10 WITH VALUES | SQL Server | For a nullable column, WITH VALUES fills existing rows; SQL Server leaves them NULL without it. Not run here: Babelfish doesn't support WITH VALUES. |
| RENAME COLUMN nm TO name | SQLite 3.25+, PostgreSQL, MySQL 8.0+ | Standard form. MySQL 5.7 needs CHANGE nm name with the full column definition. |
| EXEC sp_rename 'users.name', 'full_name', 'COLUMN' | SQL Server | Prints a caution that renaming can break scripts and stored procedures; the rename still happens. |
| DROP COLUMN fax | All four | SQLite needs 3.35+ and won't drop indexed, UNIQUE or key columns. |
| ALTER COLUMN status DROP NOT NULL | PostgreSQL, SQLite 3.53 | SET NOT NULL is the reverse and fails if any row is NULL. |
| ALTER COLUMN price TYPE NUMERIC(10,2) USING price::numeric | PostgreSQL | USING is required from TEXT: without it PostgreSQL says the column cannot be cast automatically. |
| MODIFY status VARCHAR(20) NULL DEFAULT 'active' | MySQL | Changes type and nullability. Restate the whole definition: a DEFAULT you leave out is dropped. |
| ALTER COLUMN status VARCHAR(20) NULL | SQL Server | Repeat the type; NOT NULL in place of NULL makes the column required again. |
| ALTER COLUMN price SET DEFAULT 0 | PostgreSQL, MySQL | Changes the default for future inserts only. SQLite needs the rebuild. |
Frequently asked questions
How do I add a column with a default value to an existing table?
Run ALTER TABLE users ADD COLUMN status TEXT NOT NULL DEFAULT 'active'; (in SQL Server, ALTER TABLE users ADD status VARCHAR(20) NOT NULL DEFAULT 'active';). Every row already in the table gets 'active' straight away, and later inserts that leave the column out get it too.
Why does adding a NOT NULL column fail?
The rows already in the table need a value for it, and without a default they would hold NULL. SQLite says Cannot add a NOT NULL column with default value NULL, and PostgreSQL says the column contains null values. Add a DEFAULT, or add the column as nullable, fill it with an UPDATE, then make it NOT NULL.
How do I rename a column in SQL?
Use ALTER TABLE products RENAME COLUMN nm TO name; in SQLite 3.25+, PostgreSQL and MySQL 8.0+. In SQL Server, call EXEC sp_rename 'products.nm', 'name', 'COLUMN';. SQLite also updates the indexes, views and triggers that use the column.
How do I make a column nullable?
PostgreSQL: ALTER TABLE t ALTER COLUMN c DROP NOT NULL; (recent SQLite, such as the 3.53.4 used here, accepts the same). MySQL: ALTER TABLE t MODIFY c VARCHAR(20) NULL;, restating the type and any default. SQL Server: ALTER TABLE t ALTER COLUMN c VARCHAR(20) NULL;. On older SQLite, rebuild the table.
Can I add a column with DEFAULT CURRENT_TIMESTAMP in SQLite?
Only while the table is empty. Once it has rows, SQLite rejects it with Cannot add a column with non-constant default, because it would have to compute a value for every existing row. Add the column with no default and fill it with an UPDATE, or rebuild the table with the default in its definition.
How do I change a column type in SQLite?
SQLite has no ALTER COLUMN … TYPE. Rebuild the table: create items_new with the new type, copy the rows with INSERT INTO items_new SELECT … (with a CAST where needed), DROP TABLE items, then ALTER TABLE items_new RENAME TO items, all in one transaction with foreign keys turned off, and recreate the indexes, triggers and views afterwards.
Run it yourself
Open any of these in the SQLite playground to change the table, run it again and share it.
SQLite playground