흐름 연산자

흐름 연산자 (Flow operators)

이 문서는 SQL 문을 서로 연결하는 흐름 연산자에 대해 설명해요. 현재 Snowflake가 지원하는 유일한 흐름 연산자는 파이프 연산자(->>)예요. 파이프 연산자가 어떻게 여러 SQL 문을 체인으로 연결하는지, 문법·주의사항·예시를 다루어요.

출처: Snowflake SQL Reference

본문

흐름 연산자는 SQL 문을 서로 연결해서, 한 문의 결과가 다른 문의 입력으로 사용되게 해요. 현재 Snowflake가 지원하는 유일한 흐름 연산자는 파이프 연산자(->>)예요.

파이프 (Pipe)

파이프 연산자는 명령줄의 Unix 파이프(|)와 비슷하지만, Unix 명령이 아니라 SQL 문을 대상으로 해요. 파이프 연산자를 사용하려면 연산자로 구분된 일련의 SQL 문을 지정하면 돼요. SHOW, SELECT, CREATE, INSERT 등 유효한 SQL 문을 무엇이든 지정할 수 있어요. 첫 번째 SQL 문 이후의 각 후속 문은 이전 문의 결과를 입력으로 사용할 수 있어요. FROM 절에서 이전 SQL 문은 달러 기호($)와 파이프 번호로 참조하는데, 파이프 번호는 체인에서 현재 문을 기준으로 뒤로 세어 해당 문의 상대적 위치예요.

파이프 연산자는 다음과 같이 일련의 SQL 문을 연결하며, 주석은 각 문의 상대 참조 번호를 보여줘요:

first_st   -- last_st에서는 $4, fourth_st에서는 $3, third_st에서는 $2, second_st에서는 $1로 참조됨
  ->> second_st -- last_st에서는 $3, fourth_st에서는 $2, third_st에서는 $1로 참조됨
  ->> third_st  -- last_st에서는 $2, fourth_st에서는 $1로 참조됨
  ->> fourth_st -- last_st에서는 $1로 참조됨
  ->> last_st;

예를 들어 이 일련의 SQL 문에는 세 개의 SELECT 문에 파이프 번호 참조가 있고, 각각은 첫 번째 SELECT 문의 결과를 입력으로 받아요:

SELECT ...
  ->> SELECT ... FROM $1
  ->> SELECT ... FROM $2
  ->> SELECT ... FROM $3;

보시다시피 마지막 문 뒤에 세미콜론을 두어 SQL 문 체인을 끝내요. 체인에서 이전 문들 뒤에는 세미콜론을 두지 마세요. 전체 체인의 출력은 마지막 SQL 문의 최종 결과예요. SnowSQL 같은 클라이언트 도구는 문의 체인을 단일 문으로 취급해요.

파이프 연산자는 다음과 같은 이점을 제공해요:

  • 의존적인 SQL 문의 실행을 단순화해요.
  • 복잡한 SQL 연산의 가독성과 유연성을 높여요.

문법 (Syntax)

<sql_statement_1> ->> <sql_statement_2> [ ->> <sql_statement_n> ... ]

사용 시 주의사항 (Usage notes)

  • 각 문은 체인에서 후속 문만 소비할 수 있는 결과를 생성해요.
  • 문은 지정된 순서대로 실행돼요. RESULT_SCAN(LAST_QUERY_ID())과 달리 파이프 번호는 체인 밖에서 다른 쿼리가 동시에 실행됐는지 여부와 관계없이 체인에서 올바른 결과 집합으로 해석돼요.
  • 문이 이전 문의 결과를 소비할 때, 소비되는 결과 집합은 이전 문의 쿼리 ID를 전달받은 RESULT_SCAN 함수가 반환하는 결과 집합과 같아요. 예를 들어 다음 문들은 SHOW WAREHOUSES 명령의 출력을 특정 컬럼으로 제한해요:
SHOW WAREHOUSES;

SELECT "name", "state", "type", "size"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID(-1)));

이 문은 파이프 연산자를 사용해서 같은 결과를 생성해요:

SHOW WAREHOUSES
  ->> SELECT "name", "state", "type", "size" FROM $1;

