테이블 형식 Java UDF(UDTF)

이 문서는 Java에서 UDTF(사용자 정의 테이블 함수 )를 작성하는 방법을 설명합니다.

이 항목의 내용:

소개

Java UDTF 처리기 클래스는 UDTF 호출에서 수신한 행을 처리하고 테이블 형식 결과를 반환합니다. 수신된 행은 Snowflake에 의해 암시적으로 분할되거나 함수 호출 구문에서 명시적으로 분할됩니다. 클래스에서 구현하는 메서드를 사용하여 개별 행뿐 아니라 행이 그룹화된 파티션도 처리할 수 있습니다.

처리기 클래스는 다음을 사용하여 파티션과 행을 처리할 수 있습니다.

  • 이니셜라이저로 사용되는 인자 없는 생성자. 이 생성자를 사용하여 파티션 범위 지정 상태를 설정할 수 있습니다.

  • 각 행을 처리하는 process 메서드.

  • 범위 지정된 값을 파티션으로 반환하는 것을 포함하여, 파티션 처리를 완료하기 위한 파이널라이저로 사용되는 인자 없는 endPartition 메서드.

자세한 내용은 이 항목의 UDTF용 Java 클래스 섹션을 참조하십시오.

각 Java UDTF에는 핸들러 클래스에 의해 생성된 출력 행 열의 Java 데이터 타입을 지정하는 출력 행 클래스 도 필요합니다. 자세한 내용은 이 항목의 출력 행 클래스 섹션에 포함되어 있습니다.

분할에 대한 사용법 노트

  • Snowflake에서 암시적으로 분할된 행을 수신할 때는 처리기 코드가 파티션에 대해 어떤 가정도 할 수 없습니다. 암시적 분할과 함께 실행하는 것은 UDTF가 출력 생성을 위해 개별적으로 행만 볼 필요가 있고 행 간에 집계된 상태가 없는 경우에 가장 유용합니다. 이 경우 코드에는 아마 생성자나 endPartition 메서드가 필요하지 않을 것입니다.

  • 성능을 향상하기 위해 Snowflake는 일반적으로 UDTF 핸들러 코드의 여러 인스턴스를 병렬로 실행합니다. 행의 각 파티션은 UDTF의 단일 인스턴스로 전달됩니다.

  • 각 파티션은 하나의 UDTF 인스턴스에서만 처리되지만, 그 반대의 경우에도 반드시 그런 것은 아닙니다. 즉, 단일 UDTF 인스턴스는 여러 파티션을 순차적으로 처리할 수 있습니다. 따라서 이니셜라이저와 파이널라이저를 사용하여 각 파티션을 초기화 및 정리하여 한 파티션 처리에서 다른 파티션 처리로 누적 값이 넘어가지 않도록 하는 것이 중요합니다.

참고

테이블 형식 함수(UDTF)의 입력 인자 500개, 출력 열 500개로 제한됩니다.

UDTF용 Java 클래스

UDTF의 기본 구성 요소는 핸들러 클래스와 출력 행 클래스입니다.

처리기 클래스

Snowflake는 주로 핸들러 클래스의 다음 메서드를 호출하여 UDTF와 상호 작용합니다.

  • 이니셜라이저(생성자).

  • 행별 메서드(process).

  • 파이널라이저 메서드(endPartition).

핸들러 클래스는 이 세 가지 메서드를 지원하는 데 필요한 추가 메서드를 포함할 수 있습니다.

핸들러 클래스에는 getOutputClass 메서드도 포함되며, 이는 나중에 설명합니다.

핸들러 클래스(또는 출력 행 클래스)의 메서드에서 예외가 발생하면 처리가 중지됩니다. UDTF를 호출한 쿼리는 오류 메시지와 함께 실패합니다.

생성자

핸들러 클래스는 생성자를 가질 수 있으며, 이는 0개의 인자를 취해야 합니다.

생성자는 process 호출 전에 각 파티션 에 대해 한 번씩 호출됩니다.

생성자는 출력 행을 생성할 수 없습니다.

