The ALTER Command

1

The ALTER Command

Once a database table has been created and populated with real data, the need to change its structure is almost inevitable. Business requirements evolve, new features demand additional information, and occasionally a design decision made early on turns out to be too restrictive or simply wrong. The ALTER TABLE command is the SQL mechanism that allows you to make those structural changes — adding new columns, modifying existing ones, dropping obsolete columns, and managing constraints — all without destroying the rows of data already stored in the table. Understanding ALTER TABLE thoroughly is essential for any developer or database administrator who works with live systems.

ALTER TABLE belongs to the Data Definition Language (DDL) category of SQL commands, alongside CREATE TABLE and DROP TABLE. DDL commands modify the schema — the structural blueprint of the database — rather than the data itself. Because DDL changes are applied immediately and are auto-committed in most database systems (MySQL, Oracle, SQL Server), they take effect the moment the statement executes. There is no implicit transaction wrapper that lets you roll them back with a simple ROLLBACK in those environments, so care and preparation are critical before issuing ALTER TABLE on a production database. PostgreSQL is a notable exception: its DDL statements participate in transactions and can be rolled back, which gives developers an important safety net.

The defining characteristic that sets ALTER TABLE apart from DROP TABLE followed by CREATE TABLE is data preservation. When you alter a table, all existing rows survive the structural change. You are reshaping the container, not discarding its contents. This makes ALTER TABLE the correct tool whenever the goal is to evolve a design rather than replace it entirely.

Adding Columns with ALTER TABLE

The most common use of ALTER TABLE is adding a new column to accommodate new information. The syntax uses an ADD clause followed by the column definition, which includes the column name, its data type, and any optional constraints such as NOT NULL, DEFAULT, or UNIQUE.

-- Add a single new column to an existing table
ALTER TABLE employees
ADD hire_date DATE;

-- Add a column with a default value
ALTER TABLE employees
ADD is_active BOOLEAN DEFAULT TRUE;

-- Add a column with a NOT NULL constraint and a default to satisfy existing rows
ALTER TABLE employees
ADD department_code VARCHAR(10) NOT NULL DEFAULT 'UNASSIGNED';

When a new column is added, it is appended to the end of the column list by default in most databases. MySQL is an exception: it allows you to specify FIRST or AFTER column_name to control the position of the new column, which can be useful for readability but has no impact on data integrity or query behavior.

The behavior for existing rows is critical to understand. Every row that existed before the ALTER TABLE statement ran will now have a value in the new column. If you provided a DEFAULT, each existing row receives that default value immediately. If you did not provide a default and the column allows NULLs, each existing row receives NULL. If you attempt to add a NOT NULL column without a default value, the database will typically refuse because it cannot assign a valid value to the existing rows — they would immediately violate the constraint.

Scenario Column Allows NULL? DEFAULT Provided? Value in Existing Rows Statement Succeeds?
ADD col VARCHAR(50) Yes No NULL Yes
ADD col VARCHAR(50) DEFAULT 'N/A' Yes Yes 'N/A' Yes
ADD col INT NOT NULL DEFAULT 0 No Yes 0 Yes
ADD col INT NOT NULL No No Cannot assign a value No (error)

Modifying Existing Columns

Over time you may need to change the properties of a column that already exists — perhaps the original VARCHAR length is too short, a column that was nullable should now be required, or a default value needs updating. The syntax for modifying a column varies across database platforms more than any other ALTER TABLE operation.

-- SQL Server / MS SQL syntax
ALTER TABLE products
ALTER COLUMN description VARCHAR(500);

-- MySQL syntax
ALTER TABLE products
MODIFY COLUMN description VARCHAR(500) NOT NULL;

-- Oracle syntax
ALTER TABLE products
MODIFY (description VARCHAR2(500));

-- PostgreSQL syntax
ALTER TABLE products
ALTER COLUMN description TYPE VARCHAR(500);

The safest type of modification is widening a character or numeric column — for example, changing VARCHAR(50) to VARCHAR(100), or SMALLINT to INT. This is safe because no existing value can be too large for the new, larger container. The database simply updates its metadata and all existing data remains intact.

Narrowing a column (e.g., VARCHAR(200) to VARCHAR(50)) is dangerous. If any existing row contains a value longer than 50 characters, the database will refuse the change and raise an error. You must either clean up the data first, truncating long values, or reconsider whether narrowing is truly necessary. Changing a data type incompatibly — such as from VARCHAR to INT when the column contains non-numeric text — will similarly fail because the existing data cannot be converted.

