본문으로 건너뛰기
snowflake검색
SQL과 데이터

UDF 와 저장 프로시저 (언어, 권한 모드, 외부 액세스)

SQL, Python, Java, JavaScript, Scala 로 만드는 UDF, UDTF, 저장 프로시저의 차이, 소유자 권한과 호출자 권한, Snowflake Scripting, 외부 API 를 부르는 External Access Integration, Code Bundles 까지 정리합니다.

UDFUDTF저장 프로시저Snowflake ScriptingEXECUTE ASExternal Access2026-10 기준, 약 8분

무엇을 언제 쓰나

종류호출 방법반환쓰임
스칼라 UDFSELECT f(col)행마다 값 하나계산식, 정규화, 마스킹 보조
UDTF (테이블 함수)FROM TABLE(f(...))여러 행파싱, 펼치기, 행 생성
집계 UDF (UDAF)SELECT f(col) ... GROUP BY그룹마다 값 하나맞춤 집계
저장 프로시저CALL p(...)값 하나 또는 테이블DDL, DML 여러 단계, 관리 자동화

UDF 는 쿼리 안에서 데이터에 붙어 돌고, 프로시저는 SQL 문을 여러 번 실행하는 절차입니다. 프로시저 안에서는 CREATE, GRANT, MERGE 같은 문을 실행할 수 있지만 UDF 에서는 할 수 없습니다.

지원 언어

언어UDF프로시저메모
SQL가능Snowflake Scripting가장 가볍고 옵티마이저가 안을 들여다봄
Python가능 (벡터화 가능)가능Anaconda 채널 패키지
Java, Scala가능가능JAR 를 스테이지에. Java 21 지원은 2026년 7월 Preview
JavaScript가능가능숫자 정밀도 주의 (double)

SQL UDF 와 Python UDF 예

CREATE OR REPLACE FUNCTION util.normalize_phone(p STRING)
RETURNS STRING
AS $$ REGEXP_REPLACE(p, '[^0-9]', '') $$;

CREATE OR REPLACE FUNCTION util.mask_email(e STRING)
RETURNS STRING
LANGUAGE PYTHON
RUNTIME_VERSION = '3.12'
HANDLER = 'mask'
AS $$
def mask(e):
    if e is None or '@' not in e:
        return e
    name, domain = e.split('@', 1)
    return name[:1] + '***@' + domain
$$;

RUNTIME_VERSION 에 넣을 수 있는 Python 버전은 계속 바뀌므로 만들기 전에 공식 문서의 지원 버전 표를 확인합니다.

Snowflake Scripting 프로시저

CREATE OR REPLACE PROCEDURE ops.purge_old_rows(days INT)
RETURNS STRING
LANGUAGE SQL
EXECUTE AS OWNER
AS
$$
DECLARE
  n INT;
BEGIN
  DELETE FROM raw.events WHERE loaded_at < DATEADD(day, -:days, CURRENT_TIMESTAMP());
  n := SQLROWCOUNT;
  RETURN 'deleted ' || n || ' rows';
EXCEPTION
  WHEN OTHER THEN
    RETURN 'failed: ' || SQLERRM;
END;
$$;

CALL ops.purge_old_rows(90);
  • DECLARE, IF, FOR, LOOP, CURSOR, RESULTSET, EXCEPTION 블록을 씁니다.
  • 바인드 변수는 SQL 문 안에서 :이름 으로 참조합니다.
  • 2026년 3월부터 DML 오류 기록(DML error logging)이 들어와 대량 INSERT 중 일부 행이 실패해도 전체를 버리지 않고 오류 행을 따로 남길 수 있습니다.

소유자 권한과 호출자 권한

모드실행 권한맞는 상황
EXECUTE AS OWNER (기본)프로시저 소유 역할의 권한사용자에게 테이블 권한을 주지 않고 정해진 작업만 허용
EXECUTE AS CALLER호출한 사용자의 현재 역할관리 유틸리티, 사용자 권한 범위 안에서만 동작해야 할 때
EXECUTE AS RESTRICTED CALLER호출자 권한 중 명시적으로 허용한 것만앱이나 Native App 에서 호출자 권한을 좁게 쓸 때