생성자를 사용하여 파티션의 상태를 초기화합니다. 이 상태는 processendPartition 메서드에서 사용할 수 있습니다. 생성자는 행당 한 번이 아니라 파티션당 한 번만 수행해야 하는 장기 실행 초기화를 배치하기에 적절한 위치이기도 합니다.

생성자는 선택 사항입니다.

process 메서드

process 메서드는 입력 파티션의 각 행에 대해 한 번씩 호출됩니다.

UDTF에 전달된 인자는 process 에 전달됩니다. 인자 값은 SQL 데이터 타입에서 Java 데이터 타입으로 변환됩니다. (SQL 및 Java 데이터 타입 매핑에 대한 자세한 내용은 SQL-Java 데이터 타입 매핑 섹션을 참조하십시오.)

process 메서드의 매개 변수 이름은 유효한 Java 식별자일 수 있습니다. 이름은 CREATE FUNCTION 문에 지정된 이름과 일치할 필요가 없습니다.

해당 process 가 호출될 때마다 0개, 1개 또는 여러 행을 반환할 수 있습니다.

process 메서드가 반환하는 데이터 타입은 Stream<OutputRow> 여야 합니다. 여기서 Stream은 java.util.stream.Stream에 정의되고 OutputRow 는 출력 행 클래스의 이름입니다. 아래 예는 Stream을 통해 입력을 반환하는 간단한 process 메서드를 보여줍니다.

import java.util.stream.Stream;

...

public Stream<OutputRow> process(String v) {
  return Stream.of(new OutputRow(v));
}

...
Copy

process 메서드가 오브젝트의 상태를 유지하거나 사용하지 않는 경우(예: 출력에서 선택한 입력 행을 단순히 제외하도록 메서드가 설계된 경우) 메서드를 static 으로 선언할 수 있습니다. process 메서드가 static 이고 핸들러 클래스에 생성자 또는 비정적 endPartition 메서드가 없는 경우 Snowflake는 핸들러 클래스의 인스턴스를 생성하지 않고 각 행을 정적 process 메서드에 직접 전달합니다.

입력 행을 건너뛰고 다음 행을 처리해야 하는 경우(예: 입력 행의 유효성을 검사하는 경우) 빈 Stream 오브젝트를 반환합니다. 예를 들어 아래의 process 메서드는 number 가 양의 정수인 행만 반환합니다. number 가 양수가 아닌 경우 메서드는 현재 행을 건너뛰고 다음 행을 계속 처리하기 위해 빈 Stream 오브젝트를 반환합니다.

public Stream<OutputRow> process(int number) {
  if (inputNumber < 1) {
    return Stream.empty();
  }
  return Stream.of(new OutputRow(number));
}
Copy

process 가 null Stream을 반환하면 처리가 중지됩니다. (null Stream이 반환되더라도 endPartition 메서드는 계속 호출됩니다.)

이 메서드는 필수입니다.

endPartition 메서드

이 선택적 메서드는 process 에서 집계된 상태 정보를 기반으로 하는 출력 행을 생성하는 데 사용할 수 있습니다. 이 메서드는 해당 파티션의 모든 행이 process 에 전달된 후 각 파티션 에 대해 한 번 호출됩니다.

이 메서드를 포함하면 데이터가 명시적으로 또는 암시적으로 분할되었는지 여부에 관계없이 각 파티션에서 호출됩니다. 데이터가 의미 있게 분할되지 않으면 파이널라이저의 출력이 의미가 없을 수 있습니다.

참고

사용자가 데이터를 명시적으로 분할하지 않으면 Snowflake는 데이터를 암시적으로 분할합니다. 자세한 내용은 파티션 을 참조하십시오.

이 메서드는 0개, 1개 또는 여러 행을 출력할 수 있습니다.

참고

Snowflake는 성공적으로 처리하도록 시간 제한이 조정된 대형 파티션을 지원하지만, 특히 대형 파티션으로 인해 처리 시간이 초과될 수 있습니다(예: endPartition 이 완료하는 데 너무 오래 걸리는 경우). 특정 사용 시나리오에 맞게 시간 초과 임계값을 조정해야 하는 경우 Snowflake 지원 에 문의하십시오.

