Skip to main content
The CREATE TABLE creates a new table in a database.
The “ statement performs a schema change. For more information about how online schema changes work in CockroachDB, see .

Required privileges

To create a table, the user must have one of the following:
  • Membership to the role for the cluster.
  • Membership to the role for the database.
  • The on the database.

Synopsis

create_table syntax diagram

Parameters

ParameterDescription
opt_persistence_temp_tableDefines the table as a session-scoped temporary table. For more information, see .

Note that the LOCAL, GLOBAL, and UNLOGGED options are no-ops, allowed by the parser for PostgreSQL compatibility.

Support for temporary tables is .
IF NOT EXISTSCreate a new table only if a table of the same name does not already exist in the database; if one does exist, do not return an error.

Note that IF NOT EXISTS checks the table name only; it does not check if an existing table has the same columns, indexes, constraints, etc., of the new table.
table_nameThe name of the table to create, which must be unique within its database and follow these . When the parent database is not set as the default, the name must be formatted as database.name.

The and statements use a temporary table called excluded to handle uniqueness conflicts during execution. It’s therefore not recommended to use the name excluded for any of your tables.
column_defA comma-separated list of column definitions. Each column requires a and . Column names must be unique within the table but can have the same name as indexes or constraints.

You can optionally specify a column qualification (e.g., a ). Any PRIMARY KEY, UNIQUE, and CHECK defined at the column level are moved to the table-level as part of the table’s creation. Use the statement to view them at the table level.
index_defAn optional, comma-separated list of . For each index, the column(s) to index must be specified; optionally, a name can be specified. Index names must be unique within the table and follow these . See the Create a Table with Secondary Indexes and GIN Indexes example below.

For examples, see Create a table with hash-sharded indexes below.

The statement can be used to create an index separate from table creation.
family_defAn optional, comma-separated list of . Column family names must be unique within the table but can have the same name as columns, constraints, or indexes.

A column family is a group of columns that are stored as a single key-value pair in the underlying key-value store. CockroachDB automatically groups columns into families to ensure efficient storage and performance. However, there are cases when you may want to manually assign columns to families. For more details, see .
table_constraintAn optional, comma-separated list of . Constraint names must be unique within the table but can have the same name as columns, column families, or indexes.
LIKE table_name like_table_option_listCreate a new table based on the schema of an existing table, using supported specifiers. For details, see Create a table like an existing table. For examples, see Create a new table from an existing one.
opt_partition_byAn option that lets you define table partitions at the row level. You can define table partitions by list or by range. See for more information.
opt_localitySpecify a for the table. In order to set a locality, the table must belong to a .
opt_where_clauseAn optional WHERE clause that defines the predicate boolean expression of a .
opt_index_visibleAn optional VISIBLE or NOT VISIBLE clause that indicates whether an index is visible to the . If NOT VISIBLE, the index will not be used in queries unless it is specifically selected with an or the property is overridden with the . For an example, see .

Indexes that are not visible are still used to enforce UNIQUE and FOREIGN KEY . For more considerations, see .
opt_with_storage_parameter_listA comma-separated list of . Supported parameters include fillfactor, s2_max_level, s2_level_mod, s2_max_cells, geometry_min_x, geometry_max_x, geometry_min_y, and geometry_max_y. The fillfactor parameter is a no-op, allowed for PostgreSQL-compatibility.

For details, see . For an example, see .
ON COMMIT PRESERVE ROWSThis clause is a no-op, allowed by the parser for PostgreSQL compatibility. CockroachDB only supports session-scoped , and does not support the clauses ON COMMIT DELETE ROWS and ON COMMIT DROP, which are used to define transaction-scoped temporary tables in PostgreSQL.

Column qualifications

CockroachDB supports the following column qualifications:

ON UPDATE expressions

ON UPDATE expressions update column values in the following cases:
  • An or statement modifies a different column value in the same row.
  • An ON UPDATE CASCADE modifies a different column value in the same row.
ON UPDATE expressions do not update column values in the following cases:
  • An UPDATE or UPSERT statement directly modifies the value of a column with an ON UPDATE expression.
  • An UPSERT statement creates a new row.
  • A new column is backfilled with values (e.g., by a DEFAULT expression).
