CREATE MACRO 문

CREATE MACRO 문 (CREATE MACRO Statement)

CREATE MACRO 문은 이름 붙은 호출 가능한 SQL 표현식을 데이터베이스 스키마 객체로 정의해요. 매크로가 생기면 표현식의 조합을 짧게 줄여 쓰는 일종의 단축키처럼 쓸 수 있어요. 매크로를 만든 뒤에는 이름을 참조하고 파라미터에 값을 넘겨 호출할 수 있어요. 그렇게 하면 매크로의 표현식이 평가되어 값을 반환해요. 매크로 타입에 따라 값이 스칼라일 수도, TABLE 값일 수도 있어요. CREATE FUNCTION 문은 CREATE MACRO의 별칭이에요.

출처: 문서

본문

매크로를 생성하는 간단한 문법은 다음과 같아요.

CREATE [OR REPLACE] [TEMPORARY] MACRO [IF NOT EXISTS] ⟨identifier⟩( [⟨parameters⟩] ) AS [TABLE] ⟨expression⟩;
  • 식별자는 매크로의 이름으로, 유효한 SQL 식별자면 뭐든 될 수 있어요. 매크로는 기존 데이터베이스 스키마로 명시적으로 자격을 부여할 수 있어요. 스키마를 지정하면 보통 방식으로 스키마 이름과 매크로 이름을 점으로 구분해 이름 앞에 나타나요. 명시적으로 지정하지 않으면 매크로는 현재 스키마와 연결돼요.
  • 영구 데이터베이스 파일에 연결하면 매크로는 데이터베이스에 저장돼요. 선택적 TEMPORARY 키워드는 매크로가 영속되지 않음을 나타내요.
  • CREATE 키워드 직후에 OR REPLACE 절이 올 수 있는데, (스키마 안에서) 같은 이름의 기존 매크로를 덮어쓰게 해요. OR REPLACE 없이 시도하면 Macro Function already exists 오류로 실패해요.
  • IF NOT EXISTS 절은 식별자 직전에 올 수 있는데, 아직 존재하지 않을 때만 매크로를 생성해요. OR REPLACE 또는 IF NOT EXISTS 중 하나만 있을 수 있고 둘 다는 안 돼요.
  • 매크로 이름 뒤에 괄호가 따라와요. 매크로에 파라미터가 있으면 괄호 안에 선언해야 해요.
  • AS 키워드는 오른쪽 괄호 뒤, 표현식 앞에 나타나요.
  • TABLE 키워드는 AS 키워드 직후, 표현식 바로 앞에 나타날 수 있는데, 매크로가 테이블 매크로임을 나타내고 결과 집합을 반환해요. 생략하면 매크로는 자동으로 스칼라 매크로가 돼요.
  • 표현식은 표현식의 타입이 매크로의 타입과 정렬돼 있기만 하면 유효한 SQL 표현식이면 뭐든 될 수 있어요.

CREATE MACRO 문에 대한 더 정확하고 상세한 개요는 문법 다이어그램을 참고하세요.

매크로의 타입