getOutputClass 메서드

이 메서드는 출력 행 클래스 에 대한 정보를 반환합니다. 출력 행 클래스에는 반환된 행의 데이터 타입에 대한 정보가 포함됩니다.

출력 행 클래스

Snowflake는 출력 행 클래스를 사용하여 Java 데이터 타입과 SQL 데이터 타입 간의 변환을 지정하는 데 도움을 줍니다.

Java UDTF가 행을 반환할 때 행의 각 열에 있는 값은 Java 데이터 타입에서 해당 SQL 데이터 타입으로 변환되어야 합니다. SQL 데이터 타입은 CREATE FUNCTION 문의 RETURNS 절에 지정됩니다. 그러나 Java 및 SQL 데이터 타입 간의 매핑은 1:1이 아니므로 Snowflake는 반환된 각 열에 대한 Java 데이터 타입을 알아야 합니다. (SQL 및 Java 데이터 타입 매핑에 대한 자세한 내용은 SQL-Java 데이터 타입 매핑 섹션을 참조하십시오.)

Java UDTF는 출력 행 클래스를 정의하여 출력 열의 Java 데이터 타입을 지정합니다. UDTF에서 반환된 각 행은 출력 행 클래스의 인스턴스로 반환됩니다. 출력 행 클래스의 각 인스턴스에는 각 출력 열에 대해 하나의 공용 필드가 있습니다. Snowflake는 출력 행 클래스의 각 인스턴스에서 공용 필드 값을 읽고, Java 값을 SQL 값으로 변환하고, 해당 값을 포함하는 SQL 출력 행을 구성합니다.

출력 행 클래스의 각 인스턴스에 있는 값은 출력 행 클래스의 생성자를 호출하여 설정됩니다. 생성자는 출력 열에 해당하는 매개 변수를 수락한 다음 공용 필드를 해당 매개 변수로 설정합니다.

아래 코드는 샘플 출력 행 클래스를 정의합니다.

class OutputRow {

  public String name;
  public int id;

  public OutputRow(String pName, int pId) {
    this.name = pName;
    this.id = pId
  }

}
Copy

이 클래스에 의해 지정된 공용 변수는 CREATE FUNCTION 문의 RETURNS TABLE (...) 절에 지정된 열과 일치해야 합니다. 예를 들어 위의 OutputRow 클래스는 아래의 RETURNS 절에 해당합니다.

CREATE FUNCTION F(...)
    RETURNS TABLE(NAME VARCHAR, ID INTEGER)
    ...
Copy

중요

SQL 열 이름과 출력 행 클래스의 Java 공용 필드 이름 간의 일치는 대/소문자를 구분하지 않습니다. 예를 들어, 위에 표시된 Java 및 SQL 코드에서 id 라는 Java 필드는 ID 라는 SQL 열에 해당합니다.

출력 행 클래스는 다음과 같이 사용됩니다.

  • 핸들러 클래스는 출력 행 클래스를 사용하여 process 메서드와 endPartition 메서드의 반환 타입을 지정합니다. 핸들러 클래스는 또한 출력 행 클래스를 사용하여 반환된 값을 구성합니다. 예:

    public Stream<OutputRow> process(String v) {
      ...
      return Stream.of(new OutputRow(...));
    }
    
    public Stream<OutputRow> endPartition() {
      ...
      return Stream.of(new OutputRow(...));
    }
    
    Copy
  • 출력 행 클래스는 핸들러 클래스의 getOutputClass 메서드에서도 사용됩니다. 이는 Snowflake가 출력의 Java 데이터 타입을 학습하기 위해 호출하는 정적 메서드입니다.

    public static Class getOutputClass() {
      return OutputRow.class;
    }
    
    Copy

출력 행 클래스(또는 처리기 클래스)의 메서드에서 예외가 발생하면 처리가 중지됩니다. UDTF를 호출한 쿼리는 오류 메시지와 함께 실패합니다.

요구 사항 요약