SHOW와 DESCRIBE 명령의 출력 컬럼 이름은 소문자로 생성돼요. 파이프 연산자나 RESULT_SCAN 함수로 SHOW 또는 DESCRIBE 명령의 결과 집합을 소비한다면, 스캔된 출력의 컬럼 이름과 일치하도록 쿼리의 컬럼 이름에 큰따옴표로 감싼 식별자를 사용하세요. 예를 들어 출력 컬럼 이름이 type이면 식별자로 "type"을 지정하세요.

  • 파이프 연산자를 사용하는 쿼리는 체인에서 이전 쿼리의 입력 결과 집합과 같은 순서로 행을 반환한다는 보장이 없어요. 쿼리에 ORDER BY 절을 포함해서 순서를 지정할 수 있어요.
  • 어떤 SQL 문에서 발생한 오류는 체인의 실행을 중단하고, 그 오류가 클라이언트에 반환돼요.
  • 마지막 문의 결과가 클라이언트에 반환돼요.
  • 문은 Snowflake Scripting 익명 블록으로 실행돼요.

제약 사항 (Limitations)

  • $n 매개변수는 SQL 문의 FROM 절에서만 유효해요.
  • 각 SQL 문은 파이프 체인에서 후속 문만 소비할 수 있는 결과를 생성해요. 마지막 문의 결과를 제외하면 결과를 파이프 체인 밖에서 소비할 수 없어요.
  • 바인드 변수는 지원되지 않아요.
  • Snowflake 클라이언트 드라이버에서 파이프 연산자를 다중 문 실행(즉, ->> 대신 ;로 구분된 여러 문을 단일 호출로 제출)에 사용하는 것은 지원되지 않아요.
  • Snowflake Scripting과 함께 파이프 연산자를 사용할 때, SQL 문에서 파이프 연산자를 사용하면 RESULTSET의 선언과 할당을 결합할 수 없어요. 예를 들어 다음 코드는 오류를 반환해요:
LET res RESULTSET := (SELECT 'myvalue' ->> SELECT $1 FROM $1);
RETURN TABLE(res);

다음 예시는 RESULTSET의 선언과 할당을 분리하므로 성공해요:

LET res RESULTSET;
res := (SELECT 'myvalue' ->> SELECT $1 FROM $1);
RETURN TABLE(res);

예시 (Examples)

다음 예시들은 파이프 연산자를 사용해요.

SHOW 명령 출력의 컬럼 목록 선택하기

SHOW TABLES 명령을 실행하고, 파이프 연산자를 사용해서 2025년 4월 15일 이후에 생성된 테이블의 created_on, name, owner 컬럼으로 출력을 제한해 봐요.

SHOW TABLES
  ->> SELECT "created_on" AS creation_date,
             "name" AS table_name,
             "owner" AS table_owner
        FROM $1
        WHERE creation_date > '2025-04-15'::DATE;

+-------------------------------+-------------+--------------+
| CREATION_DATE                 | TABLE_NAME  | TABLE_OWNER  |
|-------------------------------+-------------+--------------|
| 2025-04-16 08:46:16.130 -0700 | TEST_TABLE1 | ACCOUNTADMIN |
| 2025-04-16 09:44:13.701 -0700 | MYTABLE1    | USER_ROLE    |
| 2025-04-16 08:46:32.092 -0700 | MYTABLE2    | USER_ROLE    |
+-------------------------------+-------------+--------------+

여러 테이블에 대한 쿼리에서 입력을 받는 쿼리 실행하기

먼저 dept_pipe_demo 테이블과 emp_pipe_demo 테이블을 만들고 각각 데이터를 삽입해 봐요:

CREATE OR REPLACE TABLE dept_pipe_demo (
  deptno NUMBER(2),
  dname VARCHAR(14),
  loc VARCHAR(13)
  ) AS SELECT * FROM VALUES
     (10, 'ACCOUNTING', 'NEW YORK'),
     (20, 'RESEARCH', 'DALLAS'),
     (30, 'SALES', 'CHICAGO'),
     (40, 'OPERATIONS', 'BOSTON');

CREATE OR REPLACE TABLE emp_pipe_demo (
  empno NUMBER(4),
  ename VARCHAR(10),
  sal NUMBER(7,2),
  deptno NUMBER(2)
  ) AS SELECT * FROM VALUES
    (7369, 'SMITH', 800, 20),
    (7499, 'ALLEN', 1600, 30),
    (7521, 'WARD', 1250, 30),
    (7698, 'BLAKE', 2850, 30),
    (7782, 'CLARK', 2450, 10);

다음 예시는 다음 작업을 수행하는 SQL 문 체인에 파이프 연산자를 사용해요:

  • dept_pipe_demo 테이블을 쿼리해서 dnameSALES인 행을 반환해요.
  • FROM 절의 WHERE 조건에 $1을 지정해서 이전 쿼리의 결과를 입력으로 사용하고, SALES 부서에서 급여가 1500보다 큰 직원을 위해 emp_pipe_demo 테이블을 쿼리해요.
  • FROM 절에 $1을 지정해서 이전 쿼리의 결과를 입력으로 사용하고, enamesal 값을 반환하는 쿼리를 실행해요.