Changing nullability is another common modification. Adding NOT NULL to a column that currently contains NULL values will fail. The typical approach is to first update all NULLs to a sensible value, then apply the NOT NULL constraint:

-- Step 1: Replace existing NULLs
UPDATE employees
SET middle_name = ''
WHERE middle_name IS NULL;

-- Step 2: Now safely add the NOT NULL constraint
ALTER TABLE employees
ALTER COLUMN middle_name VARCHAR(50) NOT NULL;

Dropping Columns with ALTER TABLE

When a column is no longer needed, it can be removed with the DROP COLUMN clause. This is a permanent, destructive operation — all data stored in that column across every row is deleted immediately and cannot be recovered from the table itself.

-- Standard syntax supported by most databases
ALTER TABLE employees
DROP COLUMN fax_number;

-- MySQL supports dropping multiple columns in one statement
ALTER TABLE employees
DROP COLUMN fax_number,
DROP COLUMN pager_number;

Before dropping a column, you must consider dependencies. Most databases will refuse to drop a column that is:

  • Part of a PRIMARY KEY constraint
  • Referenced by a FOREIGN KEY in another table
  • Included in an index
  • Referenced in a CHECK constraint or computed column definition
  • Used in a view or stored procedure (some databases allow this but the dependent objects break)

In such cases you must first drop or modify the dependent objects before you can drop the column. In PostgreSQL, the CASCADE option can automate this, but it should be used with extreme caution because it may silently remove constraints and dependent objects you did not intend to delete:

-- PostgreSQL CASCADE example -- use with caution
ALTER TABLE employees
DROP COLUMN department_id CASCADE;

Best practice before any column drop is to run a dependency query against the database's system catalog to identify every object that references the column, and to back up the data in that column (e.g., by exporting it to a separate archive table) if there is any possibility it will be needed in the future.

Adding Constraints with ALTER TABLE

Constraints can be added to an existing table to enforce data integrity rules that were not defined at creation time, or that have become necessary as the application evolved. The ADD CONSTRAINT clause is used for this purpose.

-- Adding a PRIMARY KEY constraint
ALTER TABLE orders
ADD CONSTRAINT pk_orders PRIMARY KEY (order_id);

-- Adding a UNIQUE constraint
ALTER TABLE employees
ADD CONSTRAINT uq_employees_email UNIQUE (email);

-- Adding a CHECK constraint
ALTER TABLE products
ADD CONSTRAINT chk_price_positive CHECK (price > 0);

-- Adding a FOREIGN KEY constraint
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer
  FOREIGN KEY (customer_id)
  REFERENCES customers(customer_id);

-- Adding a NOT NULL constraint (syntax varies by database)
-- SQL Server:
ALTER TABLE employees
ALTER COLUMN last_name VARCHAR(100) NOT NULL;

The most important rule for adding constraints to an existing table is this: the current data must already satisfy the constraint. When you issue ADD CONSTRAINT, the database immediately validates every existing row against the new rule. If even one row violates it, the entire ALTER TABLE statement fails and the constraint is not added. For example, if you try to add a UNIQUE constraint on an email column but the table already contains two rows with the same email address, you will receive a constraint violation error.

This makes named constraints strongly preferable to unnamed ones. When you name a constraint (using the CONSTRAINT keyword followed by a meaningful name), you can reference it precisely when you need to drop or modify it later. Unnamed constraints receive a system-generated name that varies by database vendor and is often opaque and hard to discover:

-- Named constraint -- easy to manage
ADD CONSTRAINT fk_orders_customer FOREIGN KEY (customer_id) REFERENCES customers(customer_id);

-- Unnamed constraint -- harder to drop or reference later
ADD FOREIGN KEY (customer_id) REFERENCES customers(customer_id);

Foreign key constraints added via ALTER TABLE enforce referential integrity between two existing tables. Once in place, the database will prevent inserting a value into the child table's foreign key column if no matching value exists in the parent table's referenced column, and will prevent deleting a row from the parent table if child rows depend on it (unless ON DELETE CASCADE or ON DELETE SET NULL is specified).

Dropping Constraints with ALTER TABLE

Constraints can be removed when the rules they enforce are no longer appropriate for the data model. The syntax for dropping a constraint uses its name:

-- Dropping a named constraint (works across most databases)
ALTER TABLE orders
DROP CONSTRAINT fk_orders_customer;