UDTF의 Java 코드는 다음 요구 사항을 충족해야 합니다.

  • 코드는 출력 행 클래스 를 정의해야 합니다.

  • UDTF 핸들러 클래스에는 <출력_행_클래스> 의 Stream을 반환하는 process 라는 공용 메서드가 포함되어야 합니다. 여기서 Stream은 java.util.stream.Stream에 정의되어 있습니다.

  • UDTF 핸들러 클래스는 <출력_행_클래스>.class 를 반환해야 하는 getOutputClass 라는 공용 정적 메서드를 정의해야 합니다.

Java 코드가 이러한 요구 사항을 충족하지 않으면 UDTF 생성 또는 실행이 실패합니다.

  • CREATE FUNCTION 문이 실행될 때 세션에 활성 웨어하우스가 있는 경우 Snowflake는 함수가 생성될 때 위반을 감지합니다.

  • CREATE FUNCTION 문이 실행될 때 세션에 활성 웨어하우스가 없으면 Snowflake는 함수가 호출될 때 위반을 감지합니다.

쿼리에서 Java UDTF 호출의 예

UDF 및 UDTF 호출에 대한 일반 정보는 UDF 호출하기 섹션을 참조하십시오.

명시적 분할 없이 호출하기

이 예는 UDTF를 만드는 방법을 보여줍니다. 이 예는 각 입력의 두 복사본을 반환하고 각 파티션에 대해 하나의 추가 행을 반환합니다.

create function return_two_copies(v varchar)
returns table(output_value varchar)
language java
handler='TestFunction'
target_path='@~/TestFunction.jar'
as
$$

  import java.util.stream.Stream;

  class OutputRow {

    public String output_value;

    public OutputRow(String outputValue) {
      this.output_value = outputValue;
    }

  }


  class TestFunction {

    String myString;

    public TestFunction()  {
      myString = "Created in constructor and output from endPartition()";
    }

    public static Class getOutputClass() {
      return OutputRow.class;
    }

    public Stream<OutputRow> process(String inputValue) {
      // Return two rows with the same value.
      return Stream.of(new OutputRow(inputValue), new OutputRow(inputValue));
    }

    public Stream<OutputRow> endPartition() {
      // Returns the value we initialized in the constructor.
      return Stream.of(new OutputRow(myString));
    }

  }

$$;
Copy

이 예는 UDTF를 호출하는 방법을 보여줍니다. 이 예를 단순하게 유지하기 위해 문은 열이 아닌 리터럴 값을 전달하고 OVER() 절을 생략합니다.

SELECT output_value
   FROM TABLE(return_two_copies('Input string'));
+-------------------------------------------------------+
| OUTPUT_VALUE                                          |
|-------------------------------------------------------|
| Input string                                          |
| Input string                                          |
| Created in constructor and output from endPartition() |
+-------------------------------------------------------+
Copy

이 예에서는 다른 테이블에 읽은 값으로 UDTF를 호출합니다. process 메서드가 호출될 때마다 cities_of_interest 테이블의 현재 행에 있는 city_name 열의 값이 전달됩니다. 위와 같이, UDTF는 명시적 OVER() 절 없이 호출됩니다.

입력 소스로 사용할 간단한 테이블을 만듭니다.

CREATE TABLE cities_of_interest (city_name VARCHAR);
INSERT INTO cities_of_interest (city_name) VALUES
    ('Toronto'),
    ('Warsaw'),
    ('Kyoto');
Copy

Java UDTF 호출:

SELECT city_name, output_value
   FROM cities_of_interest,
       TABLE(return_two_copies(city_name))
   ORDER BY city_name, output_value;
+-----------+-------------------------------------------------------+
| CITY_NAME | OUTPUT_VALUE                                          |
|-----------+-------------------------------------------------------|
| Kyoto     | Kyoto                                                 |
| Kyoto     | Kyoto                                                 |
| Toronto   | Toronto                                               |
| Toronto   | Toronto                                               |
| Warsaw    | Warsaw                                                |
| Warsaw    | Warsaw                                                |
| NULL      | Created in constructor and output from endPartition() |
+-----------+-------------------------------------------------------+
Copy

주의

