> ## 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.

# Register a Snowflake Dataset from SQL

> Register a Snowflake table or view as a Narrative dataset by calling a stored procedure, without opening the Native App Configuration screen

`code.register_dataset` registers a Snowflake table or view as a Narrative dataset in code, without launching the Native App's **Configuration** screen — from a worksheet, the Snowflake SQL API, or a scheduled task. Use it to script dataset onboarding, or to run it from the pipeline that already builds the table.

The **Configuration** screen is unchanged and is still the simplest way to register your first dataset. Both routes produce the same dataset, and both depend on the app having been installed and configured once — see [Install the Snowflake Native App](/guides/data-planes/snowflake-native-app-installation) for that setup.

## Prerequisites

* The Snowflake Native App installed, with its external access integration and API token configured. These also create the Narrative [data plane](/concepts/primitives/data-planes) the dataset attaches to.

* At least one warehouse granted to the application, because every job the operator later runs against the dataset — health checks, samples, statistics, deliveries — needs one. This grant cannot be requested through Snowflake's reference framework and has to be run by an `ACCOUNTADMIN`:

  ```sql theme={null}
  grant usage on warehouse my_warehouse to application narrative_data_collaboration;
  ```

* The `dataset_registration` application role, or `app_admin`.

## Grant the registration role

`dataset_registration` carries usage on the two registration procedures and read access to the app's mapping table, and nothing else. The procedures run with owner's rights, so a caller needs no privilege on the source object, the API token, or the app's own state.

```sql theme={null}
grant application role narrative_data_collaboration.dataset_registration to role my_role;
```

<Tip>
  `app_admin` also works, since it holds `dataset_registration`. Prefer the smaller role for anyone whose job is registering datasets — `app_admin` additionally grants full write access to the application's state, every function it exposes, and its compute pools and services.
</Tip>

Neither role confers anything in your account outside the application, and neither can read your tables. Access to your own objects is granted separately, per object, as described next.

## Register a table or view

`code.register_dataset` describes the object, derives its schema, creates and activates the dataset, and returns the new `dataset_id`. There are two ways to give it the source object.

<Tabs>
  <Tab title="By reference (recommended)">
    `system$reference` mints a token that binds the object through Snowflake's reference framework. Nothing is granted to the application, Snowflake lists the binding among the app's references, and deleting the dataset unbinds it again.

    ```sql theme={null}
    call narrative_data_collaboration.code.register_dataset(
      'TABLE',
      system$reference('table', 'mydb.myschema.events', 'persistent', 'select', 'references'),
      'My Events'
    );
    ```

    The nested arguments matter:

    | Argument                 | Meaning                                                                                                                                                                      |
    | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `'table'`                | Snowflake's own object-type check. Must agree with the first argument to `register_dataset`.                                                                                 |
    | `'mydb.myschema.events'` | The object, resolved with your privileges.                                                                                                                                   |
    | `'persistent'`           | The reference scope. The default, `CALL`, expires when the statement ends, and `SESSION` dies with the connection. Only `PERSISTENT` survives for the operator to use later. |
    | `'select', 'references'` | The privileges handed to the app: `select` to read rows, `references` for the metadata that health checks read.                                                              |

    `system$reference` runs as **your** role, so you need `select` and `references` on the object to call it.
  </Tab>

  <Tab title="By fully qualified name">
    Granting the application access directly works too. The trade-off is that the grant is then ordinary Snowflake state: it can be revoked out of band without the app noticing, and there is no binding for Snowflake to show alongside the application's other references.

    ```sql theme={null}
    grant usage on database mydb to application narrative_data_collaboration;
    grant usage on schema mydb.myschema to application narrative_data_collaboration;
    grant select, references on table mydb.myschema.events to application narrative_data_collaboration;

    call narrative_data_collaboration.code.register_dataset(
      'TABLE', 'MYDB.MYSCHEMA.EVENTS', 'My Events');
    ```

    The name must be exactly three identifier parts. Unquoted parts are upper-cased, matching how Snowflake resolves them; quote a part to preserve its case (`mydb.myschema."My Events"`).
  </Tab>
</Tabs>

An object can only be bound **once** per reference. A second `system$reference` for the same table is rejected with *"The same object cannot be added more than once"* — including when the Configuration screen bound it. To register an object that is already bound, pass its alias in place of the reference:

```sql theme={null}
call narrative_data_collaboration.code.register_dataset(
  'VIEW', '5408228b-28cf-4993-a73b-f65f3ec94f14', 'My Events');
```

To register the same table as a second dataset, address it by name rather than by reference.

### Arguments