-- Dropping a PRIMARY KEY (SQL Server)
ALTER TABLE orders
DROP CONSTRAINT pk_orders;

-- Dropping a PRIMARY KEY (MySQL)
ALTER TABLE orders
DROP PRIMARY KEY;

-- Dropping a UNIQUE constraint (MySQL uses DROP INDEX)
ALTER TABLE employees
DROP INDEX uq_employees_email;

Dropping a PRIMARY KEY requires special care because it is often referenced by foreign keys in child tables. Most databases will refuse to drop a primary key if foreign keys pointing to it still exist. The correct sequence is to first drop all foreign key constraints in the child tables that reference this primary key, then drop the primary key itself. Attempting to skip this step will result in an error.

Dropping a NOT NULL or CHECK constraint relaxes the validation rules going forward. Existing data is not retroactively altered, but new inserts and updates will no longer be checked against the removed rule. This is sometimes necessary during a data migration phase where you need to load imperfect data before cleaning it, but it is a deliberate loosening of data quality controls and should be documented accordingly.

Constraint Type Effect of Dropping Special Considerations
PRIMARY KEY Rows no longer have a guaranteed unique identifier Must drop referencing FOREIGN KEY constraints first
FOREIGN KEY Referential integrity between tables is no longer enforced Child table may accumulate orphaned rows after drop
UNIQUE Duplicate values in the column are now permitted Also drops the underlying unique index
CHECK The custom validation rule is no longer applied Existing violating data is not flagged retroactively
NOT NULL NULL values are now permitted in the column Syntax varies significantly across database platforms

Evolving Table Designs Without Losing Data

In real-world systems, schema changes happen in production environments where the database is being actively used by applications and users. Careful planning is what separates a smooth schema evolution from an outage or data loss incident.

One key strategy is backward-compatible changes first. Adding a nullable column or a column with a default value is backward compatible — existing queries and application code that do not reference the new column continue to work without modification. In contrast, dropping a column or renaming one is immediately breaking if any existing query or application code references it by name. A safer pattern for renaming is to add the new column, copy the data, update the application to use the new column name, then drop the old column in a later deployment once the application no longer references it.

Some databases allow multiple ALTER TABLE operations in a single statement. This can reduce the number of times the table is locked and restructured, which matters for large tables where each ALTER TABLE locks the table (in older MySQL versions, for example) and prevents concurrent writes:

-- MySQL: combine multiple changes in one statement to minimize locking
ALTER TABLE employees
  ADD COLUMN middle_name VARCHAR(50),
  ADD COLUMN linkedin_url VARCHAR(255),
  DROP COLUMN fax_number,
  MODIFY COLUMN phone_number VARCHAR(20) NOT NULL DEFAULT '';

Modern database systems like MySQL 5.6+ and PostgreSQL support online DDL operations that allow reads and writes to continue while some types of ALTER TABLE are in progress, but not all operations are online-safe, and the rules vary by version and operation type. For very large tables, tools like pt-online-schema-change (Percona) or gh-ost (GitHub) are used to perform schema migrations with near-zero downtime by working on a shadow copy of the table.

Version-controlled migration scripts are the professional standard for tracking schema changes across a team and across environments (development, staging, production). Tools such as Flyway, Liquibase, and Django's built-in migrations store each schema change as a numbered script that is applied in order. This approach ensures that every environment can be brought to exactly the same schema state, changes are peer-reviewed before deployment, and the history of every structural modification is preserved in source control alongside the application code. A typical migration script for adding a column might look like this:

-- Migration V012__add_hire_date_to_employees.sql
ALTER TABLE employees
ADD COLUMN hire_date DATE;

UPDATE employees
SET hire_date = '2000-01-01'
WHERE hire_date IS NULL;

ALTER TABLE employees
ALTER COLUMN hire_date DATE NOT NULL;

This three-step pattern — add the column as nullable, populate it with data, then tighten the constraint — is a widely adopted safe migration pattern that works correctly whether the table has zero rows or tens of millions. It is the practical expression of the principle that evolving a live schema is not a single atomic action but a carefully sequenced series of steps that keep the data valid and the application running at every point along the way.

NotesCovers ALTER TABLE comprehensively: DDL nature and auto-commit behavior, adding/modifying/dropping columns, adding/dropping constraints, and safe schema evolution strategies. Includes syntax examples for MySQL, PostgreSQL, SQL Server, and Oracle where they differ. Tables summarise key behavioral rules for quick reference. Migration script pattern (add nullable → populate → tighten) is highlighted as a professional best practice.