> ## Documentation Index
> Fetch the complete documentation index at: https://docs.narrative.io/llms.txt
> Use this file to discover all available pages before exploring further.

# INSERT

> Reference for the INSERT statement

The `INSERT` statement adds rows to an existing dataset. NQL supports two forms: `INSERT ... VALUES (...)` for literal rows, and `INSERT ... SELECT ...` for inserting the result of a query.

<Note>
  INSERT runs against datasets you own. To create a new dataset from a query, use [CREATE MATERIALIZED VIEW](/nql/commands/create-materialized-view). To upsert into an existing materialized view during refresh, use the [MERGE ON clause](/guides/nql/incremental-upserts).
</Note>

## Syntax

### VALUES form

```sql theme={null}
INSERT INTO table_reference (column1, column2, ...)
VALUES
  (value1, value2, ...)
  [, (value1, value2, ...), ...]
```

### SELECT form

```sql theme={null}
INSERT INTO table_reference (column1, column2, ...)
SELECT
  column1,
  column2,
  ...
FROM source_reference
[WHERE condition]
```

## Explicit column list is required

The column list after `INSERT INTO table_reference` is required. NQL does not infer columns from the dataset schema, and wildcards (`SELECT *`) are not supported in the SELECT form either.

```sql theme={null}
-- Correct
INSERT INTO company_data."123" (user_id, email)
VALUES (42, 'alice@example.com')

-- Not supported — missing column list
INSERT INTO company_data."123"
VALUES (42, 'alice@example.com')
```

See [Explicit column selection](/nql/general/explicit-columns) for the rationale and how it applies across NQL.

## Source restrictions for the SELECT form

When you use `INSERT ... SELECT`, the source query can read only from datasets and views your company owns. The following sources are rejected:

* Datasets owned by another company (including those shared with you through access rules)
* Access rules (e.g., `provider_slug."access_rule_name"`)
* `narrative.rosetta_stone`

If you need to ingest data from a shared access rule, materialize it first with [`CREATE MATERIALIZED VIEW`](/nql/commands/create-materialized-view), then `INSERT ... SELECT` from your materialized view.

Subqueries inside the SELECT — including joins, CTEs, and `IN (SELECT ...)` predicates — are subject to the same restriction.

## Column matching and types

* The number of expressions in each `VALUES` row (or columns in the `SELECT` clause) must match the column list.
* Expressions are matched to columns by position, not by name.
* Each expression must be assignable to the target column's [data type](/nql/data-types). Implicit coercions follow the same rules as comparisons elsewhere in NQL.

## Examples

**Single-row VALUES:**

```sql theme={null}
INSERT INTO company_data."123" (dataset_id, status, created_at)
VALUES (456, 'created', CURRENT_TIMESTAMP)
```

**Multi-row VALUES:**

```sql theme={null}
INSERT INTO company_data.pipeline_log (dataset_id, status, created_at)
VALUES
  (101, 'created', CURRENT_TIMESTAMP),
  (102, 'created', CURRENT_TIMESTAMP),
  (103, 'skipped', CURRENT_TIMESTAMP)
```

**INSERT from a SELECT:**

```sql theme={null}
INSERT INTO company_data."456" (user_id, email, created_at)
SELECT
  company_data."123".user_id,
  company_data."123".email,
  company_data."123".created_at
FROM company_data."123"
WHERE company_data."123".created_at >= CURRENT_DATE - INTERVAL '7' DAY
```

**INSERT with transformations and a join:**

```sql theme={null}
INSERT INTO company_data."789" (user_id, email, region)
SELECT
  u.user_id,
  LOWER(u.email) AS email,
  g.region
FROM company_data."123" AS u
JOIN company_data."456" AS g
  ON u.user_id = g.user_id
WHERE u.is_active = TRUE
```

## Execution context

INSERT statements can run in two places:

| Execution surface                                                                      | When to use                                         |
| -------------------------------------------------------------------------------------- | --------------------------------------------------- |
| NQL query endpoint                                                                     | Ad-hoc inserts from an API client or Data Studio    |
| [`ExecuteDml` workflow task](/guides/workflows/workflow-orchestration#supported-tasks) | Scheduled or event-driven inserts inside a workflow |

INSERT does not create datasets. The target dataset must already exist. Use `CREATE MATERIALIZED VIEW` to create datasets from a query, then `INSERT` to add rows afterward.

<Tip>
  For bulk loads from one dataset to another, `INSERT ... SELECT` is almost always faster and cheaper than issuing many single-row `INSERT ... VALUES` statements.
</Tip>

***

## Related content

<CardGroup cols={2}>
  <Card title="UPDATE" icon="pen" href="/nql/commands/update">
    Modify existing rows in a dataset
  </Card>

  <Card title="DELETE" icon="trash" href="/nql/commands/delete">
    Remove rows from a dataset
  </Card>

  <Card title="Incremental Upserts with MERGE ON" icon="code-merge" href="/guides/nql/incremental-upserts">
    Update or insert rows during materialized view refresh
  </Card>

  <Card title="CREATE MATERIALIZED VIEW" icon="list-check" href="/nql/commands/create-materialized-view">
    Create a new dataset from a query
  </Card>

  <Card title="ExecuteDml workflow task" icon="diagram-project" href="/guides/workflows/workflow-orchestration#supported-tasks">
    Run DML statements from a workflow
  </Card>

  <Card title="Data Types" icon="shapes" href="/nql/data-types">
    Type rules for INSERT expressions
  </Card>
</CardGroup>