SELECT * FROM dept_pipe_demo WHERE dname = 'SALES'
  ->> SELECT * FROM emp_pipe_demo WHERE sal > 1500 AND deptno IN (SELECT deptno FROM $1)
  ->> SELECT ename, sal FROM $1 ORDER BY 2 DESC;

+-------+---------+
| ENAME |     SAL |
|-------+---------|
| BLAKE | 2850.00 |
| ALLEN | 1600.00 |
+-------+---------+

참고: 이 예시의 목적은 파이프 연산자로 일련의 쿼리를 결합하는 방법을 보여주는 것이에요. 하지만 같은 출력을 조인 쿼리로도 얻을 수 있고, 조인 쿼리가 보통 파이프 연산자로 결합한 쿼리보다 더 좋은 성능을 보여요.

트랜잭션에서 DML 작업의 행 개수 반환하기

테이블을 만들고 행을 하나씩 삽입해 봐요. 모든 문을 연결하면 파이프 연산자로 각 INSERT 문의 결과(삽입된 총 행 수를 나타냄)를 조사할 수 있어요.

예시의 각 SELECT 문에서 SELECT 목록의 $1은 이전 결과가 아니라 첫 번째 컬럼을 가리키는 축약 참조예요. 파이프 번호의 $n 매개변수는 FROM 절에서만 유효해요.

CREATE OR REPLACE TABLE test_sql_pipe_dml (a INT, b INT)
  ->> INSERT INTO test_sql_pipe_dml VALUES (1, 2)
  ->> INSERT INTO test_sql_pipe_dml VALUES (3, 4)
  ->> INSERT INTO test_sql_pipe_dml VALUES (5, 6)
  ->> INSERT INTO test_sql_pipe_dml VALUES (7, 8)
  ->> SELECT (SELECT $1 FROM $4) +
             (SELECT $1 FROM $3) +
             (SELECT $1 FROM $2) +
             (SELECT $1 FROM $1)
        AS "Number of rows";

+----------------+
| Number of rows |
|----------------|
|              4 |
+----------------+

다음 예시는 다음 작업을 수행하는 SQL 문 체인에 파이프 연산자를 사용해요:

  • 트랜잭션을 시작해요.
  • 앞서 만든 테이블에 행을 삽입해요.
  • 테이블에서 행을 삭제해요.
  • 테이블의 행을 갱신해요.
  • 트랜잭션을 커밋해요.
  • 각 DML 작업의 영향을 받은 행 수를 쿼리해요.
BEGIN TRANSACTION
  ->> INSERT INTO test_sql_pipe_dml VALUES (1, 2)
  ->> DELETE FROM test_sql_pipe_dml WHERE a = 1
  ->> UPDATE test_sql_pipe_dml SET b = 2
  ->> COMMIT
  ->> SELECT
        (SELECT $1 FROM $4) AS "Inserted rows",
        (SELECT $1 FROM $3) AS "Deleted rows",
        (SELECT $1 FROM $2) AS "Updated rows";

+---------------+--------------+--------------+
| Inserted rows | Deleted rows | Updated rows |
|---------------+--------------+--------------|
|             1 |            2 |            3 |
+---------------+--------------+--------------+

나중에 삭제된 테이블에 대한 삽입 결과 반환하기

이 예시는 다음 작업을 수행하는 SQL 문 체인에 파이프 연산자를 사용해요:

  • IDENTITY 컬럼이 있는 테이블을 만들어요.
  • 테이블에 행을 삽입해요.
  • 테이블을 쿼리해요.
  • 테이블을 삭제해요.
  • 파이프 번호 $2(SELECT 문)의 결과를 쿼리해요.

마지막 SELECT 문에서 소비되는 결과 집합은 이전 SELECT 문의 쿼리 ID에 대한 RESULT_SCAN 함수가 반환하는 결과 집합과 같아요.

CREATE OR REPLACE TABLE test_sql_pipe_drop (
    id INT IDENTITY START 10 INCREMENT 1,
    data VARCHAR)
  ->> INSERT INTO test_sql_pipe_drop (data) VALUES ('row1'), ('row2'), ('row3')
  ->> SELECT * FROM test_sql_pipe_drop
  ->> DROP TABLE test_sql_pipe_drop
  ->> SELECT COUNT(*) "Number of rows", MAX(id) AS "Last ID" FROM $2;

+----------------+---------+
| Number of rows | Last ID |
|----------------+---------|
|              3 |      12 |
+----------------+---------+

더 알아보기 (Learn more)