파이프 연산자(Pipe Operators)
파이프 연산자(Pipe Operators)
파이프 연산자는 GoogleSQL의 파이프 구문과 유사하게, 쿼리를 위에서 아래로 읽히는 변환의 선형 체인으로 작성할 수 있게 해줘요. 각 연산자는 |> 토큰으로 시작하며, 그 앞의 쿼리 결과를 입력으로 받아 하나의 변환을 더 적용해요.
출처: 문서
본문
파이프 연산자는 위에서 아래로 읽히는 변환의 선형 체인으로 쿼리를 작성할 수 있게 해줘요. GoogleSQL의 파이프 구문과 유사해요.
FROM orders
|> WHERE cancelled = 0
|> AGGREGATE sum(amount) AS total GROUP BY customer
|> ORDER BY total DESC
|> LIMIT 3
모든 SELECT 쿼리 뒤에는 파이프 연산자의 체인이 이어질 수 있어요. 각 연산자는 |> 토큰으로 시작하고, 그 앞의 쿼리 결과를 입력으로 받아 하나의 변환을 더 적용해요. 각 연산자 안에서는 일반적인 ClickHouse 구문이 사용돼요. 파이프 연산자는 구문 확장이에요. 각 연산자는 그 앞의 쿼리를 서브쿼리로 감싸므로, 결과 AST는 중첩 서브쿼리로 작성된 동등한 쿼리의 AST와 같아요. 위 쿼리는 다음과 동등해요.
SELECT * FROM
(
SELECT customer, sum(amount) AS total FROM
(
SELECT * FROM
(
SELECT * FROM orders
)
WHERE cancelled = 0
)
GROUP BY customer
)
ORDER BY total DESC
LIMIT 3
FROM queries (FROM 쿼리)
쿼리는 FROM 절로 시작할 수 있으며, 그런 쿼리에서는 SELECT 절이 선택적이에요. 생략하면 SELECT *가 작성된 것처럼 동작해요.
FROM orders;
FROM orders WHERE amount > 100;
FROM orders |> WHERE amount > 100;
테이블 별칭은 일반 SELECT 쿼리의 FROM 절과 마찬가지로 AS 키워드를 쓰거나 쓰지 않고 작성할 수 있어요: FROM orders o WHERE o.amount > 100. 유일한 예외는 별칭이 select라는 단어 하나로 작성된 경우인데, 테이블 뒤에서 명시적인 SELECT 절을 시작하게 되어 별칭으로 취급되지 않아요. select라는 이름의 테이블은 영향을 받지 않고 자체 별칭을 유지해요: FROM select s WHERE s.id = 1. 괄호 안의 서브쿼리도 FROM 절로 시작할 수 있는데, 이로 인해 (from IN ('a'))는 모호해져요. 이는 from이라는 열에 대한 표현식 from IN ('a')로 읽힐 수도 있고, IN이라는 테이블 함수에 대한 서브쿼리 SELECT * FROM IN('a')로 읽힐 수도 있어요. 열로 읽는 것이 더 오래된 방식이라 우선해요. 즉 from이라는 단어로 시작하고 연산자가 뒤따르는 표현식으로 읽히는 괄호 내용은 서브쿼리가 아니라 항상 그 표현식이에요. 다른 해석을 얻으려면 SELECT 절을 명시적으로 작성하세요: 1 IN (SELECT * FROM in). 마지막 테이블의 샘플 오프셋이 쿼리 레벨 OFFSET으로 읽힐 수도 있을 때는 SELECT 절을 생략할 수 없어요. FROM t SAMPLE 1/10 OFFSET 5에서 OFFSET은 SAMPLE에 속하지만, FROM t SAMPLE 1/10 SELECT * OFFSET 5에서는 쿼리 레벨 OFFSET이기 때문이에요. 이 둘을 구분하려면 명시적인 SELECT가 필요해요. 쿼리가 쿼리 레벨 OFFSET이 앞에 오지 못하는 절로 이어질 때는 모호함이 없으며 SELECT 절은 평소처럼 선택적이에요: FROM t SAMPLE 1/10 OFFSET 5 WHERE x > 0, FROM t SAMPLE 1/10 OFFSET 5 JOIN dim USING (id).
Operators (연산자)
WHERE
|> WHERE condition은 입력 행을 필터링해요. 집계 후에 적용되면 HAVING처럼 동작해요.
FROM orders
|> AGGREGATE sum(amount) AS total GROUP BY customer
|> WHERE total > 100
SELECT
|> SELECT [DISTINCT] expr1 [AS alias1], ...는 나열된 표현식만 출력 열로 남겨요.
FROM orders |> SELECT customer, amount * 2 AS doubled
일반 쿼리의 SELECT 절과 같은 위치에서 표현식 목록 끝에 끝 쉼표(trailing comma)가 허용돼요. 여기서는 쿼리 끝이나 다음 |> 연산자로 이어질 수 있어요: FROM orders |> SELECT customer, amount, |> LIMIT 1. 이는 EXTEND와 AGGREGATE 연산자에도 동일하게 적용돼요.
EXTEND
|> EXTEND expr1 [AS alias1], ...는 나열된 표현식을 입력 열에 추가해요. SELECT *, expr1 AS alias1, ...와 동등해요.
FROM orders |> EXTEND amount * 10 AS big
SET
|> SET column1 = expr1, ...는 나열된 열의 값을 대체해요. SELECT * REPLACE (expr1 AS column1, ...)와 동등해요.
FROM orders |> SET amount = amount + 1000
DROP
|> DROP column1, ...는 나열된 열을 제거해요. SELECT * EXCEPT (column1, ...)와 동등해요.
FROM orders |> DROP cancelled
AS
|> AS alias는 다음 연산자의 입력에 별칭을 부여해서 해당 연산자에서 참조할 수 있게 해줘요. 주로 조인에 유용해요.
FROM orders
|> AGGREGATE sum(amount) AS total GROUP BY customer
|> AS agg
|> JOIN orders AS o ON agg.customer = o.customer
AGGREGATE
|> AGGREGATE agg1 [AS alias1], ... [GROUP BY expr1 [AS alias1], ...]는 입력 행을 집계해요. 출력 열은 그룹핑 열 다음에 집계 열 순서로 돼요. GROUP BY가 없으면 전체 입력이 단일 행으로 집계돼요.
FROM orders |> AGGREGATE count() AS c, sum(amount) AS total GROUP BY customer;
FROM orders |> AGGREGATE count() AS c;
DISTINCT
|> DISTINCT는 중복 행을 제거해요. SELECT DISTINCT *와 동등해요.
ORDER BY
|> ORDER BY expr1 [ASC/DESC], ...는 입력 행을 정렬해요. ORDER BY ALL, WITH FILL, INTERPOLATE를 포함한 전체 ORDER BY 절 구문이 지원돼요.
FROM orders |> ORDER BY amount DESC;
FROM orders |> SELECT customer, amount |> ORDER BY ALL;
FROM points |> ORDER BY x WITH FILL FROM 1 TO 10 INTERPOLATE (y AS y + 1)
LIMIT and OFFSET
|> LIMIT length [OFFSET offset]와 |> OFFSET offset은 행 수를 제한해요.
FROM orders |> ORDER BY amount DESC |> LIMIT 3 OFFSET 1
JOIN and ARRAY JOIN
|> [GLOBAL] [ANY/ALL/ASOF/SEMI/ANTI] [INNER/LEFT/RIGHT/FULL/CROSS] JOIN table [ON expr | USING (columns)]는 입력을 다른 테이블, 서브쿼리, 또는 테이블 함수와 조인해요. 모든 종류의 JOIN과 ARRAY JOIN이 지원되며, 단일 연산자가 FROM 절처럼 여러 조인을 포함할 수 있어요.
FROM customers
|> AS c
|> LEFT JOIN orders AS o ON c.name = o.customer
|> ARRAY JOIN tags
모든 연산자는 새로운 서브쿼리 스코프이므로 테이블 별칭은 같은 연산자 안에서만 보여요(ON 조건에서). 이후 연산자들은 SELECT * 이후처럼 조인 결과의 결합된 열을 보게 돼요. 크로스 조인의 쉼표 표기도 지원되며, 연산자의 입력이 왼쪽이 돼요: FROM customers |> AS c |> , orders. 다른 조인과 마찬가지로 joined_subquery_requires_alias 설정이 활성화되어 있으면(기본값) 입력에 별칭이 필요해요. 일반 쿼리의 FROM 절에서처럼, 쉼표(크로스) 조인은 ARRAY JOIN 바로 뒤에는 지원되지 않아요. ARRAY JOIN 뒤의 쉼표는 항상 그 표현식 목록에 속해요.
UNION, INTERSECT, and EXCEPT
|> UNION [ALL/DISTINCT] (query1) [, (query2), ...], |> INTERSECT [ALL/DISTINCT] ..., |> EXCEPT [ALL/DISTINCT] ...는 입력을 다른 쿼리의 결과와 결합해요.
FROM orders
|> SELECT customer
|> UNION ALL (FROM customers |> SELECT name)
|> DISTINCT
단일 쿼리의 경우 피연산자 주변의 괄호는 선택적이지만, 집합 연산(set operation) 뒤에 다른 파이프 연산자로 체인이 계속될 때는 필수예요. 그렇지 않으면 다음 연산자가 마지막 피연산자에 적용되는지 전체 결과에 적용되는지가 불분명해지기 때문이에요.
Notes (참고 사항)
- 쿼리의
WITH절은 스칼라 별칭과 CTE 모두에 대해 이후의 모든 파이프 연산자에서 계속 보여요:WITH 10 AS threshold FROM t |> WHERE x < threshold. INSERT ... SELECT에서INSERT앞에 작성된WITH절은 가장 바깥쪽 생성된SELECT에 붙고,enable_global_with_statement설정(기본 활성화)을 통해 해석 중에 안쪽 파이프 단계에 도달해요. 이것은 손으로 작성한 중첩 서브쿼리에 도달하는 방식과 같아요. 해당 설정이 비활성화되면INSERT스코프의WITH에서 온 별칭과 CTE는 손으로 작성한 서브쿼리 안에서처럼 파이프 단계 안에서 보이지 않아요.- 다른
SELECT쿼리와 마찬가지로, 파이프 연산자로 생성된 쿼리는SETTINGS절로 끝날 수 있으며, 이는 그 생성된 쿼리에 붙어요:FROM t |> LIMIT 1 SETTINGS max_threads = 1은SELECT * FROM (SELECT * FROM t) LIMIT 1 SETTINGS max_threads = 1과 같아요. 이것은 서브쿼리,CREATE VIEW,view테이블 함수처럼 쿼리 설정을 위한 별도 패스가 없는 곳에서도 동작해요. 체인 중간의SETTINGS절은 해당 단계에 남아 다음 연산자의 서브쿼리가 돼요. 괄호로 묶인 피연산자가 있는 집합 연산 뒤에는 끝의SETTINGS가 허용되지 않아요. 서브쿼리가 있는 동등한 쿼리도 그 위치에SETTINGS절을 가질 수 없어요. - 첫 번째 파이프 연산자 앞 쿼리의
SETTINGS절은 그 쿼리에 남아, 생성된 래퍼의 서브쿼리가 돼요. 서브쿼리의 설정은 그 서브쿼리가 해석될 때 적용되므로 일반 설정은 계속 동작해요. - 파이프 연산자는 집합 연산을 포함한 그 앞의 전체 쿼리에 바인딩돼요.
SELECT 1 UNION ALL SELECT 2 |> AGGREGATE count()에서 집계는UNION ALL의 결과에 적용돼요. 파이프 연산자 뒤 쿼리를UNION으로 계속하려면|> UNION연산자나 괄호를 사용해요. - 파이프 연산자는
SELECT쿼리가 예상되는 곳 어디에서나 사용할 수 있어요: 서브쿼리,INSERT ... SELECT(INSERT INTO t FROM src |> ...형식 포함),CREATE VIEW,view테이블 함수 등. - 열의 제자리 이름 변경은 별도 연산자로 제공되지 않아요.
|> SELECT * EXCEPT (old_name), old_name AS new_name또는SET및DROP연산자를 사용해요.