소유자 권한 프로시저 안에서는 세션 변수 변경, 일부 SHOW 와 DESCRIBE 같은 제약이 있었는데, 2026년 2월부터 소유자 권한 문맥에서도 INFORMATION_SCHEMA, SHOW, DESCRIBE 를 쓸 수 있게 되었습니다.

주의 소유자 권한 프로시저는 소유 역할의 권한을 빌려 주는 것입니다. 동적 SQL 에 사용자 입력을 그대로 이어 붙이면 권한 상승 통로가 됩니다. 식별자는 IDENTIFIER(:name) 로, 값은 바인드로 넘깁니다.

외부 API 호출 (External Access Integration)

UDF 와 프로시저는 기본적으로 인터넷에 나갈 수 없습니다. 외부 호출이 필요하면 네트워크 규칙, 시크릿, 외부 액세스 통합을 만들고 함수에 붙입니다.

CREATE NETWORK RULE ops.api_rule MODE = EGRESS TYPE = HOST_PORT VALUE_LIST = ('api.example.com');
CREATE SECRET ops.api_key TYPE = GENERIC_STRING SECRET_STRING = '...';
CREATE EXTERNAL ACCESS INTEGRATION api_eai
  ALLOWED_NETWORK_RULES = (ops.api_rule)
  ALLOWED_AUTHENTICATION_SECRETS = (ops.api_key)
  ENABLED = TRUE;

CREATE OR REPLACE FUNCTION ops.lookup(id STRING)
RETURNS VARIANT
LANGUAGE PYTHON RUNTIME_VERSION = '3.12'
HANDLER = 'run' PACKAGES = ('requests')
EXTERNAL_ACCESS_INTEGRATIONS = (api_eai)
SECRETS = ('key' = ops.api_key)
AS $$
import _snowflake, requests
def run(id):
    k = _snowflake.get_generic_secret_string('key')
    return requests.get(f"https://api.example.com/items/{id}", headers={"x-api-key": k}, timeout=10).json()
$$;

팁 외부 API UDF 를 수백만 행에 걸면 행마다 HTTP 요청이 나갑니다. 고유 키만 먼저 뽑아 결과를 테이블에 캐시하고 조인하는 편이 빠르고 상대 API 도 덜 괴롭힙니다.

Code Bundles

2026년 9월 Preview 로 나온 Code Bundles 는 Python 이나 Java 파일을 저장 프로시저로 감싸지 않고 Snowflake 안에서 바로 실행하는 기능입니다. 2026년 8월에는 기존 Notebook Project 객체가 Code Bundles 로 이름이 바뀌었습니다. 아직 Preview 이므로 운영 파이프라인은 프로시저와 태스크 조합을 유지하는 편이 안전합니다.

비용과 성능

  • UDF 실행 시간은 그 쿼리를 돌리는 웨어하우스 크레딧으로 잡힙니다. 프로시저도 안에서 실행한 쿼리가 웨어하우스를 씁니다.
  • SQL UDF 는 인라인되어 옵티마이저가 최적화하지만, Python 과 Java UDF 는 블랙박스라 프루닝과 조인 최적화를 막을 수 있습니다.
  • 메모리가 큰 Python UDF 는 Snowpark-optimized 웨어하우스에서 돌립니다. 자세한 것은 Snowpark 에 있습니다.

트러블슈팅

  • 증상: 프로시저 안의 SHOW 결과가 비어 있거나 권한 오류가 납니다.
  • 원인: 소유자 권한으로 실행되어 소유 역할이 그 객체를 볼 권한이 없습니다.
  • 해결: 소유 역할에 필요한 권한을 주거나, 관리용 유틸리티라면 EXECUTE AS CALLER 로 바꿉니다.

공식 문서

이 문서에서