Alter a Table

Times change, needs change, and so do the requirements of your database. In the previous chapter we looked at how to create a new table in our database. There may be situations, however, where you need to alter an existing table's defintion in some way. It may be the case that you need to alter a column's name, add a new column, change the data type of a column, or remove the table altogether. DDL provides a way for you to make such changes.

It is important to consider how schema changes will affect the data in a table. Adding an extra column to a table will add an extra column to all existing rows, just as it would in a spreadsheet. Deleting a column means all data in that column will be lost forever. Before jumping in to alter your table, take extra time to think about how the change will affect your existing data.

Alter Table Syntax

Existing tables can be altered with an ALTER TABLE statement. An ALTER TABLE statement is part of DDL, and is for altering a table schema only; we'll look at updating data in a table later in this book.

The basic format of an ALTER TABLE statement is:

ALTER TABLE table_to_change HOW TO CHANGE THE TABLE additional arguments

In a moment we'll work through some specific examples of using the ALTER TABLE statement, but first let's remind ourselves of what our table currently looks like. Remember that within the psql console we can use the meta-command \d followed by a table name to describe that table. Let's do that now for our users table.

sql_book=# \d users
        Table "public.users"
  Column  |     Type      |  Modifiers
----------+---------------+----------------------------------------------------
 id       | integer       | not null default nextval('users_id_seq'::regclass)
 username | character(25) |
 enabled  | boolean       | default true
Indexes:
    "users_id_key" UNIQUE CONSTRAINT, btree (id)

Renaming a Table

One example of a change we might want to make to a table is to rename the table itself. A table can be renamed using the RENAME clause. We then need to specify what we want to rename the table to using the TO clause followed by the new name. Let's rename our users table to all_users.

sql_book=# ALTER TABLE users
sql_book-# RENAME TO all_users;
ALTER TABLE

On the first line above we run our SQL statement. The ALTER TABLE on the second line is the response we receive. This response is pretty generic; other than telling us our statement was executed without error, it doesn't really tell us what's changed. If we now try to describe our users table however, we get the following response.

sql_book=# \d users
Did not find any relation named "users".

PostgreSQL is unable to find a relation (i.e. table) called users since it has been renamed. If we use \d all_users instead, we can see the description of what used to be called the users table; all that's changed is the name of the table.

sql_book=# \d all_users
        Table "public.all_users"
  Column  |     Type      |  Modifiers
----------+---------------+----------------------------------------------------
 id       | integer       | not null default nextval('users_id_seq'::regclass)
 username | character(25) |
 enabled  | boolean       | default true
Indexes:
   "users_id_key" UNIQUE CONSTRAINT, btree (id)

Renaming a Column

As well as renaming an entire table, you can also use the RENAME clause to rename a specific column within the table. For example, we have been storing full names in the username column, and we may decide to rename it to full_name. The syntax is very similar to that for renaming a table, except that in between the RENAME and TO clauses, we need to use the COLUMN clause, passing it the name of the column we want to change.

sql_book=# ALTER TABLE all_users
sql_book-# RENAME COLUMN username TO full_name;
ALTER TABLE

Again we receive the generic ALTER TABLE response, but can use \d all_users to see that the column name has been changed.

sql_book=# \d all_users
        Table "public.all_users"
  Column   |     Type      |  Modifiers
-----------+---------------+----------------------------------------------------
 id        | integer       | not null default nextval('users_id_seq'::regclass)
 full_name | character(25) |
 enabled   | boolean       | default true
Indexes:
   "users_id_key" UNIQUE CONSTRAINT, btree (id)

Changing a Column's Datatype

Remember when we created our table, there were two mandatory pieces of information that we needed to supply about each column: name and data type. We've already seen how to change a column's name, but there may also be situations where you need to change the column's data type. We again do this by using an ALTER TABLE statement, but this time in conjunction with ALTER COLUMN to target a specific column for change. In the statement below, the datatype of full_name is changed from CHAR(25) to VARCHAR(25).

sql_book=# ALTER TABLE all_users
sql_book-# ALTER COLUMN full_name TYPE VARCHAR(25);
ALTER TABLE

Now when we describe all_users, we have a field username with type VARCHAR(25).

sql_book=# \d all_users
                Table "public.all_users"
   Column   |          Type         |    Modifiers
------------+-----------------------+----------------------------------------------------
  id        | integer               | not null default nextval('users_id_seq'::regclass)
  full_name | character varying(25) |
  enabled   | boolean               | default true