| Argument          | Required | Description                                                                                                                                                                             |
| ----------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `object_type`     | Yes      | `'TABLE'` or `'VIEW'`, matching what the object actually is — Snowflake rejects a reference whose object type disagrees. Case and surrounding whitespace are ignored.                   |
| `source`          | Yes      | The object to register: a `system$reference` token, a fully qualified name, or a bound alias.                                                                                           |
| `display_name`    | Yes      | The dataset's human-readable name on the platform. Must be unique among registered datasets. The check runs before anything is bound, so a clash cannot leave a stray reference behind. |
| `dataset_name`    | No       | The dataset's machine name. Defaults to `display_name` with every non-word character replaced by an underscore, truncated to 256 characters — `'My Events'` becomes `My_Events`.        |
| `description`     | No       | Free text stored on the dataset.                                                                                                                                                        |
| `schema_override` | No       | A dataset schema to use instead of the inferred one. See [Override the inferred schema](#override-the-inferred-schema).                                                                 |

### What it returns

| Field                  | Description                                                                                                                         |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `dataset_id`           | The dataset's id on the Narrative platform.                                                                                         |
| `name`, `display_name` | As registered.                                                                                                                      |
| `external_id`          | How Narrative addresses the object — either a reference or the qualified name.                                                      |
| `reference_alias`      | The alias the reference was bound under, or the qualified name when there is no reference.                                          |
| `source_kind`          | How `source` was interpreted: `reference`, `alias`, or `name`. Worth checking on a first call.                                      |
| `table_or_view`        | `table` or `view`.                                                                                                                  |
| `schema`               | The schema that was sent.                                                                                                           |
| `unsupported_columns`  | Columns left out because their Snowflake type has no dataset equivalent — `binary`, `geography`, `geometry`, `vector` and the like. |

<Warning>
  `unsupported_columns` is not an error. The dataset registers without those columns, so check this field if a column you expected is missing downstream.
</Warning>

## Override the inferred schema

By default the schema comes from `desc table` or `desc view`, with `variant`, `array` and `object` columns resolved by sampling 1000 rows. `code.infer_dataset_schema` returns what `register_dataset` would infer, without creating anything:

```sql theme={null}
call narrative_data_collaboration.code.infer_dataset_schema('TABLE', 'MYDB.MYSCHEMA.EVENTS');
```

<Note>
  `infer_dataset_schema` takes `object_type` and `source` exactly as `register_dataset` does, which means passing a `system$reference` token here **will** bind the reference. Pass a qualified name or an already-bound alias when you only want to look.
</Note>

Edit what it returns and pass it back as `schema_override`. Inference is then skipped entirely, including the sampling:

```sql theme={null}
call narrative_data_collaboration.code.register_dataset(
  'TABLE',
  'MYDB.MYSCHEMA.EVENTS',
  'My Events',
  'my_events',
  'Events from our warehouse',
  parse_json('{"type":"object","properties":{"ID":{"type":"string"},"COUNT":{"type":"long"}}}')
);
```

The format is Narrative's dataset schema: an object whose `properties` map column names to types, where a type is one of `string`, `long`, `double`, `boolean`, `timestamptz`, `{"type":"array","items":…}`, or `{"type":"object","properties":{…}}`. Property names must match the column names Snowflake reports, which are upper-case unless the column was created quoted. Do not include `file_config` — the procedure adds it.

## Errors

The procedure validates before it changes anything, and every failure names its remedy.

| Message                                              | Meaning                                                                                                                                                                                 |
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `object_type must be 'TABLE' or 'VIEW'`              | Materialized views, stages and the like are not registrable sources.                                                                                                                    |
| `a dataset named '…' is already registered`          | `display_name` is taken. Checked first, before any reference is bound.                                                                                                                  |
| `… is already registered as '…'`                     | This object already has a dataset. Delete that one first if you meant to re-register.                                                                                                   |
| `could not describe table …`                         | For a qualified name, the application has not been granted access, and the message lists the three `grant` statements to run. For a reference or alias, the binding no longer resolves. |
| `'…' is not a bound SOURCE_TABLE reference`          | The alias passed is not bound. The message lists the ones that are.                                                                                                                     |
| `could not bind the SOURCE_TABLE reference`          | Usually the object is already bound. The message lists the bound alias and name pairs, so re-run with the alias of the one you meant.                                                   |
| `this account has no Narrative data plane yet`       | The external access integration and API token steps have not been completed.                                                                                                            |
| `none of the columns of … have a dataset equivalent` | Every column was unsupported. Register a view that projects supported types instead.                                                                                                    |

## Troubleshooting

<AccordionGroup>
  <Accordion title="Unknown function CODE.REGISTER_DATASET">
    The procedure is created once the external access integration is bound, and recreated on every app upgrade. If it is missing, the integration or the API token is not configured. Configure them, then force the pass:

    ```sql theme={null}
    call narrative_data_collaboration.code.reinitialize_api();
    ```

    This returns `external access integration not available` if the integration still is not bound.
  </Accordion>

  <Accordion title="Unknown user-defined function">
    Everything here is a stored procedure, so it is invoked with `CALL` and cannot be used inside a `SELECT` expression. Snowflake reports that as `Unknown user-defined function`, which reads like the procedure is missing when it is only being called the wrong way.
  </Accordion>

  <Accordion title="A registered dataset stops working after the source is dropped or renamed">
    The dataset keeps pointing at the old object. Remove it and register the replacement.
  </Accordion>
</AccordionGroup>

## Remove a dataset

Removing a dataset goes through the Narrative API. `code.call_narrative_api` calls it with the account's configured token, and is granted to `app_admin` only:

```sql theme={null}
call narrative_data_collaboration.code.call_narrative_api('DELETE', '/datasets/12345', null);
```

The operator then removes the app's mapping, and the reference too if there was one. The same procedure inspects a dataset:

```sql theme={null}
call narrative_data_collaboration.code.call_narrative_api('GET', '/datasets/12345', null);

select parse_json(CALL_NARRATIVE_API) from table(result_scan(last_query_id()));
```

***

## Related content

<CardGroup cols={2}>
  <Card title="Install the Snowflake Native App" icon="snowflake" href="/guides/data-planes/snowflake-native-app-installation">
    One-time setup, and registering a dataset through the Configuration screen
  </Card>

  <Card title="Snowflake Native App Reference" icon="book" href="/reference/integrations/snowflake-native-app">
    Capabilities, requirements, privileges, and naming conventions
  </Card>

  <Card title="Data Planes" icon="server" href="/concepts/primitives/data-planes">
    How a customer-hosted data plane executes work
  </Card>

  <Card title="Compute Pools" icon="microchip" href="/concepts/primitives/compute-pools">
    The warehouses that run jobs against your registered datasets
  </Card>
</CardGroup>
