> For the complete documentation index, see [llms.txt](https://docs.slingdata.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.slingdata.io/concepts/build/models.md).

# Models

A model is a `.sql` file with a SELECT query that defines a table or view in the target database. Models can use Jinja templating for dynamic SQL, reference other models for dependency resolution, and declare configuration with front-matter or a config block.

## Config Declaration

There are two ways to declare model configuration. If both are present, YAML front-matter takes priority and the `config()` block becomes a no-op.

### YAML Front-Matter (Recommended)

Sling reads front-matter from the leading comment of a model file. Any standard SQL comment style is accepted, so the file stays valid for editors and linters.

**Quick guide:**

* **A few keys?** Use `-- { ... }` on a single line — it stays out of the way.
* **More config (hooks, ranges, tests, multi-line lists)?** Use `/* { ... } */` or `/** ... **/` for a proper block.

#### `-- { ... }` line comment (best for short config)

Inline `{ ... }` flow object — good when you need only a key or two:

```sql
-- {schema: marts}
SELECT id, name FROM raw_customers
```

```sql
-- {mode: incremental, unique_key: id, update_key: updated_at}
SELECT id, name, updated_at FROM raw_customers WHERE {{ incremental_where_cond() }}
```

You can also span multiple `--` lines, as long as the joined body forms a `{ ... }` object:

```sql
-- {
--   mode: incremental,
--   unique_key: id,
--   update_key: updated_at
-- }
SELECT id, name, updated_at FROM raw_customers WHERE {{ incremental_where_cond() }}
```

#### `/* { ... } */` block comment (best for richer config)

Use a regular block comment with a `{ ... }` body when you have hooks, ranges, or anything that is easier in multi-line YAML:

```sql
/* {
  mode: incremental,
  unique_key: id,
  update_key: updated_at,
  hooks: {
    start: [{type: log, message: "starting"}],
    end:   [{type: log, message: "done"}]
  }
} */
SELECT id, name, updated_at FROM raw_customers WHERE {{ incremental_where_cond() }}
```

JSON syntax also works in `/* ... */`:

```sql
/* {"mode": "view", "schema": "marts"} */
SELECT * FROM {{ ref('stg_customers') }}
```

#### `/** ... **/` doc-block (plain YAML, no braces required)

A doc-block lets you write plain YAML, with bare keys, and no `{ }` around it. Use this when you prefer block YAML over flow syntax:

```sql
/**
mode: incremental
unique_key: id
merge_strategy: delete+insert
update_key: updated_at
tags:
  - daily
  - finance
tests:
  - not_null: [id, updated_at]
  - unique: id
hooks:
  start:
    - type: query
      connection: '{target.name}'
      query: "REFRESH MATERIALIZED VIEW upstream_mv"
  end:
    - type: log
      message: "Model build complete"
**/

SELECT id, name, updated_at
FROM {{ ref('stg_customers') }}
WHERE {{ incremental_where_cond() }}
```

{% hint style="info" %}
**Why the `{ ... }` requirement for the `--` and `/* */` styles?** It separates config from a regular comment. A line like `-- Pre-statement: build temp table` would otherwise look like YAML and be read as front-matter. The `/** ... **/` doc-block style is unambiguous and accepts any YAML, including bare keys.
{% endhint %}

### Jinja Config Block (dbt Compatibility)

Use `{%- config(...) -%}` for a syntax that dbt users know. Sling applies it only when the file has no YAML front-matter.

```sql
{%- config(mode='incremental', unique_key='id', update_key='updated_at') -%}

SELECT id, name, updated_at
FROM {{ ref('stg_customers') }}
{% if is_incremental() %}
WHERE updated_at > (SELECT MAX(updated_at) FROM {{ this }})
{% endif %}
```

{% hint style="warning" %}
The `config()` block accepts a **subset** of the keys. You cannot set `range`, `hooks`, or `tests` in `config()` — use YAML front-matter for those. Unknown keys give a warning and are ignored. A `schema` set in `config()` also does not change the resolved table name, because Sling computes the table name before it renders the template. Use front-matter `schema:` instead.
{% endhint %}

### Config Priority

1. YAML front-matter (`/** ... **/`, `-- {...}`, or `/* {...} */`)
2. Jinja `config()` block (ignored if front-matter exists)
3. `sling_build.yml` `defaults` section
4. Built-in default: `full-refresh`

`tags` and `hooks` are additive: model values are added to the `defaults` values instead of replacing them.

## Config Options

| Option           | Type           | Default         | Description                                                                                                             |
| ---------------- | -------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `mode`           | string         | `full-refresh`  | Materialization mode. See [Modes](#materialization-modes)                                                               |
| `materialized`   | string         |                 | dbt alias for `mode`. Accepts `table`, `view`, `incremental`. An explicit `mode` wins                                   |
| `unique_key`     | string or list |                 | Primary key column(s) for incremental merge. Also the ClickHouse `ORDER BY`                                             |
| `merge_strategy` | string         | `delete+insert` | Merge strategy: `delete+insert`, `update+insert`, `insert`                                                              |
| `update_key`     | string         |                 | Watermark column. Required for `mode: incremental`                                                                      |
| `tags`           | list           | `[]`            | Tags for [selector](/concepts/build/structure.md#selectors) filtering                                                   |
| `hooks`          | object         |                 | Start/end [hooks](/concepts/hooks.md) to run before and after the model. See [Hooks](#hooks)                            |
| `tests`          | list           | `[]`            | Declarative data tests. See [Data Tests](#data-tests)                                                                   |
| `schema`         | string         | (from folder)   | Override the schema derived from the folder structure                                                                   |
| `database`       | string         |                 | Override the database. Three-part dialects only. See [Database](/concepts/build/structure.md#database-three-part-names) |
| `enabled`        | bool           | `true`          | Set to `false` to remove this model from the DAG                                                                        |
| `engine`         | string         | `MergeTree()`   | ClickHouse `ENGINE` clause override                                                                                     |
| `range`          | object         |                 | Range block for incremental models: `start` / `advance` / `lookback`. See [Incremental](/concepts/build/incremental.md) |
| `drop_cascade`   | bool           | `false`         | Add `CASCADE` to `DROP TABLE` / `DROP VIEW` when the dialect supports it                                                |
| `rewrite`        | bool           | `true`          | Set `false` to skip bare-name table rewriting. `ref()`, `src()`, and `@name` still resolve                              |

{% hint style="warning" %}
`pre_hook` and `post_hook` are not supported. Sling gives an error if it finds them. Use `hooks.start` and `hooks.end` instead.
{% endhint %}

## Materialization Modes

Each model has a materialization mode that sets how Sling writes its SQL output to the target database.

| Mode           | Description                                                           |
| -------------- | --------------------------------------------------------------------- |
| `full-refresh` | Default. Replaces the table with `CREATE TABLE AS SELECT`             |
| `truncate`     | Truncates the table and re-inserts. Keeps DDL, GRANTs, and indexes    |
| `incremental`  | Merges new and updated rows through a temp table and a merge strategy |
| `append`       | Appends the full result set with `INSERT INTO`. No deduplication      |
| `view`         | Creates or replaces a SQL view instead of a table                     |

Sling also accepts these aliases:

| Alias       | Resolves to    | Note                                                  |
| ----------- | -------------- | ----------------------------------------------------- |
| `table`     | `full-refresh` | dbt compatibility                                     |
| `snapshot`  | `append`       | Deprecated. Gives a warning                           |
| `ephemeral` |                | Not supported. Gives an error — use `view` or `table` |

{% hint style="warning" %}
`snapshot` is deprecated and means append-only insert. Use `append`. A future release will make `snapshot` mean SCD2.
{% endhint %}

Sling validates the mode at project load, before it runs anything. An unrecognized mode gives an error that lists the valid values:

> model '\<name>': unknown mode 'incremenal'; expected one of: append, full-refresh, incremental, truncate, view

The same check applies to `defaults.mode` in `sling_build.yml`:

> \<path>/sling\_build.yml: unknown defaults.mode 'bogus'; expected one of: append, full-refresh, incremental, truncate, view

### full-refresh

The default mode. Replaces the table on every run.

| Run        | SQL Behavior                                             |
| ---------- | -------------------------------------------------------- |
| First      | `CREATE TABLE {table} AS ({sql})`                        |
| Subsequent | `DROP TABLE {table}` → `CREATE TABLE {table} AS ({sql})` |

**When to use**: Dimension tables, lookup tables, and small tables where a full reload is fast.

```sql
/**
mode: full-refresh
**/

SELECT id, name, category
FROM {{ ref('stg_products') }}
```

If a view of the same name exists, Sling drops it first.

### truncate

Similar to `full-refresh`, but keeps the existing table structure: columns, constraints, permissions, and indexes.

| Run        | SQL Behavior                                                |
| ---------- | ----------------------------------------------------------- |
| First      | `CREATE TABLE {table} AS ({sql})`                           |
| Subsequent | `TRUNCATE TABLE {table}` → `INSERT INTO {table} SELECT ...` |

**When to use**: When you must keep GRANTs, indexes, or other DDL applied to the table.

### incremental

Merges new and updated rows into the target table. Needs a `unique_key` and an `update_key`.

| Run        | SQL Behavior                                                           |
| ---------- | ---------------------------------------------------------------------- |
| First      | Falls back to `full-refresh` (creates the table)                       |
| Subsequent | Load into a temp table → merge into the target with the merge strategy |

Sling supports two styles of incremental models: **dbt-compatible** (`is_incremental()` + `{{ this }}`) and **sling-native** (`{{ incremental_where_cond() }}` + optional `range:` front-matter). See [Incremental](/concepts/build/incremental.md) for the full comparison.

#### Merge Strategies

| Strategy                  | Behavior                                                       | Best For                            |
| ------------------------- | -------------------------------------------------------------- | ----------------------------------- |
| `delete+insert` (default) | DELETE matching rows by `unique_key`, then INSERT all new rows | All databases, including ClickHouse |
| `update+insert`           | UPDATE existing rows, INSERT new rows (SQL MERGE)              | Postgres, Snowflake, BigQuery       |
| `insert`                  | Append only — no deduplication                                 | Event logs, append-only tables      |

An unrecognized value falls back to `delete+insert`.

```sql
/**
mode: incremental
unique_key: id
merge_strategy: delete+insert
update_key: created_at
**/

SELECT id, name, amount, created_at
FROM {{ ref('stg_orders') }}
WHERE {{ incremental_where_cond() }}
```

{% hint style="info" %}
Use `--full-refresh` to force a full rebuild of incremental models: `sling build run --full-refresh`. This drops and recreates the table, then runs the model with no incremental condition.
{% endhint %}

### append

Appends the complete result set to the target table on every run. No deduplication.

| Run        | SQL Behavior                      |
| ---------- | --------------------------------- |
| First      | `CREATE TABLE {table} AS ({sql})` |
| Subsequent | `INSERT INTO {table} SELECT ...`  |

**When to use**: Audit tables, change history, and time-series snapshots where you want to track state over time.

```sql
/**
mode: append
**/

SELECT id, status, balance, CURRENT_TIMESTAMP as snapshot_at
FROM {{ ref('stg_accounts') }}
```

### view

Creates a SQL view instead of a materialized table. No data is stored.

| Run       | SQL Behavior                                |
| --------- | ------------------------------------------- |
| Every run | `CREATE OR REPLACE VIEW {table} AS ({sql})` |

**When to use**: Logical groupings, lightweight transformations, or when queries must always show the latest source data.

```sql
/**
mode: view
**/

SELECT id, name, status
FROM {{ ref('stg_customers') }}
```

{% hint style="warning" %}
If a model was a table before and you change its mode to `view`, Sling drops the existing table before it creates the view.
{% endhint %}

### Mode Priority

1. `--full-refresh` CLI flag → forces `full-refresh` for all models
2. Model config (`mode` in front-matter or `config()`)
3. `sling_build.yml` `defaults.mode`
4. Built-in default: `full-refresh`

### Database Specifics

**ClickHouse:**

* Sling adds `ENGINE = MergeTree()` and `ORDER BY` (from `unique_key`) to `CREATE TABLE` statements
* The `engine` config option overrides the default ENGINE clause
* Incremental mode always uses `delete+insert`, because ClickHouse does not support standard SQL MERGE. Any other strategy gives a warning

```sql
/**
mode: incremental
unique_key: id
update_key: updated_at
engine: ReplacingMergeTree(updated_at)
**/

SELECT id, name, updated_at
FROM {{ ref('stg_events') }}
WHERE {{ incremental_where_cond() }}
```

**SQL Server family:** uses `SELECT * INTO ... FROM (...)` instead of CTAS, and `CREATE OR ALTER VIEW` for views.

**DuckDB family:** these are single-writer databases, so Sling forces the thread count to 1.

## Template Functions

### `ref()`

Resolves a model or seed name to its full table name and creates a DAG dependency.

```sql
SELECT *
FROM {{ ref('stg_orders') }}
JOIN {{ ref('country_codes') }} USING (country_code)
```

If `stg_orders` resolves to `staging.stg_orders` and `country_codes` is a seed at `staging.country_codes`, the compiled SQL becomes:

```sql
SELECT *
FROM staging.stg_orders
JOIN staging.country_codes USING (country_code)
```

An unknown name gives the error `ref('<name>'): model or seed not found in project`.

### `src()` / `source()`

References an external table that the build does not manage. This creates no DAG dependency.

```sql
-- Single argument: schema.table
SELECT * FROM {{ src('raw_data.events') }}

-- Two arguments: schema, table
SELECT * FROM {{ source('raw_data', 'events') }}
```

### `this`

Resolves to the model's own full table name. This is a variable, not a function.

```sql
{% if is_incremental() %}
WHERE updated_at > (SELECT MAX(updated_at) FROM {{ this }})
{% endif %}
```

### `is_incremental()`

Returns `true` when all of these are true:

1. The model's mode is `incremental`
2. The target table already exists
3. The `--full-refresh` flag is **not** set

```sql
{%- config(mode='incremental', unique_key='id', update_key='created_at') -%}

SELECT id, name, created_at
FROM {{ ref('stg_orders') }}
{% if is_incremental() %}
WHERE created_at > (SELECT MAX(created_at) FROM {{ this }})
{% endif %}
```

On the first run the table does not exist, so `is_incremental()` returns `false` and the full dataset loads. On later runs, only new rows are selected.

### `incremental_where_cond()`

A Jinja function that returns a `WHERE` clause body bounded by the model's `update_key` range. Sling-native incremental models use it in place of `is_incremental()`. On the first run, or with no state, it returns `1=1`.

```sql
/**
mode: incremental
unique_key: id
update_key: created_at
**/
SELECT id, name, created_at
FROM {{ ref('stg_orders') }}
WHERE {{ incremental_where_cond() }}
```

See [Incremental](/concepts/build/incremental.md) for how Sling resolves the bounds and how to use the `range:` front-matter block.

### `incremental_value()`

A Jinja function that returns the current lower-bound value as a SQL literal (for example `'2024-01-01'`), or `null` on the first run. Use it for non-`WHERE` expressions that need the watermark value directly.

### `@model_name` Shorthand

Prefix a model or seed name with `@` to resolve it to the full table name and add a DAG dependency. This is a shorter form of `ref()`.

```sql
SELECT * FROM @stg_orders
```

Sling ignores `@@name` (MySQL system variables), and leaves an unknown `@name` untouched, so SQL Server `@variable` syntax still works.

### Bare Variable References

Model and seed names also work directly as variables. They resolve to the full table name but do **not** create an explicit DAG dependency. Auto-detection may still find them.

```sql
SELECT * FROM {{ stg_orders }}
```

### User Variables

Variables defined in `sling_build.yml` or passed with `--vars` are top-level template variables.

```yaml
# sling_build.yml
vars:
  start_date: '2024-01-01'
```

```sql
SELECT * FROM orders
WHERE created_at >= '{{ start_date }}'
```

```bash
# Override from CLI
sling build run --vars '{"start_date": "2024-06-01"}'
```

{% hint style="info" %}
Use `{{ my_var }}` directly. There is no `var()` function.
{% endhint %}

## Auto-Detection of References

Sling parses `FROM` and `JOIN` clauses in the compiled SQL to find references to other project models. It adds these detected dependencies to the DAG together with the explicit `ref()` calls.

For example, if your SQL has `FROM staging.stg_orders` and `stg_orders` is a model in the project, Sling adds it as a dependency even with no `ref()` call.

{% hint style="info" %}
Auto-detection supplements `ref()` but does not replace it. Use `ref()`, because it makes sure table names resolve correctly in both dev and prod modes. Set `rewrite: false` on a model to turn off bare-name rewriting.
{% endhint %}

## Multi-Statement SQL

A model file can hold more than one SQL statement, separated by semicolons. Sling splits them into:

* **Pre-statements** — statements before the main SELECT query, for example creating temp tables
* **Model query** — the single SELECT/WITH query used for materialization
* **Post-statements** — statements after the model query, for example cleanup or GRANT

```sql
-- Pre-statement: create a temp staging table
CREATE TEMP TABLE tmp_raw_data AS
SELECT id, name FROM raw_source;

-- Model query (the SELECT used for CTAS/merge)
SELECT id, UPPER(name) as name
FROM tmp_raw_data;

-- Post-statement: cleanup
DROP TABLE IF EXISTS tmp_raw_data;
```

Sling uses SQL classification to find the model query. A file must have **exactly one** SELECT query. Zero or more than one gives an error.

## Hooks

Models support `start` and `end` hooks that run before and after the model. They use the same [hook types](/concepts/hooks.md) as replications and pipelines: query, log, check, http, shell, and others.

Declare hooks in YAML front-matter under the `hooks` key:

```sql
/**
mode: incremental
unique_key: id
update_key: created_at
hooks:
  start:
    - type: query
      connection: postgres
      query: "REFRESH MATERIALIZED VIEW upstream_mv"
    - type: log
      message: "Starting model build"
  end:
    - type: check
      check: execution.status.error == 0
    - type: http
      url: https://slack.webhook/...
      body: "Model built successfully"
**/

SELECT * FROM {{ ref('stg_orders') }}
WHERE {{ incremental_where_cond() }}
```

### Execution Order

When a model has hooks, pre-statements, post-statements, and tests, the order is:

1. **Start hooks** — `hooks.start` from front-matter
2. **Pre-statements** — SQL statements before the model query
3. **Model query** — the SELECT used for materialization
4. **Post-statements** — SQL statements after the model query
5. **Data tests** — `tests` from front-matter
6. **End hooks** — `hooks.end` from front-matter

### Available State Variables

Hooks can use these template variables:

| Variable          | Description                                            |
| ----------------- | ------------------------------------------------------ |
| `model.name`      | Model name, for example `fct_orders`                   |
| `model.schema`    | Model schema, for example `marts`                      |
| `model.full_name` | Full table name, for example `marts.core_fct_orders`   |
| `model.mode`      | Execution mode, for example `incremental`              |
| `target.name`     | Target connection name                                 |
| `store.*`         | Shared key-value store for communication between hooks |
| `timestamp.*`     | Date and time fields (`YYYY`, `MM`, `DD`, and others)  |

## Data Tests

Declare data tests in the `tests` front-matter key. Each test compiles to a query that counts violating rows against the materialized table. A count above 0 fails the model, and its downstream models are skipped.

```sql
/**
mode: full-refresh
tests:
  - not_null: [id, customer_id]
  - unique: id
  - accepted_values:
      column: status
      values: [pending, shipped, delivered]
  - expr: sum(amount) >= 0
**/

SELECT id, customer_id, status, amount
FROM {{ ref('stg_orders') }}
```

### Test Types

| Test              | Syntax                                              | Checks                                            |
| ----------------- | --------------------------------------------------- | ------------------------------------------------- |
| `not_null`        | `not_null: id` or `not_null: [id, name]`            | No listed column is NULL                          |
| `unique`          | `unique: id` or `unique: [a, b]`                    | The column set has no duplicate rows              |
| `accepted_values` | `accepted_values: {column: status, values: [a, b]}` | Non-null values of the column are all in the list |
| `expr`            | `expr: sum(amount) >= 0`                            | The SQL expression is true over the table         |

{% hint style="warning" %}
The `expr` test injects your expression into the query without validation. Use it only with SQL you control.
{% endhint %}

### Running Tests Only

Use `sling build test` to run the data tests and skip materialization:

```bash
sling build test
sling build test -s "tag:critical"
```

In this mode Sling walks the selected DAG, skips all seeds, and runs only the tests. Models with no `tests` are no-ops and report OK.

{% hint style="info" %}
`tests` is a front-matter-only key. You cannot set it in a `config()` block.
{% endhint %}

## Macros

Macros are reusable Jinja blocks that remove repeated SQL patterns across models. Define them in `.macros.sql` files. Sling makes them available to models by directory scope.

### Defining Macros

Create a file that ends in `.macros.sql` with one or more `{% macro %}` definitions.

{% code title="utils.macros.sql" %}

```sql
{% macro cents_to_dollars(column_name) %}
    ({{ column_name }} / 100.0)
{% endmacro %}

{% macro safe_divide(numerator, denominator) %}
    CASE WHEN {{ denominator }} = 0 THEN NULL ELSE {{ numerator }}::float / {{ denominator }} END
{% endmacro %}
```

{% endcode %}

{% hint style="info" %}
Sling treats files that end in `.macros.sql` as macro files, not models. It does not compile or execute them against the database.
{% endhint %}

### Using Macros in Models

Call macros in any model SQL with standard Jinja syntax.

```sql
SELECT
    id,
    {{ cents_to_dollars('amount_cents') }} as amount_dollars,
    {{ safe_divide('revenue', 'num_orders') }} as avg_order_value
FROM {{ ref('stg_orders') }}
```

Compiles to:

```sql
SELECT
    id,
    (amount_cents / 100.0) as amount_dollars,
    CASE WHEN num_orders = 0 THEN NULL ELSE revenue::float / num_orders END as avg_order_value
FROM staging.stg_orders
```

### Scoping Rules

Macros are scoped by directory. A macro file is available to models in the same directory and all child directories.

| Macro File Location                        | Available To                                   |
| ------------------------------------------ | ---------------------------------------------- |
| Root-level, for example `utils.macros.sql` | All models in the project                      |
| `staging/helpers.macros.sql`               | Models in `staging/` and its subdirectories    |
| `marts/core/core_utils.macros.sql`         | Models in `marts/core/` and its subdirectories |

```
my_project/
├── utils.macros.sql                    # global — available everywhere
├── staging/
│   ├── helpers.macros.sql              # scoped — staging/ only
│   ├── stg_orders.sql                  # can use: utils + helpers
│   └── stg_customers.sql               # can use: utils + helpers
└── marts/
    └── core/
        └── fct_orders.sql              # can use: utils only (not helpers)
```

### Macro Name Conflicts

When the same macro name exists at more than one scope level, the **closest scope wins**. Sling prints a warning in debug mode when it finds macro shadowing.

```
WARN: macro 'clean_string' in staging/helpers.macros.sql shadows definition in utils.macros.sql
```

Sling loads all `.macros.sql` files in a directory. It concatenates them root-first, then by directory, then alphabetically by filename, for deterministic behavior.

## Seeds

Seeds are static data files that Sling loads into the target database before models execute. Use them for reference data, lookup tables, and test fixtures.

### Supported Formats

| Format  | Extension  | Notes                                |
| ------- | ---------- | ------------------------------------ |
| CSV     | `.csv`     | Parsed with automatic type inference |
| JSON    | `.json`    | Nested structures are flattened      |
| Parquet | `.parquet` | Schema kept from file metadata       |

Extension matching is not case-sensitive. Compressed and other formats, such as `.csv.gz`, `.tsv`, or `.xlsx`, are not supported as seeds.

### Placement and Naming

Seed files follow the same naming rules as models. The first folder sets the schema. The file name is the table name.

**Flat structure** (default): seeds are together with models in schema folders.

```
staging/
├── stg_orders.sql           # model → staging.stg_orders
└── country_codes.csv        # seed  → staging.country_codes
```

**DBT-compatible structure** (`dbt_project: true`): seeds go in the `seeds/` directory.

```
seeds/
└── staging/
    └── country_codes.csv    # seed → staging.country_codes
```

See [Structure](/concepts/build/structure.md) for the full naming rules.

### Loading Behavior

* Seeds always load in **full-refresh** mode. This ignores `defaults.mode`
* Sling uses its task infrastructure to load them, so you get type inference, bulk loading, and all 30+ connectors
* Seeds execute at depth 0 in the DAG, before any models

### Referencing Seeds

Reference seeds in models with `ref()`, the same as any other model.

{% code title="staging/country\_codes.csv" %}

```csv
code,name,region
US,United States,North America
GB,United Kingdom,Europe
JP,Japan,Asia
```

{% endcode %}

{% code title="marts/dim\_countries.sql" %}

```sql
SELECT
    code as country_code,
    name as country_name,
    region
FROM {{ ref('country_codes') }}
```

{% endcode %}

The `ref('country_codes')` resolves to `staging.country_codes` and adds a DAG dependency, so the seed loads before the model executes.

### Skipping Seeds

Use `--no-seeds` to skip seed loading.

```bash
sling build run --no-seeds
```

Seeds show as `SKIP` in the progress output. Downstream models still execute — a skipped seed does **not** cascade as a failure.

### Seeds in the DAG

* Seeds have no dependencies and are always at depth 0
* More than one seed can execute in parallel when `--threads` is above 1
* You can select and exclude seeds with `--select` and `--exclude`, the same as models
* A `tag:` selector never matches a seed, because tags apply to models only

## Complete Example

```sql
/**
mode: incremental
unique_key: [order_id, line_id]
merge_strategy: delete+insert
update_key: updated_at
tags:
  - daily
  - core
tests:
  - not_null: [order_id, line_id]
  - unique: [order_id, line_id]
hooks:
  start:
    - type: query
      connection: '{target.name}'
      query: "DELETE FROM marts.core_fct_orders WHERE is_cancelled = true"
  end:
    - type: log
      message: "Model fct_orders built successfully"
**/

SELECT
    o.id as order_id,
    ol.id as line_id,
    o.customer_id,
    {{ cents_to_dollars('ol.amount_cents') }} as amount_dollars,
    o.updated_at
FROM {{ ref('stg_orders') }} o
JOIN {{ ref('stg_order_lines') }} ol ON o.id = ol.order_id
WHERE {{ incremental_where_cond() }}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.slingdata.io/concepts/build/models.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