Indexes:
  "users_id_key" UNIQUE CONSTRAINT, btree (id)

Adding a Constraint

When we created our table, we explained that column names and data types are a required part of each column defintion, whereas constraints are optional. If we want to change a column's name or data type we need to alter the existing definition for those things. Constraints are a little different in that rather than changing them we add them to, or remove them from, the column definition (note: there is an ALTER CONSTRAINT clause that can be used to change certain aspects of Foreign Key constraints, but most of the time you will be adding or removing constraints). Another difference with constraints is that whereas a column can only have one name and one data type, it can have more than one constraint.

Our id column in our all_users table already has a NOT NULL constraint, which means that a null value cannot be input to that column as part of a database record. It woudl be useful to apply this same safeguard to our full_name column. That column currently does not have a NOT NULL constraint; let's fix that.

The syntax for adding constraints can vary depending on the type of constraint we're adding. Some types of constraint are considered 'table constraints' (even if they apply to a specific column) and others, such as NOT NULL and DEFAULT are considered 'column constraints'.

The form for adding a column constraint is as follows:

ALTER TABLE table_name ALTER COLUMN column_name SET CONSTRAINT CLAUSE

Whereas the form for adding a table constraint is:

ALTER TABLE table_name ADD CONSTRAINT constraint_name CONSTRAINT CLAUSE

We'll look a bit more closely at this second form in later in the book, for now let's add our NOT NULL constraint to our full_name column.

sql_book=# ALTER TABLE all_users
sql_book-# ALTER COLUMN full_name SET NOT NULL;
ALTER TABLE

Let's check what our table looks like after executing this statement.

sql_book=# \d all_users
                Table "public.all_users"
   Column   |          Type         |    Modifiers
------------+-----------------------+----------------------------------------------------
  id        | integer               | not null default nextval('users_id_seq'::regclass)
  full_name | character varying(25) | not null
  enabled   | boolean               | default true
Indexes:
  "users_id_key" UNIQUE CONSTRAINT, btree (id)

Removing a Constraint

Just as we can add constraints to a table after creating it, we may also remove them. The syntax for removing a constraint also has a couple of forms, depending on the type of constraint.

The form for removing a column constraint is as follows:

ALTER TABLE table_name ALTER COLUMN column_name DROP CONSTRAINT

Whereas to remove a table constraint the form is:

ALTER TABLE table_name DROP CONSTRAINT constraint_name

When use specified a data type of serial for our id column, this automatically added a DEFAULT constraint that uses nextval function to set id to the next available value if no value is specified. This is quite a useful set-up for our id column, as we'll see later when we start adding data to our table. If we did want to remove the DEFAULT constraint on the id column we could do it like this:

sql_book=# ALTER TABLE all_users
sql_book-# ALTER COLUMN id DROP DEFAULT;

After executing this statement, our table should look like this:

sql_book=# \d all_users
                Table "public.all_users"
   Column   |          Type         |    Modifiers
------------+-----------------------+-----------------------------
 id         | integer               | not null
 full_name  | character varying(25) | not null
 enabled    | boolean               | default true
Indexes:
   "users_id_key" UNIQUE CONSTRAINT, btree (id)

Adding a Column

So far we've looked at changing the definition of existing columns in our table. There may also be situations where you need to add an entirely new column. The product manager for our application has decided that we need to record the last login time for all of our users; let's meet this requirement by adding a last_login column to our all_users table.

If you need to add a column to the table you've created, one that was not specified in the original schema, you can use an ADD COLUMN clause in an ALTER TABLE statement.

Run this command in your psql console and follow along to see how the database changes.

ALTER TABLE all_users
  ADD COLUMN last_login timestamp NOT NULL DEFAULT NOW();

The way we define a column when adding a new column to a table is essentially the same as the way we define a column when creating a table: we need to define a column name, a data type, and any optional constraints. The command above adds a new column to the all_users table with the name last_login, a datatype of timestamp and a default value based on the function NOW(). Let's see what our table looks like with the new column by running the meta command \d and using the table name as an argument:

sql_book=# \d users
                  Table "public.all_users"
   Column   |            Type             |     Modifiers
------------+-----------------------------+----------------------------
 id         | integer                     | not null
 full_name  | character varying(25)       | not null
 enabled    | boolean                     | default true
 last_login | timestamp without time zone | not null default now()