특정 매크로를 호출할 수 있는 컨텍스트는 결과 값의 데이터 타입에 따라 달라져요.

  • 스칼라 매크로는 스칼라 값으로 평가돼요. 스칼라 매크로의 경우 표현식은 단순 표현식이거나 스칼라 서브쿼리일 수 있어요.
  • 테이블 매크로는 표 형식 결과를 반환해요. 호출되면 본질적으로 [테이블 함수]({% link docs/current/sql/query_syntax/from.md %}#table-functions)처럼 동작하며 테이블 값을 반환해요. 그 표현식은 SELECT 문이거나 다른 테이블 함수에 대한 호출일 수 있어요.

파라미터 선언

매크로는 파라미터를 선언할 수 있어요. 파라미터 선언은 AS 키워드 앞의 괄호 사이에 쉼표로 구분된 목록으로 나타나요. 단일 파라미터 선언의 간단한 문법은 다음과 같아요.

⟨parameter-name⟩ [⟨datatype⟩] [ := ⟨default-value⟩ ]
  • 파라미터 이름은 필수이며 유효한 SQL 식별자면 뭐든 될 수 있어요. 파라미터 이름은 파라미터 목록 안에서 고유해야 해요. 같은 이름으로 여러 파라미터를 정의하면 Duplicate parameter 오류가 나요.
  • 선택적으로 파라미터는 특정 데이터 타입을 명시할 수 있어요. 기존 DuckDB [data types]({% link docs/current/sql/data_types/overview.md %}) 중 아무거나 될 수 있어요. 참고: TABLE 타입의 파라미터를 지정할 방법은 없어요.
  • 파라미터는 선택적으로 기본값을 지정할 수 있어요. 할당 연산자 :=와 그 뒤의 기본값으로 사용할 표현식으로 지정해요.
  • 기본값 표현식은 원칙적으로 정의 시점(definition-time)에 평가돼요 — 실행 시점(run-time)이 아니라요. (CURRENT_SCHEMA 같은 몇 가지 예외가 있지만, 그걸 믿는 건 좋지 않아요. 기본값이 동적이어야 한다면 NULL 같은 잘 알려진 값을 기본값으로 쓰고 표현식의 조건부 로직으로 실행 시점 값을 만들어 내세요.)
  • 기본값 표현식을 지정하면 사실상 파라미터가 선택 사항이 돼요. 매크로를 호출하면 DuckDB 바인더가 전달된 파라미터에 기반해 후보 시그니처를 찾는데, 빠진 파라미터에 기본값을 지정하는 시그니처로 백필(backfill)돼요.
  • 기본값을 지정하는 파라미터는 기본값이 없는 파라미터의 정의 앞에 올 수 없어요. 즉 기본값이 없는 파라미터는 "앞"에 정의해야 하고, 기본값이 있는 파라미터는 "뒤"에 나타나요.
  • 여러 파라미터 선언은 쉼표로 서로 구분돼요.

오버로딩

매크로는 오버로딩을 지원해요.

  • 단일 CREATE MACRO 문이 여러 구현(때로 'overload'라고 함)을 정의할 수 있어요. 각 구현은 자체 파라미터 목록, AS 키워드, 표현식을 가져요. 모든 구현이 같은 CREATE MACRO 문 안에서 정의된다는 점을 주의하세요. 매크로 생성 후에 개별 구현을 추가·제거·변경할 수 없어요.
  • 여러 구현은 서로 쉼표로 구분돼요.
  • 각 구현은 고유한 파라미터 타입 시그니처를 가져야 해요. 즉 하나의 CREATE MACRO 문에서 같은 수의 파라미터를 가진 모든 구현은 파라미터 이름과 무관하게 고유한 파라미터 타입 시퀀스를 가져야 해요. 파라미터 타입 시그니처가 고유하지 않으면 Ambiguity in macro overloads 오류가 나요.
  • 오버로딩은 파라미터 타입에만 적용되고 매크로 타입 자체에는 적용되지 않아요. 단일 매크로의 모든 구현은 스칼라이거나 TABLE이에요.
  • 표현식으로 SELECT 문을 사용해 정의한 테이블 함수를 오버로딩할 때는 SELECT 문을 괄호로 감싸야 할 수 있어요.

매크로 호출

매크로는 이름을 언급하고 괄호를 붙여 호출해요. 괄호 사이에 쉼표로 구분된 값 표현식 목록이 나타날 수 있는데, 이들이 실제 파라미터예요. DuckDB 바인더는 실제 파라미터의 데이터 타입을 검사해 일치하는 시그니처의 매크로 구현을 찾으려 해요. 구현이 발견되면 파라미터 값이 전달되고 구현의 표현식이 평가돼요. 마지막으로 그 결과 값이 반환되어 매크로가 호출된 자리에 사용돼요. 이는 [함수]({% link docs/current/sql/functions/overview.md %})를 호출하는 것과 비슷해요.

일반적으로 매크로에 대한 호출은 그 표현식이 그 컨텍스트에도 나타날 수 있다면 유효해요.

  • 스칼라 매크로에 대한 호출은 SELECT 절이나 SELECT 문의 WHERE 절에서 사용할 수 있어요.
  • 스칼라 매크로의 표현식이 [집계 함수]({% link docs/current/sql/functions/aggregates.md %})를 참조하면 매크로도 집계 함수처럼 동작해요.
  • 테이블 매크로에 대한 호출은 SELECT 문의 FROM 절이나 [CALL 문]({% link docs/current/sql/statements/call.md %})에 나타날 수 있어요.

파라미터 전달

파라미터 값은 매크로 이름 뒤의 괄호 사이에 나타나는 쉼표로 구분된 표현식 목록으로 매크로에 전달할 수 있어요.

파라미터 값은 위치적으로(positionally) 또는 이름으로 전달할 수 있어요.

  • 위치적 파라미터 전달은 값 표현식만 전달하는 것을 의미해요.
  • 위치적 전달과 대조적으로, 이름 있는 파라미터 전달은 실제 파라미터 값을 특정 형식 파라미터에 명시적으로 할당해요. 파라미터 이름 + 할당 연산자 := + 파라미터 값 표현식 순으로 언급해요.
  • 일부 내장 함수도 이름 있는 파라미터를 허용하지만 할당 연산자로 =를 허용해요. 매크로에서는 이 방법이 동작하지 않아요! 대신 =는 비교 연산자로 해석돼요. 이는 사실상 의도했던 이름 있는 파라미터를, 파라미터 이름과 파라미터 값 표현식을 비교한 결과를 전달하는 위치적 파라미터로 바꿔버려요.
  • 매크로 호출에 이름 있는 파라미터가 있으면 위치적 파라미터 뒤에 나타나야 해요. 즉 위치적 파라미터는 "앞"에, 모든 이름 있는 파라미터는 "뒤"에 나타나야 해요.
  • 정의에 따라 위치적 파라미터는 선언된 순서대로 전달돼요. 하지만 이름 있는 파라미터는 (위치적 파라미터 뒤에만 있으면) 어떤 순서로든 나타날 수 있어요.

Examples

스칼라 매크로

두 표현식(ab)을 더하는 매크로 생성:

CREATE MACRO add(a, b) AS a + b;

기존 정의를 대체하는 매크로 생성:

CREATE OR REPLACE MACRO add(a, b) AS a + b;

아직 존재하지 않으면 매크로 생성, 이미 있으면 아무것도 안 함:

CREATE MACRO IF NOT EXISTS add(a, b) AS a + b;

CASE 표현식용 매크로 생성:

CREATE MACRO ifelse(a, b, c) AS CASE WHEN a THEN b ELSE c END;

서브쿼리를 수행하는 매크로 생성:

CREATE MACRO one() AS (SELECT 1);

매크로는 스키마에 의존하며 FUNCTION이라는 별칭이 있어요:

CREATE FUNCTION main.my_avg(x) AS sum(x) / count(x);

기본 파라미터를 가진 매크로 생성:

CREATE MACRO add_default(a, b := 5) AS a + b;

arr_append 매크로 생성(array_append와 기능 동일):

CREATE MACRO arr_append(l, e) AS list_concat(l, list_value(e));

타입 있는 파라미터를 가진 매크로 생성:

CREATE MACRO is_maximal(a INTEGER) AS a = 2^31 - 1;

테이블 매크로

파라미터 없는 테이블 매크로 생성:

CREATE MACRO static_table() AS TABLE
    SELECT 'Hello' AS column1, 'World' AS column2;

파라미터(어떤 타입이든)가 있는 테이블 매크로 생성:

CREATE MACRO dynamic_table(col1_value, col2_value) AS TABLE
    SELECT col1_value AS column1, col2_value AS column2;

여러 행을 반환하는 테이블 매크로 생성. 이미 존재하면 대체되고, 임시 매크로입니다(커넥션이 끝나면 자동 삭제됨):

CREATE OR REPLACE TEMP MACRO dynamic_table(col1_value, col2_value) AS TABLE
    SELECT col1_value AS column1, col2_value AS column2
    UNION ALL
    SELECT 'Hello' AS col1_value, 456 AS col2_value;

인자를 리스트로 전달:

CREATE MACRO get_users(i) AS TABLE
    SELECT * FROM users WHERE uid IN (SELECT unnest(i));

get_users 테이블 매크로를 사용하는 예시는 다음과 같아요.

CREATE TABLE users AS
    SELECT *
    FROM (VALUES (1, 'Ada'), (2, 'Bob'), (3, 'Carl'), (4, 'Dan'), (5, 'Eve')) t(uid, name);
SELECT * FROM get_users([1, 5]);

임의의 테이블에 매크로를 정의하려면 [query_table 함수]({% link docs/current/guides/sql_features/query_and_query_table_functions.md %})를 사용하세요. 예를 들어 다음 매크로는 테이블에 열별 체크섬을 계산해요.

CREATE MACRO checksum(tbl) AS TABLE
    SELECT bit_xor(md5_number(COLUMNS(*)::VARCHAR))
    FROM query_table(tbl);

CREATE TABLE tbl AS SELECT unnest([42, 43]) AS x, 100 AS y;
SELECT * FROM checksum('tbl');

오버로딩

파라미터의 타입이나 개수에 기반해 매크로를 오버로드할 수 있어요. 이는 스칼라와 테이블 매크로 모두에서 동작해요.

오버로드를 제공하면 서로 다른 함수 본문을 가진 add_x(a, b)add_x(a, b, c) 둘 다 가질 수 있어요.

CREATE MACRO add_x
    (a, b) AS a + b,
    (a, b, c) AS a + b + c;
SELECT
    add_x(21, 42) AS two_args,
    add_x(21, 42, 21) AS three_args;
two_args three_args
63 84
CREATE OR REPLACE MACRO is_maximal
    (a TINYINT) AS a = 2^7 - 1,
    (a INT) AS a = 2^31 - 1;
SELECT
    is_maximal(127::TINYINT) AS tiny,
    is_maximal(127) AS regular;
tiny regular
true false

Syntax

매크로를 사용하면 표현식의 조합에 대한 단축키를 만들 수 있어요.

CREATE MACRO add(a) AS a + b;
Binder Error:
Referenced column "b" not found in FROM clause!

이건 동작해요:

CREATE MACRO add(a, b) AS a + b;

사용 예시:

SELECT add(1, 2) AS x;
x
3

하지만 이건 실패해요:

SELECT add('hello', 3);
Binder Error:
Could not choose a best candidate function for the function call "add(STRING_LITERAL, INTEGER_LITERAL)". In order to select one, please add explicit type casts.
    Candidate functions:
    add(DATE, INTEGER) -> DATE
    add(INTEGER, INTEGER) -> INTEGER

매크로는 기본 파라미터를 가질 수 있어요.

b는 기본 파라미터예요:

CREATE MACRO add_default(a, b := 5) AS a + b;

다음은 42가 돼요:

SELECT add_default(37);

이름 있는 파라미터의 순서는 중요하지 않아요:

CREATE MACRO triple_add(a, b := 5, c := 10) AS a + b + c;
SELECT triple_add(40, c := 1, b := 1) AS x;
x
42

매크로를 사용하면 매크로는 확장(즉 원래 표현식으로 대체)되고, 확장된 표현식 안의 파라미터는 제공된 인자로 대체돼요. 단계별로 볼게요.

위에서 정의한 add 매크로를 쿼리에서 사용:

SELECT add(40, 2) AS x;

내부적으로 add는 그 정의인 a + b로 대체돼요:

SELECT a + b AS x;

그런 다음 파라미터가 제공된 인자로 대체돼요.

SELECT 40 + 2 AS x;

Limitation

서브쿼리 매크로 사용

스칼라 서브쿼리로 정의된 테이블 매크로와 스칼라 매크로는 테이블 함수의 인자로 사용할 수 없어요. DuckDB는 다음 오류를 반환해요.

Binder Error:
Table function cannot contain subqueries

오버로드

매크로 함수의 오버로드는 생성 시점에 설정해야 해요. 첫 정의를 제거하지 않고 같은 이름으로 매크로를 두 번 정의할 수는 없어요.

재귀 함수

재귀 함수 정의는 지원되지 않아요. 예를 들어 Fibonacci 수열의 n번째 수를 계산한다고 가정한 다음 매크로는 실패해요.

CREATE OR REPLACE FUNCTION fibo(n) AS (SELECT 1);
CREATE OR REPLACE FUNCTION fibo(n) AS (
    CASE
        WHEN n <= 1 THEN 1
        ELSE fibo(n - 1)
    END
);
SELECT fibo(3);
Binder Error:
Max expression depth limit of 1000 exceeded. Use "SET max_expression_depth TO x" to increase the maximum expression depth.

첫 함수에 대한 함수 체이닝은 동작하지 않음

매크로는 첫 함수에 대한 함수 체이닝의 점 연산자를 지원하지 않아요. 이를 보여주기 위해 동작하는 lower 함수 예시를 볼게요.

CREATE OR REPLACE MACRO low(s) AS lower(s);
SELECT low('AA');

하지만 lower(s)를 함수 체이닝으로 다시 쓰면 동작하지 않아요.

CREATE OR REPLACE MACRO low(s) AS s.lower();
SELECT low('AA');
Binder Error:
Referenced column "s" not found in FROM clause!

매크로와 테이블 매크로 목록 보기

다음 쿼리를 사용해 매크로와 테이블 매크로 목록을 표시할 수 있어요.

SELECT schema_name, function_name, function_type, parameters
FROM duckdb_functions();

더 알아보기 (Learn more)