Note the following limitations of ON UPDATE expressions:
  • ON UPDATE expressions allow context-dependent expressions, but not expressions that reference other columns. For example, the current_timestamp() is allowed, but CONCAT(<column_one, <column_two>) is not.
  • You cannot add a and an ON UPDATE expression to the same column.
For an example of ON UPDATE, see .

Identity columns

Identity columns are columns that are populated with values in a . When you create an identity column, CockroachDB creates a sequence and sets the default value for the identity column to the result of the nextval() on the sequence. To create an identity column, add a GENERATED BY DEFAULT AS IDENTITY/GENERATED ALWAYS AS IDENTITY clause to the column definition, followed by . If you do not specify any sequence options in the column definition, the column assumes the default options of . If you use GENERATED BY DEFAULT AS IDENTITY to define the identity column, any // operations that specify a new value for the identity column will overwrite the default sequential values in the column. If you use GENERATED ALWAYS AS IDENTITY, the column’s sequential values cannot be overwritten. Note the following limitations of identity columns:
  • GENERATED ALWAYS AS IDENTITY/GENERATED BY DEFAULT AS IDENTITY is supported in statements only when the table being altered is empty. Refer to .
  • Unlike PostgreSQL, CockroachDB does not support using the OVERRIDING SYSTEM VALUE clause in INSERT/UPDATE/UPSERT statements to overwrite GENERATED ALWAYS AS IDENTITY identity column values.
For an example of an identity column, see Create a table with an identity column.

NOT VISIBLE property

The NOT VISIBLE property of a column specifies that a column will not be returned when using * in a . You can apply the NOT VISIBLE property only to individual columns. For an example, refer to .

Create a table like an existing table

CockroachDB supports the CREATE TABLE LIKE syntax for creating a new table based on the schema of an existing table. The following options are supported:
  • INCLUDING CONSTRAINTS adds all constraints from the source table.
  • INCLUDING DEFAULTS adds all column expressions from the source table.
  • INCLUDING GENERATED adds all expressions from the source table.
  • INCLUDING INDEXES adds all from the source table.
  • INCLUDING ALL includes all of the specifiers above.
To exclude specifiers, use the EXCLUDING keyword. Excluding specifiers can be useful if you want to use INCLUDING ALL, and exclude just one or two specifiers. The last INCLUDING/EXCLUDING keyword for a given specifier takes priority.
CREATE TABLE LIKE statements cannot copy , , and from existing tables. If you want these column qualifications in a new table, you must recreate them manually.CREATE TABLE LIKE copies all hidden columns (e.g., the hidden column in multi-region tables) from the existing table to the new table.
Supported LIKE specifiers can also be mixed with ordinary CREATE TABLE specifiers. For example:
In this example, table2 is created with the indexes and default values of table1, but not the CHECK constraints, because EXCLUDING CONSTRAINTS was specified after INCLUDING ALL. table2 also includes an additional column and index. For additional examples, see Create a new table from an existing one.

Examples

Create a table

In this example, we create the users table with a single column defined. In CockroachDB, every table requires a . If one is not explicitly defined, a column called rowid of the type INT is added automatically as the primary key, with the unique_rowid() function used to ensure that new rows always default to unique rowid values. The primary key is automatically indexed. For performance recommendations on primary keys, see the page.
If no primary key is explicitly defined in a CREATE TABLE statement, you can add a primary key to the table with or . If the ADD or ALTER statement follows the CREATE TABLE statement, and is part of the same transaction, no default primary key will be created. If the table has already been created and the transaction committed, the ADD or ALTER statements replace the default primary key.

Create a table with secondary and GIN indexes

In this example, we create secondary and GIN indexes during table creation. allow efficient access to data with keys other than the primary key. allow efficient access to the schemaless data in a column.

Create a table with a vector index

Enable vector indexes:
The following statement creates a table with a column, along with a that makes vector search efficient.

Create a table with auto-generated unique row IDs

To auto-generate unique row identifiers, you can use the following : For performance reasons, if you are going to use UUIDs, Cockroach Labs strongly recommends using UUIDv4 as defined by RFC 4122. This is the format generated by the . Other types of UUID are largely untested with CockroachDB and will require performance testing to avoid hotspots.

