# Пользовательские функции (User-defined functions)

Пользовательские функции (UDF) позволяют определять и регистрировать собственные функции непосредственно в вашем хранилище данных. Подобно [макросам](https://docs.getdbt.tech/docs/build/jinja-macros.md), UDF способствуют повторному использованию кода, но в отличие от макросов они являются объектами в хранилище. Это означает, что одну и ту же логику можно использовать не только в dbt, но и в других инструментах — например, в BI-системах, ноутбуках для data science и т.д.

UDF особенно полезны для переиспользования логики в нескольких инструментах, стандартизации сложных бизнес‑расчётов, повышения производительности вычислительно тяжёлых операций (поскольку они компилируются и оптимизируются движком запросов вашего хранилища), а также для контроля версий кастомной логики внутри вашего dbt‑проекта.

dbt создаёт, обновляет и переименовывает UDF в процессе выполнения DAG. UDF создаётся в хранилище до той модели, которая на него ссылается. Подробнее о том, как dbt строит UDF в проекте, см. [listing and building UDFs](https://docs.getdbt.tech/docs/build/udfs.md#listing-and-building-udfs).

Дополнительную информацию о конфигурациях и свойствах UDF см. в разделах [Function properties](https://docs.getdbt.tech/reference/function-properties.md) и [Function configurations](https://docs.getdbt.tech/reference/function-configs.md).

## Предварительные требования

* Убедитесь, что вы используете **v2 Stable** или **v1 Latest** [канала выпуска](https://docs.getdbt.tech/docs/dbt-versions/dbt-release-tracks.md) платформы dbt либо dbt v1.11+.

* Используйте один из следующих адаптеров:

  ### dbt v1

  * BigQuery
  * Snowflake
  * Redshift
  * Postgres
  * Databricks
  * [Starburst/Trino](https://docs.getdbt.tech/reference/resource-configs/trino-configs.md#user-defined-functions) (`dbt-trino` v1.11+)

  ### dbt v2

  * BigQuery
  * Snowflake
  * Redshift
  * Databricks

Поддержка UDF

UDF на JavaScript поддерживаются в dbt v1.12 и выше на Snowflake и BigQuery.

Другие языки (например, Java, Scala) в настоящее время не поддерживаются для UDF.

Полный список поддерживаемых возможностей UDF см. в разделе [Limitations](#limitations) ниже.

## Определение UDF в dbt

В dbt можно определять UDF на SQL, Python и JavaScript (доступны в dbt v1.12+). UDF на Python поддерживаются в Snowflake, BigQuery и Databricks (требуется [Unity Catalog](https://docs.databricks.com/aws/en/data-governance/unity-catalog/)). UDF на JavaScript поддерживаются в Snowflake и BigQuery.

Выполните следующие шаги, чтобы определить UDF в dbt:

1. Создайте файл SQL, Python или JavaScript в каталоге `functions`. Например, эта UDF проверяет, представляет ли строка положительное целое число:

   ### SQL

   Определение SQL UDF в SQL‑файле.

   functions/is\_positive\_int.sql

   ```sql
   # syntax for BigQuery, Snowflake, and Databricks
   REGEXP_INSTR(a_string, '^[0-9]+$')

   # syntax for Redshift and Postgres
   SELECT REGEXP_INSTR(a_string, '^[0-9]+$')

   # syntax for Starburst/Trino
   CASE WHEN regexp_like(a_string, '^[0-9]+$') THEN 1 ELSE 0 END
   ```

   ### Python

   Определите Python UDF в файле Python.

   functions/is\_positive\_int.py

   ```py
   import re

   def main(a_string):
       return 1 if re.search(r'^[0-9]+$', a_string or '') else 0
   ```

   В Databricks содержимое файла `.py` используется как тело UDF без изменений. Databricks выполняет его напрямую, без вызова именованной точки входа. Последней инструкцией тела должен быть `return` верхнего уровня, который возвращает результат. Из-за такого `return` исходник Databricks представляет собой *тело функции*, а не исполняемый модуль `.py`. Например:

   functions/is\_positive\_int.py

   ```py
   import re

   def main(a_string):
       return 1 if re.search(r'^[0-9]+$', a_string or '') else 0

   return main(a_string)
   ```

   **Примечание:** Для Python UDF в Databricks требуется [Unity Catalog](https://docs.databricks.com/aws/en/data-governance/unity-catalog/).

   ### JavaScript

   Определите JavaScript UDF в файле JavaScript.

   functions/is\_positive\_int.js

   ```js
   return /^[0-9]+$/.test(a_string) ? 1 : 0;
   ```

**Примечание**: Вы можете указать конфигурации в блоке config в SQL-файле или в соответствующем YAML-файле свойств на шаге 2.

2. Укажите имя функции и определите конфигурацию, свойства, тип возвращаемого значения и необязательные аргументы в соответствующем YAML-файле свойств.

   ### SQL

   functions/is\_positive\_int.yml

   ```yml
   functions:
     - name: is_positive_int       # required
       description: My UDF that returns 1 if a string represents a naked positive integer (like "10", "+8" is not allowed). # optional
       config:
         schema: udf_schema
         database: udf_db
         volatility: deterministic
       arguments:                  # optional
         - name: a_string          # required if arguments is specified
           data_type: string       # required if arguments is specified
           description: The string that I want to check if it's representing a positive integer (like "10") 
           default_value: "'1'"    # optional, available in Snowflake and Postgres
       returns:                    # required
         data_type: integer        # required
   ```

   Для Starburst/Trino укажите в `data_type` аргумента [тип данных](https://trino.io/docs/current/language/types.html), который поддерживает движок, например `varchar` вместо `string`. Не задавайте `default_value`. В Starburst Galaxy задайте `database: galaxy` и `schema: functions`.

   ### Python

   Следующие конфигурации обязательны при определении Python UDF в Snowflake и BigQuery:

   * [`runtime_version`](https://docs.getdbt.tech/reference/resource-configs/runtime-version.md) — версия Python, в которой будет выполняться код. Поддерживаемые значения:

     * [Snowflake](https://docs.snowflake.com/en/developer-guide/udf/python/udf-python-introduction): `3.10`, `3.11`, `3.12`, `3.13`
     * [BigQuery](https://cloud.google.com/bigquery/docs/user-defined-functions-python): `3.11`

   * [`entry_point`](https://docs.getdbt.tech/reference/resource-configs/entry-point.md) — Укажите функцию Python, которую нужно вызвать.

   <br />

   В Databricks параметры `runtime_version` и `entry_point` принимаются для совместимости между адаптерами, но ни на что не влияют. Databricks самостоятельно управляет средой выполнения Python и напрямую использует тело функции, поэтому dbt выводит предупреждение, если вы задаёте эти параметры.

   С помощью необязательной настройки `packages` можно подключить к Python UDF общедоступные пакеты из PyPI. Укажите имена пакетов, например `numpy` и `pandas`. При необходимости закрепите версии, например `pandas==1.5.0`. Хранилище установит пакеты при создании UDF. В Snowflake некоторые пакеты устанавливаются из репозитория Anaconda. Для их использования может потребоваться [принять условия обслуживания Anaconda](https://docs.snowflake.com/en/developer-guide/udf/python/udf-python-packages#using-third-party-packages-from-anaconda).

   В примере Python UDF заданы обязательные параметры `runtime_version` и `entry_point`, необязательный параметр `packages` и другие распространённые настройки:

   functions/is\_positive\_int.yml

   ```yml
     functions:
       - name: is_positive_int # обязательно
         description: Моя UDF, которая возвращает 1, если строка представляет собой «чистое» положительное целое число (например, "10"; "+8" не допускается). # optional
         config:
           runtime_version: "3.11"   # обязательно для Snowflake и BigQuery; необязательно и игнорируется в Databricks
           entry_point: main         # обязательно для Snowflake и BigQuery; необязательно и игнорируется в Databricks
           packages:                 # необязательно, только для Python UDF
             - numpy
             - pandas==1.5.0
           schema: udf_schema
           database: udf_db
           volatility: deterministic  
         arguments:                   # опционально
           - name: a_string           # обязательно, если указан arguments
             data_type: string        # обязательно, если указан arguments
             description: Строка, по которой я хочу проверить, что она представляет положительное целое число (например, "10")
             default_value: "'1'"     # опционально; доступно в Snowflake и Postgres
         returns:                     # обязательно
           data_type: integer         # обязательно
   ```

   ### JavaScript

   Параметр [`snowflake.quote_args`](https://docs.getdbt.tech/reference/resource-configs/quote_args.md) позволяет указать, следует ли заключать имена аргументов в кавычки при создании JavaScript UDF в Snowflake.

   functions/is\_positive\_int.yml

   ```yml
   functions:
     - name: is_positive_int                    # required
       description: My UDF that returns 1 if a string represents a naked positive integer (like "10", "+8" is not allowed). # optional
       config:
         snowflake:                             # optional
           quote_args: true                     # optional, JavaScript UDFs on Snowflake only
       arguments:                               # optional
         - name: a_string                       # required if arguments is specified
           data_type: string                    # required if arguments is specified
           description: The string to check     # optional
       returns:                                 # required
         data_type: integer                     # required
   ```

Зависимость volatility от хранилища данных

В dbt параметр `volatility` принимается для UDF на SQL, Python и JavaScript, но его обработка зависит от хранилища данных. Для UDF на SQL и Python в BigQuery параметр `volatility` игнорируется, а dbt выводит предупреждение. Для UDF на JavaScript в BigQuery значения `deterministic` и `non-deterministic` применяются при создании UDF; значение `stable` не поддерживается. В Snowflake все поддерживаемые значения `volatility` применяются при создании UDF. Дополнительную информацию см. в разделе [volatility](https://docs.getdbt.tech/reference/resource-configs/volatility.md).

3. Выполните одну из следующих команд `dbt build`, чтобы собрать UDF и создать их в хранилище:

   Сборка всех UDF:

   ```bash
   dbt build --select "resource_type:function"
   ```

   Или сборка конкретной UDF:

   ```bash
   dbt build --select is_positive_int
   ```

При выполнении `dbt build` файл свойств (`functions/is_positive_int.yml`) и соответствующий файл SQL, Python или JavaScript совместно используются для формирования инструкции `CREATE FUNCTION`.

Сформированная инструкция `CREATE FUNCTION` зависит от используемого адаптера. Например:

### SQL

### Snowflake

```sql
CREATE OR REPLACE FUNCTION udf_db.udf_schema.is_positive_int(a_string STRING DEFAULT '1')
RETURNS INTEGER
LANGUAGE SQL
IMMUTABLE
AS $$
  REGEXP_INSTR(a_string, '^[0-9]+$')
$$;
```

### Redshift

```sql
CREATE OR REPLACE FUNCTION udf_db.udf_schema.is_positive_int(a_string VARCHAR)
RETURNS INTEGER
IMMUTABLE
AS $$
  SELECT REGEXP_INSTR(a_string, '^[0-9]+$')
$$ LANGUAGE SQL;
```

### BigQuery

```sql
CREATE OR REPLACE FUNCTION udf_db.udf_schema.is_positive_int(a_string STRING)
RETURNS INT64
AS (
  REGEXP_INSTR(a_string, r'^[0-9]+$')
);
```

### Databricks

```sql
CREATE OR REPLACE FUNCTION udf_db.udf_schema.is_positive_int(a_string STRING)
RETURNS INT
DETERMINISTIC
RETURN REGEXP_INSTR(a_string, '^[0-9]+$');
```

### Postgres

```sql
CREATE OR REPLACE FUNCTION udf_schema.is_positive_int(a_string text DEFAULT '1')
RETURNS int
LANGUAGE sql
IMMUTABLE
AS $$
  SELECT regexp_instr(a_string, '^[0-9]+$')
$$;
```

### Starburst/Trino

```sql
CREATE OR REPLACE FUNCTION galaxy.functions.is_positive_int(a_string VARCHAR)
RETURNS INTEGER
DETERMINISTIC
COMMENT 'My UDF that returns 1 if a string represents a naked positive integer (like "10", "+8" is not allowed).'
RETURN CASE WHEN regexp_like(a_string, '^[0-9]+$') THEN 1 ELSE 0 END;
```

Подробнее см. раздел [Настройки Starburst/Trino](https://docs.getdbt.tech/reference/resource-configs/trino-configs.md#user-defined-functions).

### Python

### Snowflake

```sql
CREATE OR REPLACE FUNCTION udf_db.udf_schema.is_positive_int(a_string STRING DEFAULT '1')
  RETURNS INTEGER
  LANGUAGE PYTHON
  RUNTIME_VERSION = '3.11'
  HANDLER = 'main'
  PACKAGES = ('numpy', 'pandas==1.5.0')
AS $$
import re
def main(a_string):
  return 1 if re.search(r'^[0-9]+$', a_string or '') else 0
$$;
```

### BigQuery

```sql
CREATE OR REPLACE FUNCTION udf_db.udf_schema.is_positive_int(a_string STRING)
RETURNS INT64
LANGUAGE python
OPTIONS(
  runtime_version = "python-3.11",
  entry_point = "main",
  packages = ['numpy', 'pandas==1.5.0']
)
AS r'''
  import re
  def main(a_string):
    return 1 if re.search(r'^[0-9]+$', a_string or '') else 0
''';
```

### Databricks

```sql
CREATE OR REPLACE FUNCTION udf_db.udf_schema.is_positive_int(a_string STRING)
RETURNS INT
LANGUAGE PYTHON
AS $$
  import re
  def main(a_string):
    return 1 if re.search(r'^[0-9]+$', a_string or '') else 0
  return main(a_string)
$$;
```

Databricks не добавляет выражения `RUNTIME_VERSION` и `HANDLER`. Средой выполнения управляет Databricks. Содержимое файла `.py` становится телом функции без изменений, включая завершающую инструкцию `return main(a_string)`, которая возвращает результат.

### JavaScript

### Snowflake

```sql
CREATE OR REPLACE FUNCTION udf_db.udf_schema.is_positive_int("a_string" STRING)
RETURNS INTEGER
LANGUAGE JAVASCRIPT
AS $$
return /^[0-9]+$/.test(a_string) ? 1 : 0;
$$;
```

### BigQuery

```sql
CREATE OR REPLACE FUNCTION udf_db.udf_schema.is_positive_int(a_string STRING)
RETURNS INT64
LANGUAGE js
AS r'''
return /^[0-9]+$/.test(a_string) ? 1 : 0;
''';
```

4. Используйте UDF в модели с помощью макроса `{{ function(...) }}`. Например:

   models/my\_model.sql

   ```sql
   select
       maybe_positive_int_column,
       {{ function('is_positive_int') }}(maybe_positive_int_column) as is_positive_int
   from {{ ref('a_model_i_like') }}
   ```

При использовании [`--defer`](https://docs.getdbt.tech/reference/node-selection/defer.md) `function()` ссылается на существующую UDF в среде, на которую перенаправляются ссылки (например, в production), если функция не выбрана или ещё не создана в вашей целевой среде. Для этого требуется манифест состояния, указанный с помощью `--state` или эквивалентной переменной окружения (например, `DBT_ENGINE_STATE`), который dbt использует, чтобы определить, куда перенаправлять ссылки. Это позволяет успешно запускать модели, зависящие от UDF, в рабочих процессах [непрерывной интеграции](https://docs.getdbt.tech/docs/deploy/continuous-integration.md) и разработки. Подробнее см. в разделе [Настройка выбора на основе состояния](https://docs.getdbt.tech/reference/node-selection/configure-state.md).

5. Выполните `dbt compile`, чтобы увидеть, как формируется ссылка на UDF. В следующем примере `{{ function('is_positive_int') }}` заменяется именем UDF `udf_db.udf_schema.is_positive_int`.

   models/my\_model.sql

   ```sql
   select
       maybe_positive_int_column,
       udf_db.udf_schema.is_positive_int(maybe_positive_int_column) as is_positive
   from analytics.dbt_schema.a_model_i_like
   ```

   В DAG создаётся узел UDF на основе SQL/Python‑файла и YAML‑описания, и появляется зависимость `is_positive_int` → `my_model`.

   [![Узел UDF в графе зависимостей](/img/docs/building-a-dbt-project/UDF-DAG.png?v=2 "Узел UDF в графе зависимостей")](#)Узел UDF в графе зависимостей

После определения UDF ваши изменения применяются к UDF в хранилище при следующем запуске `dbt build`, если вы изменили что-либо из следующего:

* Файл SQL, Python или JavaScript, содержащий тело функции (`is_positive_int.sql`, `is_positive_int.py` или `is_positive_int.js` в этих примерах)
* Её конфигурации
* Её свойства, определённые в файле `.yml` (например, `arguments` или `returns`)

dbt обнаруживает все эти изменения при использовании [`state:modified`](https://docs.getdbt.tech/reference/node-selection/methods.md#state).

### Определение перегруженных UDF

Используйте свойство [`overloads`](https://docs.getdbt.tech/reference/resource-properties/overloads.md) (доступно в dbt v1.12+), чтобы определить несколько сигнатур аргументов для одной функции. Это позволяет вызывать функцию с одним и тем же именем, передавая входные данные разных типов, без создания отдельных UDF для каждого варианта. `overloads` поддерживается для SQL UDF в Snowflake и Postgres, а также для UDF на Python и JavaScript в Snowflake.

Чтобы определить перегруженные UDF:

1. Добавьте список `overloads` в определение функции в YAML-файле свойств. Каждая запись использует `defined_in` для ссылки на отдельный файл и может содержать необязательные свойства `arguments` и `returns`:

   functions/is\_positive\_int.yml

   ```yml
   functions:
     - name: is_positive_int
       arguments:
         - name: a_string
           data_type: string
       returns:
         data_type: integer
       overloads:
         - defined_in: is_positive_int_numeric   # references functions/is_positive_int_numeric.sql
           arguments:
             - name: a_num
               data_type: numeric
           returns:              # optional, inherits from root function if omitted
             data_type: integer
   ```

2. Создайте отдельный файл для тела каждой перегрузки.

   Например, тело основной функции, принимающей аргумент типа `string`:

   functions/is\_positive\_int.sql

   ```sql
   # Snowflake syntax
   REGEXP_INSTR(a_string, '^[0-9]+$')
   ```

   И тело перегрузки, принимающей аргумент типа `numeric`:

   functions/is\_positive\_int\_numeric.sql

   ```sql
   # Snowflake syntax
   CASE WHEN a_num > 0 THEN 1 ELSE 0 END
   ```

Все перегрузки объединяются в один узел DAG (основную функцию), поэтому они создаются и выбираются вместе. При повторной попытке dbt пропускает успешно созданные перегрузки и повторно запускает только те, при создании которых произошла ошибка. При создании функции dbt формирует отдельный оператор `CREATE FUNCTION` для каждой перегрузки, используя одно и то же имя функции, но разные типы аргументов.

Дополнительную информацию см. в разделе [`overloads`](https://docs.getdbt.tech/reference/resource-properties/overloads.md).

## Использование UDF в unit‑тестах

Вы можете использовать [unit tests](https://docs.getdbt.tech/docs/build/unit-tests.md) для проверки моделей, которые ссылаются на UDF. Перед запуском unit‑тестов убедитесь, что функция существует в хранилище. Чтобы гарантировать это для unit‑теста, выполните:

```bash
dbt build --select "+my_model_to_test" --empty
```

Следуя примеру из раздела [Определение UDF в dbt](#defining-udfs-in-dbt), ниже приведён пример unit‑теста, который проверяет модель, вызывающую UDF:

tests/test\_is\_positive\_int.yml

```yml
unit_tests:
  - name: test_is_positive_int 
    description: "Проверьте, что моя логика is_positive_int учитывает крайние случаи"
    model: my_model
    given:
      - input: ref('a_model_i_like')
        rows:
          - { maybe_positive_int_column: 10 }
          - { maybe_positive_int_column: -4 }
          - { maybe_positive_int_column: +8 }
          - { maybe_positive_int_column: 1.0 }
    expect:
      rows:
        - { maybe_positive_int_column: 10,  is_positive: true }
        - { maybe_positive_int_column: -4,  is_positive: false }
        - { maybe_positive_int_column: +8,  is_positive: true }
        - { maybe_positive_int_column: 1.0, is_positive: true }
```

## Просмотр и сборка UDF

Используйте команду [`list`](https://docs.getdbt.tech/reference/commands/list.md#listing-functions), чтобы вывести список UDF в проекте: `dbt list --select "resource_type:function"` или `dbt list --resource-type function`.

Используйте команду [`build`](https://docs.getdbt.tech/reference/commands/build.md#functions), чтобы выбирать UDF при сборке проекта: `dbt build --select "resource_type:function"`.

Подробнее о выборе UDF см. в примерах в разделе [Node selector methods](https://docs.getdbt.tech/reference/node-selection/methods.md#file).

## Ограничения

* UDF на других языках (например, Java или Scala) пока не поддерживаются.
* UDF на JavaScript поддерживаются только в Snowflake и BigQuery. Использование UDF на JavaScript с неподдерживаемым адаптером вызывает ошибку разбора.
* UDF на Python поддерживаются только в Snowflake, BigQuery и Databricks (при использовании dbt v1 или dbt v2). Другие хранилища данных пока не поддерживаются для UDF на Python.
* В настоящее время поддерживаются только скалярные и агрегатные функции. Подробнее см. в разделе [Поддерживаемые типы функций](https://docs.getdbt.tech/reference/resource-configs/type.md#supported-function-types).
* Свойство `overloads` поддерживается для UDF на SQL в Snowflake и Postgres, а также для UDF на Python и JavaScript в Snowflake.

## Связанные FAQ

Когда следует использовать UDF вместо макроса?

И пользовательские функции (UDF), и макросы позволяют переиспользовать логику в dbt‑проекте, однако работают они принципиально по‑разному. Ниже — рекомендации, когда стоит использовать каждый из подходов.

#### Используйте UDF, когда:

 Вам нужна логика, доступная за пределами dbt

UDF создаются непосредственно в хранилище данных и могут использоваться BI‑инструментами, ноутбуками для data science, SQL‑клиентами или любыми другими инструментами, которые подключаются к вашему хранилищу. Макросы же работают только внутри dbt.

 Вы хотите стандартизировать нативные функции хранилища

UDF позволяют создавать переиспользуемые функции хранилища для валидации данных, пользовательского форматирования или бизнес‑специфичных вычислений, которые должны быть единообразными во всех инструментах работы с данными. После создания такие функции становятся частью каталога функций вашего хранилища.

 Вы хотите, чтобы dbt управлял жизненным циклом функции

dbt управляет UDF как частью выполнения DAG, гарантируя, что они будут созданы до моделей, которые на них ссылаются. Вы можете хранить определения UDF в системе контроля версий вместе с моделями, тестировать изменения в средах разработки и разворачивать их через CI/CD‑пайплайны.

 Jinja компилируется в момент создания, а не при каждом вызове функции

В конфигурации UDF можно использовать Jinja (циклы, условия, макросы, `ref`, `source`, `var`). dbt разрешает этот Jinja **в момент создания UDF**, и именно получившееся SQL‑тело сохраняется в хранилище.

Jinja влияет на функцию в момент её создания, тогда как аргументы влияют на неё во время выполнения в хранилище:

* ✅ **Разрешено:** Jinja, зависящий от состояния проекта или времени сборки — например, `var(“can_do_things”)`, статический `ref(‘orders’)` или логика, зависящая от окружения. Всё это вычисляется один раз при создании.
* ❌ **Запрещено:** Jinja, зависящий от **аргументов функции**, передаваемых во время выполнения. Компилятор их не видит, поэтому динамический `ref(ref_name)` или условный Jinja на основе значений аргументов работать не будут.

 Вам нужна Python‑логика, выполняемая в вашем хранилище

Python UDF создаёт Python‑функцию непосредственно в хранилище данных, которую затем можно вызывать с помощью SQL.<br />Это упрощает применение сложных трансформаций, вычислений или логики, которые трудно или громоздко выразить на SQL.

Python UDF поддерживают условия и циклы внутри самой функции (с использованием синтаксиса Python) и выполняются во время выполнения запроса, а не на этапе компиляции, как макросы. В настоящее время Python UDF поддерживаются в Snowflake и BigQuery.

#### Используйте макросы, когда:

 Вам нужно генерировать SQL на этапе компиляции

Макросы динамически генерируют SQL **до** отправки его в хранилище (на этапе компиляции). Это критично для:

* построения разного SQL для разных хранилищ;
* генерации повторяющихся SQL‑паттернов (например, создания десятков похожих колонок);
* создания целых определений моделей или DDL‑выражений;
* динамических ссылок на модели в зависимости от структуры проекта.

UDF выполняются **во время выполнения запроса** в хранилище. Хотя в их определениях можно использовать Jinja, они не генерируют новые SQL‑запросы — это заранее определённые функции, которые просто вызываются из SQL.

Расширение UDF

В настоящее время поддерживаются SQL‑ и Python‑UDF. Поддержка Java и Scala UDF планируется в будущих релизах.

 Вам нужно генерировать DDL или DML‑выражения

В настоящее время поддерживаются SQL‑ и Python‑UDF. Поддержка Java и Scala UDF планируется в будущих релизах.

 Вам нужно адаптировать SQL под разные хранилища

Макросы могут использовать условную логику Jinja для генерации SQL, специфичного для конкретного хранилища (см. [cross-database macros](https://docs.getdbt.tech/reference/dbt-jinja-functions/cross-database-macros.md)), что делает dbt‑проект переносимым между платформами.

UDF — это объекты, специфичные для хранилища. Даже несмотря на то, что в определениях UDF можно использовать Jinja, у каждого хранилища свой синтаксис создания функций, разные поддерживаемые типы данных и диалекты SQL. Для каждого поддерживаемого хранилища вам придётся определять отдельные файлы UDF.

 Вашей логике нужен доступ к контексту dbt

И макросы, и UDF могут использовать Jinja, а значит имеют доступ к переменным контекста dbt, таким как `{{ ref() }}`, `{{ source() }}`, переменные окружения и конфигурации проекта. Более того, можно вызывать макрос из UDF (и наоборот), комбинируя динамическую генерацию SQL с выполнением логики во время выполнения.

Однако ключевое различие между ними — это *момент*, когда выполняется логика:

* Макросы выполняются на этапе компиляции, генерируя SQL до отправки его в хранилище.
* UDF выполняются внутри хранилища во время выполнения запроса.

 Вы хотите избежать создания объектов в хранилище

Макросы ничего не создают в вашем хранилище; они лишь генерируют SQL на этапе компиляции. UDF же создают реальные объекты функций в хранилище, которыми нужно управлять.

#### Можно ли использовать оба подхода вместе?

Да! Вы можете использовать макрос для вызова UDF или вызывать макрос внутри UDF, сочетая преимущества обоих подходов. Например, следующий пример показывает, как использовать макрос для задания значений аргументов по умолчанию вместе с логикой UDF:

```sql
{% macro cents_to_dollars(column_name, scale=2) %}
  {{ function('cents_to_dollars') }}({{ column_name }}, {{scale}})
{% endmacro %}
```

#### Связанная документация

* [Пользовательские функции (UDF)](https://docs.getdbt.tech/docs/build/udfs.md)
* [Jinja‑макросы](https://docs.getdbt.tech/docs/build/jinja-macros.md)