이 예에서 FROM 절에 사용된 구문은 내부 조인의 구문(즉, FROM t1, t2)과 동일합니다. 그러나 수행된 작업은 실제 내부 조인이 아닙니다. 실제 동작은 함수가 테이블에 있는 각 행의 값으로 호출된다는 것입니다. 즉, 다음 FROM 절이 주어졌을 때:

from cities_of_interest, table(f(city_name))
Copy

동작은 다음 의사 코드와 동일합니다.

for city_name in cities_of_interest:
    output_row = f(city_name)
Copy

JavaScript UDTF에 대한 설명서의 예 섹션 에는 테이블의 값으로 UDTF를 호출하는 쿼리의 더 복잡한 예가 포함되어 있습니다.

이 문이 분할을 명시적으로 지정하지 않은 경우에는 Snowflake 실행 엔진은 암시적 분할 을 사용합니다.

파티션이 하나만 있는 경우 endPartition 메서드는 한 번만 호출되고 쿼리 출력에는 Created in constructor and output from endPartition() 값이 있는 행이 하나만 포함됩니다. 문을 다르게 실행하는 중에 데이터가 다른 개수의 파티션으로 그룹화되면 endPartition 메서드가 다른 횟수로 호출되고 출력에는 이 행의 복사본이 포함되는데 그 개수가 다릅니다.

자세한 내용은 암시적 분할 을 참조하십시오.

명시적 분할로 호출하기

Java UDTF는 명시적 분할을 사용하여 호출할 수도 있습니다.

다중 파티션

다음 예에서는 이전에 만든 동일한 UDTF 및 테이블을 사용합니다. 이 예에서는 city_name을 기준으로 데이터를 분할합니다.

SELECT city_name, output_value
   FROM cities_of_interest,
       TABLE(return_two_copies(city_name) OVER (PARTITION BY city_name))
   ORDER BY city_name, output_value;
+-----------+-------------------------------------------------------+
| CITY_NAME | OUTPUT_VALUE                                          |
|-----------+-------------------------------------------------------|
| Kyoto     | Created in constructor and output from endPartition() |
| Kyoto     | Kyoto                                                 |
| Kyoto     | Kyoto                                                 |
| Toronto   | Created in constructor and output from endPartition() |
| Toronto   | Toronto                                               |
| Toronto   | Toronto                                               |
| Warsaw    | Created in constructor and output from endPartition() |
| Warsaw    | Warsaw                                                |
| Warsaw    | Warsaw                                                |
+-----------+-------------------------------------------------------+
Copy

단일 파티션

다음 예에서는 이전에 만든 동일한 UDTF 및 테이블을 사용하고, 상수로 데이터를 분할하므로 Snowflake는 단일 분할만 사용하게 됩니다.

SELECT city_name, output_value
   FROM cities_of_interest,
       TABLE(return_two_copies(city_name) OVER (PARTITION BY 1))
   ORDER BY city_name, output_value;
+-----------+-------------------------------------------------------+
| CITY_NAME | OUTPUT_VALUE                                          |
|-----------+-------------------------------------------------------|
| Kyoto     | Kyoto                                                 |
| Kyoto     | Kyoto                                                 |
| Toronto   | Toronto                                               |
| Toronto   | Toronto                                               |
| Warsaw    | Warsaw                                                |
| Warsaw    | Warsaw                                                |
| NULL      | Created in constructor and output from endPartition() |
+-----------+-------------------------------------------------------+
Copy

메시지 Created in constructor and output from endPartition() 의 복사본 하나만 출력에 포함되었으며 이는 endPartition 이 한 번만 호출되었음을 나타냅니다.

매우 큰 입력 처리하기(예: 큰 파일)

어떤 경우에는 UDTF가 각 입력 행을 처리하는 데 매우 많은 양의 메모리가 필요합니다. 예를 들어, UDTF는 너무 커서 메모리에 맞지 않는 파일을 읽고 처리할 수 있습니다.

UDF 또는 UDTF의 대용량 파일을 처리하려면 SnowflakeFile 클래스 또는 InputStream 클래스를 사용하십시오. 자세한 내용은 UDF 및 프로시저 처리기로 비정형 데이터 처리하기 섹션을 참조하십시오.