Use gen_random_uuid()

To use the column with the gen_random_uuid() as the :
For performance, CockroachDB defaults to skipping uniqueness checks for gen_random_uuid() due to the near-zero probability of UUID collisions. UUID uniqueness checks can be enabled by setting the cluster setting to true.

Use uuid_v4()

Alternatively, you can use the column with the uuid_v4() function as the default value:
In either case, generated IDs will be 128-bit, sufficiently large to generate unique values. Once the table grows beyond a single key-value range’s , new IDs will be scattered across all of the table’s ranges and, therefore, likely across different nodes. This means that multiple nodes will share in the load. This approach has the disadvantage of creating a primary key that may not be useful in a query directly, which can require a join with another table or a secondary index.

Use unique_rowid()

If it is important for generated IDs to be stored in the same key-value range, you can use an with the unique_rowid() as the default value, either explicitly or via the :
Upon insert or upsert, the unique_rowid() function generates a default value from the timestamp and ID of the node executing the insert. Such time-ordered values are likely to be globally unique except in cases where a very large number of IDs (100,000+) are generated per node per second. Also, there can be gaps and the order is not completely guaranteed. In multi-region deployments with tables, uniqueness checks can add latency due to cross-partition validation. You can improve performance by setting the skip_unique_checks index storage parameter to true, but this is only recommended if the application can guarantee uniqueness. For more information, refer to . To understand the differences between the UUID and unique_rowid() options, see the . For further background on UUIDs, see What is a UUID, and Why Should You Care?.

Create a table with a foreign key constraint

guarantee a column uses only values that already exist in the column it references, which must be from another table. This constraint enforces referential integrity between the two tables. There are a that govern foreign keys, but the most important rule is that referenced columns must contain only unique values. This means the REFERENCES clause must use exactly the same columns as a or constraint. You can include a to specify what happens when a column referenced by a foreign key constraint is updated or deleted. The default actions are ON UPDATE NO ACTION and ON DELETE NO ACTION. In this example, we use ON DELETE CASCADE (i.e., when row referenced by a foreign key constraint is deleted, all dependent rows are also deleted).

Create a table with a check constraint

In this example, we create the users table, but with some column . One column is the , and another column is given a and a that limits the length of the string. Primary key columns and columns with unique constraints are automatically indexed.

Create a table that mirrors key-value storage

CockroachDB is a distributed SQL database built on a transactional and strongly-consistent key-value store. Although it is not possible to access the key-value store directly, you can mirror direct access using a “simple” table of two columns, with one set as the primary key:
When such a “simple” table has no indexes or foreign keys, /// statements translate to key-value operations with minimal overhead (single digit percent slowdowns). For example, the following UPSERT to add or replace a row in the table would translate into a single key-value Put operation:
This SQL table approach also offers you a well-defined query language, a known transaction model, and the flexibility to add more columns to the table if the need arises.

Create a table from a SELECT statement

You can use the statement to create a new table from the results of a SELECT statement. For example, suppose you have a number of rows of user data in the users table, and you want to create a new table from the subset of users that are located in New York.

Create a table with a computed column

In this example, let’s create a simple table with a computed column:
Then, insert a few rows of data:
The full_name column is computed from the first_name and last_name columns without the need to define a .

Create a table with a hash-sharded primary index

We . If a table must be indexed on sequential keys, use . Hash-sharded indexes distribute sequential traffic uniformly across ranges, eliminating single-range and improving write performance on sequentially-keyed indexes at a small cost to read performance. Let’s create the products table and add a hash-sharded primary key on the ts column:

Create a table with a hash-sharded secondary index

Let’s now create the events table and add a secondary index on the ts column in a single statement:

Create a new table from an existing one

Create a table including all supported source specifiers

Note that the foreign key constraint fk_owner_id_ref_users in the source table is not included in the new table.

Create a table with some source specifiers and a foreign key constraint

Create a table in a multi-region database

To create a table with a specific in a , add a LOCALITY clause to the end of the table’s CREATE TABLE statement.
In order to set table localities, the database that contains the table must have .By default, all tables in a multi-region database have a locality.