Indexes:
    "users_id_key" UNIQUE CONSTRAINT, btree (id)

NOW() is an SQL function. It provides the current date and time when it is called. There are many such functions available and we will look at some of the common ones in subsequent chapters.

Removing a Column

Just as we can add new columns to existing tables, we also remove columns. The command to remove a column from a table also uses the ALTER TABLE clause. Say for example we wanted to remove the enabled column from the users table.

sql_book=# ALTER TABLE all_users DROP COLUMN enabled;
ALTER TABLE

If we describe our table now, we can see that there are only three columns, and enabled is not one of them.

sql_book=# \d users
                  Table "public.all_users"
   Column   |            Type             |     Modifiers
------------+-----------------------------+----------------------------
 id         | integer                     | not null
 full_name  | character varying(25)       | not null
 last_login | timestamp without time zone | not null default now()
Indexes:
    "users_id_key" UNIQUE CONSTRAINT, btree (id)

Dropping Tables

As well as renaming a table, altering the columns within it, and adding or removing columns, we can also remove a table from our database completely.

Deleting a table has a relatively straightforward command and the syntax for deleting a table is much like the command for dropping a database, DROP. Run the statement and command below on your psql console. If you try to describe the deleted table, you will get an error.

sql_book=# DROP TABLE all_users;
DROP TABLE
sql_book=# \d all_users
Did not find any relation named "all_users".

Caution: Actions such as DROP COLUMN and DROP TABLE are not reversible. Currently we don't have any data in our table, but if we did and we performed one of these actions, we would lost all of the data in that table or column. Always perform such actions with care.

Summary

Whoa! We covered a lot of ground in this chapter! We've explored the general syntax for ALTER TABLE and also looked at numerous ways in which we can alter an existing table:

  • Renaming a table
  • Renaming a column
  • Changing a column's data type
  • Adding a constraint
  • Removing a constraint
  • Adding a column
  • Removing a column
  • Dropping a table

Although the SQL statements for all of these actions use the same initial ALTER TABLE clause, the specific syntax for each varies according to the action. Let's quickly recap:

Action Command Notes
Add a column to a table ALTER TABLE table_name ADD COLUMN column_name data_type CONSTRAINTS; Alters a table by adding a column with with a specified data type and optional constraints.
Alter a column's data type ALTER TABLE table_name ALTER COLUMN column_name TYPE data_type; Alters the table by changing the datatype of column.
Rename a table ALTER TABLE table_name RENAME TO new_table_name; Changes the name of a table in the currently connected to database.
Rename a column within a table ALTER TABLE table_name RENAME COLUMN column_name TO new_column_name; Renames a column of the specified table.
Add column constraint ALTER TABLE table_name ALTER COLUMN column_name SET CONSTRAINT CLAUSE; Adds a specified constraint to the specified table column.
Add table constraint ALTER TABLE table_name ADD CONSTRAINT constraint_name CONSTRAINT CLAUSE; Adds a specified constraint to the specified table.
Remove a column constraint ALTER TABLE table_name ALTER COLUMN column_name DROP CONSTRAINT; Removes a constraint the specified table.
Remove a table constraint ALTER TABLE table_name DROP CONSTRAINT constraint_name; Removes a constraint the specified table.
Remove a column from a table ALTER TABLE table_name DROP COLUMN column_name Removes a column from the specified table.
Delete a table from the database DROP TABLE table_name; permanently deletes the specified table from its database.

In this section of the book we covered how to change the structure and foundation of our database and its relations. Having the database structure in place is only part of the story though; as explained earlier the schema and data work together to provide us with the structured information that we require from our database. In the next section we'll focus on using that new structure to manage data within our database, learning how to select, add, update, and delete data as needed.

Before moving onto the next section, make sure that you have a clear understanding the topics we covered in this one at a conceptual level. You probably don't need to memorize all of the specific syntax we've covered in this set of chapters. The syntax for DDL is generally this is only used at the outset when first creating your database and its tables, which is much less often than when you actually work with the data in those tables.

As long as you have a clear picture of how schema works, you can always refer back to this book or to the official documentation if you need to check on a particular piece of syntax. Over the next few chapters however, you should familiarize yourself as much as possible with the DML syntax which we will cover. The bulk of the time you spend working with databases will be spent manipulating data, so it is important to be as fluent as possible with the relevant commands and clauses.