# CREATE TABLE `CREATE TABLE` creates a table that is persisted in durable storage. `CREATE TABLE` defines a table that is persisted in durable storage. In Materialize, you can create: - Read-write tables. With read-write tables, users can read ([`SELECT`]) and write to the tables ([`INSERT`], [`UPDATE`], [`DELETE`]). - ***Private Preview***. Read-only tables from [PostgreSQL sources (new syntax)](/sql/create-source/postgres-v2/). Users cannot be write ([`INSERT`], [`UPDATE`], [`DELETE`]) to these tables. These tables are populated by [data ingestion from a source](/ingest-data/postgres/). Tables in Materialize are similar to tables in standard relational databases: they consist of rows and columns where the columns are fixed when the table is created. Tables can be joined with other tables, materialized views, views, and subsources; and you can create views/materialized views/indexes on tables. [//]: # "TODO(morsapaes) Bring back When to use a table? once there's more clarity around best practices." ## Syntax **Read-write table:** ### Read-write table To create a new read-write table (i.e., users can perform [`SELECT`](/sql/select/), [`INSERT`](/sql/insert/), [`UPDATE`](/sql/update/), and [`DELETE`](/sql/delete/) operations): ```mzsql CREATE [TEMP|TEMPORARY] TABLE [IF NOT EXISTS] ( [NOT NULL][DEFAULT ] [, ...] ) [WITH ( PARTITION BY ( [, ...]) | RETAIN HISTORY [=] FOR )] ; ``` | Syntax element | Description | | --- | --- | | **TEMP** / **TEMPORARY** | *Optional.* If specified, mark the table as temporary. Temporary tables are: - Automatically dropped at the end of the session; - Not visible to other connections; - Created in the special `mz_temp` schema. Temporary tables may depend upon other temporary database objects, but non-temporary tables may not depend on temporary objects. | | **IF NOT EXISTS** | *Optional.* If specified, do not throw an error if the table with the same name already exists. Instead, issue a notice and skip the table creation. | | `` | The name of the table to create. Names for tables must follow the [naming guidelines](/sql/identifiers/#naming-restrictions). | | `` | The name of a column to be created in the new table. Names for columns must follow the [naming guidelines](/sql/identifiers/#naming-restrictions). | | `` | The type of the column. For supported types, see [SQL data types](/sql/types/). | | **NOT NULL** | *Optional.* If specified, disallow _NULL_ values for the column. Columns without this constraint can contain _NULL_ values. | | **DEFAULT ** | *Optional.* If specified, use the `` as the default value for the column. If not specified, `NULL` is used as the default value. | | **WITH ([,...])** | The following ``s are supported: \| Option \| Description \| \|--------\|-------------\| \| `PARTITION BY ( [, ...])` \| {{< include-md file="shared-content/partition-by-option-description.md" >}} \| \| `RETAIN HISTORY ` \| *Optional.* ***Private preview.** This option has known performance or stability issues and is under active development.*
If specified, Materialize retains historical data for the specified duration, which is useful to implement [durable subscriptions](/transform-data/patterns/durable-subscriptions/#history-retention-period).
Accepts positive [interval](/sql/types/interval/) values (e.g., `'1hr'`).\| | **PostgreSQL source table:** ### PostgreSQL source table > **Note:** You must be on **v26+** to use the new syntax. To create a read-only table from a [source](/sql/create-source/) connected (via native connector) to an external PostgreSQL: ```mzsql CREATE TABLE [IF NOT EXISTS] FROM SOURCE (REFERENCE ) [WITH ( TEXT COLUMNS ( [, ...]) | EXCLUDE COLUMNS ( [, ...]) | PARTITION BY ( [, ...]) [, ...] )] ; ``` | Syntax element | Description | | --- | --- | | **IF NOT EXISTS** | *Optional.* If specified, do not throw an error if the table with the same name already exists. Instead, issue a notice and skip the table creation. {{< include-md file="shared-content/create-table-if-not-exists-tip.md" >}} | | `` | The name of the table to create. Names for tables must follow the [naming guidelines](/sql/identifiers/#naming-restrictions). | | `` | The name of the [source](/sql/create-source/) associated with the reference object from which to create the table. | | **(REFERENCE )** | The name of the upstream table from which to create the table. You can create multiple tables from the same upstream table. To find the upstream tables available in your [source](/sql/create-source/), you can use the following query, substituting your source name for ``:
```mzsql SELECT refs.* FROM mz_internal.mz_source_references refs, mz_sources s WHERE s.name = '' -- substitute with your source name AND refs.source_id = s.id; ``` | | **WITH ([,...])** | The following ``s are supported: \| Option \| Description \| \|--------\|-------------\| \| `TEXT COLUMNS ( [, ...])` \|*Optional.* If specified, decode data as `text` for the listed column(s),such as for unsupported data types. See also [supported types](#supported-data-types). \| \| `EXCLUDE COLUMNS ( [, ...])`\| *Optional.* If specified,exclude the listed column(s) from the table, such as for unsupported data types. See also [supported types](#supported-data-types).\| \| `PARTITION BY ( [, ...])` \| {{< include-md file="shared-content/partition-by-option-description.md" >}} \| | For an example, see [Create a table (PostgreSQL source)](/sql/create-table/#create-a-table-postgresql-source). **SQL Server source table:** ### SQL Server source table > **Note:** You must be on **v26+** to use the new syntax. To create a read-only table from a [source](/sql/create-source/) connected (via native connector) to an external SQL Server database: ```mzsql CREATE TABLE [IF NOT EXISTS] FROM SOURCE (REFERENCE ) [WITH ( TEXT COLUMNS ( [, ...]) | EXCLUDE COLUMNS ( [, ...]) | PARTITION BY ( [, ...]) [, ...] )] ; ``` | Syntax element | Description | | --- | --- | | **IF NOT EXISTS** | *Optional.* If specified, do not throw an error if the table with the same name already exists. Instead, issue a notice and skip the table creation. {{< include-md file="shared-content/create-table-if-not-exists-tip.md" >}} | | `` | The name of the table to create. Names for tables must follow the [naming guidelines](/sql/identifiers/#naming-restrictions). | | `` | The name of the [source](/sql/create-source/) associated with the reference object from which to create the table. | | **(REFERENCE )** | The name of the upstream table from which to create the table. You can create multiple tables from the same upstream table. To find the upstream tables available in your [source](/sql/create-source/), you can use the following query, substituting your source name for ``:
```mzsql SELECT refs.* FROM mz_internal.mz_source_references refs, mz_sources s WHERE s.name = '' -- substitute with your source name AND refs.source_id = s.id; ``` | | **WITH ([,...])** | The following ``s are supported: \| Option \| Description \| \|--------\|-------------\| \| `TEXT COLUMNS ( [, ...])` \|*Optional.* If specified, decode data as `text` for the listed column(s),such as for unsupported data types. See also [supported types](#supported-data-types). \| \| `EXCLUDE COLUMNS ( [, ...])`\| *Optional.* If specified,exclude the listed column(s) from the table, such as for unsupported data types. See also [supported types](#supported-data-types).\| \| `PARTITION BY ( [, ...])` \| {{< include-md file="shared-content/partition-by-option-description.md" >}} \| | ## Read-write tables ### Table names and column names Names for tables and column(s) must follow the [naming guidelines](/sql/identifiers/#naming-restrictions). ### Known limitations Tables do not currently support: - Primary keys - Unique constraints - Check constraints See also the known limitations for [`INSERT`](/sql/insert#known-limitations), [`UPDATE`](/sql/update#known-limitations), and [`DELETE`](/sql/delete#known-limitations). ## Source-populated tables > **Note:** You must be on **v26+** to use the new syntax. ### Table names and column names Names for tables and column(s) must follow the [naming guidelines](/sql/identifiers/#naming-restrictions). ### Read-only tables Source-populated tables are read-only tables. Users cannot perform write operations (INSERT/UPDATE/DELETE) on these tables. ### DDL transaction block For performance, when issuing multiple `CREATE TABLE FROM SOURCE...` statements, use within a [transaction block](/sql/begin/#ddl-only-transactions). ### Source-populated tables and snapshotting

Creating the tables from sources starts the snapshotting process. Snapshotting syncs the currently available data into Materialize. Because the initial snapshot is persisted in the storage layer atomically (i.e., at the same ingestion timestamp), you are not able to query the table until snapshotting is complete.

> **Note:** During the snapshotting, the data ingestion for > the existing tables for the same source is temporarily blocked. As such, if > possible, you can resize the cluster to speed up the snapshotting process and > once the process finishes, resize the cluster for steady-state. ### Supported data types **PostgreSQL:** #### PostgreSQL types

Materialize natively supports the following PostgreSQL types (including the array type for each of the types):

  • bool
  • bpchar
  • bytea
  • char
  • date
  • daterange
  • float4
  • float8
  • int2
  • int2vector
  • int4
  • int4range
  • int8
  • int8range
  • interval
  • json
  • jsonb
  • numeric
  • numrange
  • oid
  • text
  • time
  • timestamp
  • timestamptz
  • tsrange
  • tstzrange
  • uuid
  • varchar

Replicating tables that contain unsupported data types is possible via the TEXT COLUMNS option. The specified columns will be treated as text; i.e., will not have the expected PostgreSQL type features. For example:

  • enum: When decoded as text, the implicit ordering of the original PostgreSQL enum type is not preserved; instead, Materialize will sort values as text.

  • money: When decoded as text, resulting text value cannot be cast back to numeric, since PostgreSQL adds typical currency formatting to the output.

**SQL Server:** #### SQL Server types

Materialize natively supports the following SQL Server types:

  • tinyint
  • smallint
  • int
  • bigint
  • real
  • double precision
  • float
  • bit
  • decimal
  • numeric
  • money
  • smallmoney
  • char
  • nchar
  • varchar
  • varchar(max)
  • nvarchar
  • nvarchar(max)
  • sysname
  • binary
  • varbinary
  • json
  • date
  • time
  • smalldatetime
  • datetime
  • datetime2
  • datetimeoffset
  • uniqueidentifier

Replicating tables that contain unsupported data types is possible via the EXCLUDE COLUMNS option for the following types:

  • text
  • ntext
  • image
  • varbinary(max)

Columns with the specified types need to be excluded because SQL Server does not provide the “before” value when said column is updated.

To replicate tables that contain the following unsupported data types:

  • text
  • ntext
  • image
  • varbinary(max)

You can use either the TEXT COLUMNS or the EXCLUDE COLUMNS option.

  • For text and ntext columns:
    • You can use TEXT COLUMNS to expose them as varchar and nvarchar, respectively.
    • You can use EXCLUDE COLUMNS to omit them from replication.
  • For image and varbinary(max) columns:
    • You can use EXCLUDE COLUMNS.
### Handling table schema changes The use of [`CREATE SOURCE`](/sql/create-source/postgres-v2/) with `CREATE TABLE FROM SOURCE` allows for the handling of the upstream DDL changes, specifically adding or dropping columns in the upstream tables, without downtime. For details, see: - [PostgreSQL: Handling upstream schema changes with zero downtime](/ingest-data/postgres/source-versioning/) - [SQL Server: Handling upstream schema changes with zero downtime](/ingest-data/sql-server/source-versioning/) #### Incompatible schema changes

All other schema changes to upstream tables will set the corresponding Materialize tables into an error state, preventing reads from these tables.

To handle incompatible schema changes, drop the affected table DROP TABLE , and then, CREATE TABLE FROM SOURCE to recreate the table with the updated schema.

### Upstream table truncation restrictions

Avoid truncating upstream tables that are being replicated into Materialize. If a replicated upstream table is truncated, the corresponding subsource(s)/table(s) in Materialize becomes inaccessible and will not produce any data until it is recreated.

Instead of truncating, use an unqualified DELETE to remove all rows from the upstream table:

DELETE FROM t;
### Inherited tables

When using PostgreSQL table inheritance, PostgreSQL serves data from SELECTs as if the inheriting tables’ data is also present in the inherited table. However, both PostgreSQL’s logical replication and COPY only present data written to the tables themselves, i.e. the inheriting data is not treated as part of the inherited table.

PostgreSQL sources use logical replication and COPY to ingest table data, so inheriting tables’ data will only be ingested as part of the inheriting table, i.e. in Materialize, the data will not be returned when serving SELECTs from the inherited table.

You can mimic PostgreSQL’s SELECT behavior with inherited tables by creating a materialized view that unions data from the inherited and inheriting tables (using UNION ALL). However, if new tables inherit from the table, data from the inheriting tables will not be available in the view. You will need to add the inheriting tables via CREATE TABLE .. FROM SOURCE and create a new view (materialized or non-) that unions the new table. ## Privileges The privileges required to execute this statement are: - `CREATE` privileges on the containing schema. - `USAGE` privileges on all types used in the table definition. - `USAGE` privileges on the schemas that all types in the statement are contained in. ## Examples ### Create a table (User-populated) The following example uses `CREATE TABLE` to create a new read-write table `mytable` with two columns `a` (of type `int`) and `b` (of type `text` and not nullable): ```mzsql CREATE TABLE mytable (a int, b text NOT NULL); ``` Once a user-populated table is created, you can perform CRUD (Create/Read/Update/Write) operations on it. The following example uses [`INSERT`](/sql/insert/) to write two rows to the table: ```mzsql INSERT INTO mytable VALUES (1, 'hello'), (2, 'goodbye') ; ``` The following example uses [`SELECT`](/sql/select/) to read all rows from the table: ```mzsql SELECT * FROM mytable; ```The results should display the two rows inserted: ```hc {hl_lines="3-4"} | a | b | | - | ------- | | 1 | hello | | 2 | goodbye | ``` ### Create a table (PostgreSQL source) > **Note:** You must be on **v26+** to use the new syntax. > The example assumes you have configured your upstream PostgreSQL 11+ (i.e., > enabled logical replication, created the publication for the various tables and > replication user, and updated the network configuration). > For details about configuring your upstream system, see the [PostgreSQL > integration guides](/ingest-data/postgres/#supported-versions-and-services). To create new **read-only** tables from a source table, use the `CREATE TABLE ... FROM SOURCE ... (REFERENCE )` statement in a [DDL transaction block](/sql/begin/#ddl-only-transactions). The following example creates **read-only** tables `items` and `orders` from the PostgreSQL source's `public.items` and `public.orders` tables (the schema is `public`). {{< note >}} - Although the example creates the tables with the same names as the upstream tables, the tables in Materialize can have names that differ from the referenced table names. - For supported PostgreSQL data types, refer to [supported types](/sql/create-table/#supported-data-types). {{< /note >}} ```mzsql /* This example assumes: - In the upstream PostgreSQL, you have defined: - replication user and password with the appropriate access. - a publication named `mz_source` for the `items` and `orders` tables. - In Materialize: - You have created a secret for the PostgreSQL password. - You have defined the connection to the upstream PostgreSQL. - You have used the connection to create a source. For example (substitute with your configuration): CREATE SECRET pgpass AS ''; -- substitute CREATE CONNECTION pg_connection TO POSTGRES ( HOST '', -- substitute DATABASE , -- substitute USER , -- substitute PASSWORD SECRET pgpass -- [, ] ); CREATE SOURCE pg_source FROM POSTGRES CONNECTION pg_connection ( PUBLICATION 'mz_source' -- substitute ); */ BEGIN; CREATE TABLE items FROM SOURCE pg_source(REFERENCE public.items) ; CREATE TABLE orders FROM SOURCE pg_source(REFERENCE public.orders) ; COMMIT; ``` {{< include-md file="shared-content/create-table-from-source-snapshotting.md" >}} {{< include-md file="shared-content/create-table-if-not-exists-tip.md" >}} Source-populated tables are read-only tables. Users cannot perform write operations (INSERT/UPDATE/DELETE) on these tables. Once the snapshotting process completes and the table is in the running state, you can query the table: ```mzsql SELECT * FROM items; ``` ## Related pages - [`INSERT`] - [`DROP TABLE`](/sql/drop-table) [`INSERT`]: /sql/insert/ [`SELECT`]: /sql/select/ [`UPDATE`]: /sql/update/ [`DELETE`]: /sql/delete/