Create a table with a global locality

To create a table with a locality, add a LOCALITY GLOBAL clause to the end of the CREATE TABLE statement. The GLOBAL locality is useful for “read-mostly” tables of reference data that are rarely updated, but need to be read with low latency from all regions. For example, the promo_codes table of the is rarely updated after being initialized, but it needs to be read by nodes in all regions.

Create a table with a regional-by-table locality

To create a table with a locality, add a LOCALITY REGIONAL BY TABLE clause to the end of the CREATE TABLE statement.
REGIONAL BY TABLE IN PRIMARY REGION is the default locality for all tables created in a multi-region database.
The REGIONAL BY TABLE locality is useful for tables that require low-latency reads and writes from specific region. For example, suppose you want to create a table for your application’s end users in a specific state:
LOCALITY REGIONAL is an alias for LOCALITY REGIONAL BY TABLE.

Create a table with a regional-by-row locality

To create a table with a locality, add a LOCALITY REGIONAL BY ROW clause to the end of the CREATE TABLE statement. The REGIONAL BY ROW locality is useful for tables that require low-latency reads and writes from different regions, where the low-latency region is specified at the row level. For example, the vehicles table of the is read to and written from nodes in different regions.
CockroachDB will automatically assign each row to a region based on the locality of the node from which the row is inserted. It will then optimize subsequent read and write queries executed from nodes located in the region assigned to the rows being queried.
If the node from which a row is inserted has a locality that does not correspond to a region in the database, then the row will be assigned to the database’s primary region.
To assign rows to regions, CockroachDB creates and manages a hidden , of type crdb_internal_region. To override the automatic region assignment and choose the region in which rows will be placed, you can provide a value for the crdb_region column in INSERT and UPDATE queries on the table.
The region value for crdb_region must be one of the regions added to the database, and present in the crdb_internal_region ENUM. To return the available regions, use a statement, or a statement.
For example:
You can then manually set the values of the region with each statement:
Alternatively, you could update the rows in the crdb_region column to compute the region based on the value of another column, like the city column.

Create a table with a regional-by-row locality, using a custom region column

To create a table with a locality, where the region of each row in a table is based on the value of a specific column that you create, you can add a LOCALITY REGIONAL BY ROW AS <region> clause to the end of the CREATE TABLE statement. Using the LOCALITY REGIONAL BY ROW AS <region> clause, you can assign rows to regions based on the value of any custom column of type crdb_internal_region. For example:
CockroachDB will then assign a region to each row, based on the value of the region column. In this example, the region column is computed from the value of the city column.

Modify the region column or its expression

The following instructions show how to change the mapping of the column that determines row locality for a where the column was already defined with REGIONAL BY ROW AS {column}(#rename-crdb_region)(/docs/v26.3/create-table#create-a-table-with-a-regional-by-row-locality-using-a-custom-region-column). This method .
  1. of the same type (crdb_internal_region) with the updated scalar expression for the computed column:
  2. Atomically so the new computed column takes the original name:
  3. Point the table locality at the new computed column using :
  4. After verifying the changes have occurred (using a query like SELECT region, * FROM app.public.users WHERE ...), :

Create a table with an identity column

Identity columns define a sequence from which to populate a column when a new row is inserted. For example:
CockroachDB creates a sequence to use as the numerical column’s default value.
When a new row is added to the table, CockroachDB populates the numerical column with the result of the nextval('bank_numerical_seq') .
The numerical column in this example follows the BY DEFAULT rule. According to this rule, if the value of an identity is explicitly updated, the sequence value is overwritten:
Inserting explicit values does not affect the next value of the sequence:
If the numerical column were to follow the ALWAYS rule instead, then the sequence values in the column could not be overwritten.

Create a table with data excluded from backup

In some situations, you may want to exclude a table’s row data from a . For example, a table could contain high-churn data that you would like to more quickly than the schedule for the database or cluster that will hold the table. You can use the exclude_data_from_backup = true parameter with CREATE TABLE to mark a table’s row data for exclusion from a backup:
To set exclude_data_from_backup on an existing table, see the example.

See also