이름 해석
이름 해석
이름 해석(name resolution)은 식별자를 특정 컬럼·필드·매개변수·테이블·함수·변수 참조로 해석하는 과정이에요.
컬럼, 필드, 매개변수, 변수 해석
표현식의 식별자는 다음 중 하나를 참조할 수 있어요.
- 뷰, 테이블, 공통 테이블 표현식(CTE), 또는 column_alias에 기반한 컬럼 이름.
- struct나 map 안의 필드 이름 또는 map 키. 필드와 키는 한정되지 않은(unqualified) 형태로는 절대 사용할 수 없어요.
- SQL 사용자 정의 함수의 매개변수 이름.
- 세션 또는 SQL 스크립트 지역 변수 이름.
current_user나current_date처럼()를 필요로 하지 않는 특수 함수.INSERT또는SET VARIABLE의 맥락에서 컬럼이나 변수 값을 기본값으로 설정하는 데 사용하는DEFAULT키워드.
이름 해석은 다음 원칙을 적용해요.
- 가장 가까운 일치 참조가 승리하고,
- 컬럼과 매개변수는 필드와 키보다 우선해요.
자세히 말하면, 식별자를 특정 참조로 해석하는 것은 다음 규칙을 순서대로 따릅니다.
-
- 지역 참조(Local references)
-
- 컬럼 참조
한정(qualified)될 수 있는 식별자를 FROM 절의 테이블 참조에 있는 컬럼 이름과 대응시켜요.
그런 일치가 둘 이상이면 AMBIGUOUS_COLUMN_OR_FIELD 오류를 발생시켜요. 2. - 매개변수 없는 함수 참조
식별자가 한정되지 않았고 current_user, current_date, current_time, current_timestamp, current_path와 일치하면: 이 함수들 중 하나로 해석해요.
3. - 컬럼 DEFAULT 지정
식별자가 한정되지 않았고, default와 일치하며, UPDATE SET, INSERT VALUES, 또는 MERGE WHEN [NOT] MATCHED 맥락에서 표현식 전체를 구성하면: INSERT의 대상 테이블의 해당 DEFAULT 값으로 해석해요.
4. - Struct 필드 또는 map 키 참조
식별자가 한정되어 있으면 다음 단계에 따라 필드나 map 키에 대응시키려 시도해요.
A. 마지막 식별자를 제거하고 그것을 필드 또는 키로 취급해요.
B. 나머지를 FROM 절의 테이블 참조에 있는 컬럼에 대응시켜요.
-
그런 일치가 둘 이상이면 AMBIGUOUS_COLUMN_OR_FIELD 오류를 발생시켜요.
-
일치가 있고 그 컬럼이:
-
STRUCT: 필드를 대응시켜요.필드를 대응시킬 수 없으면 FIELD_NOT_FOUND 오류를 발생시켜요.
필드가 둘 이상이면 AMBIGUOUS_COLUMN_OR_FIELD 오류를 발생시켜요.
-
MAP: 키가 한정되어 있으면 오류를 발생시켜요.키가 실제로 map에 없으면 런타임 오류가 발생할 수 있어요.
-
그 외 타입: 오류를 발생시켜요.
C. 앞 단계를 반복해서 뒤에 오는 식별자를 필드로 제거해요. 컬럼으로 해석할 식별자가 남아 있는 동안 규칙 (A)와 (B)를 적용해요. 2. - 측면 컬럼 별칭(Lateral column aliasing)
표현식이 SELECT 목록 안에 있으면, 첫 식별자를 해당 SELECT 목록의 앞선 컬럼 별칭과 대응시켜요.
그런 일치가 둘 이상이면 AMBIGUOUS_LATERAL_COLUMN_ALIAS 오류를 발생시켜요.
나머지 각 식별자를 필드나 map 키로 대응시키고, 대응할 수 없으면 FIELD_NOT_FOUND 또는 AMBIGUOUS_COLUMN_OR_FIELD 오류를 발생시켜요. 3. - 상관(Correlation)
- LATERAL
쿼리 앞에 LATERAL 키워드가 있으면, 그 쿼리를 포함하고 LATERAL 앞에 있는 FROM의 테이블 참조들을 고려해 규칙 1.a와 1.d를 적용해요.
- 일반(Regular)
쿼리가 스칼라 서브쿼리, IN, 또는 EXISTS 서브쿼리이면, 포함하는 쿼리의 FROM 절에 있는 테이블 참조들을 고려해 규칙 1.a, 1.d, 2를 적용해요.
4. - 중첩 상관
쿼리의 중첩 수준을 반복하면서 규칙 3을 다시 적용해요. 5. - FOR 루프
문이 FOR 루프 안에 있으면:
A. 식별자를 FOR 루프 문 쿼리의 컬럼에 대응시켜요.
식별자가 한정되어 있으면, 한정자가 정의된 경우 FOR 루프 변수의 이름과 일치해야 해요.
B. 식별자가 한정되어 있으면, 규칙 1.c를 따라 매개변수의 필드나 map 키에 대응시켜요. 6. - 복합문
문이 복합문에 있으면:
A. 식별자를 그 복합문에서 선언된 변수에 대응시켜요.
식별자가 한정되어 있으면, 한정자가 정의된 경우 복합문의 레이블과 일치해야 해요.
B. 식별자가 한정되어 있으면, 규칙 1.c를 따라 변수의 필드나 map 키에 대응시켜요.
7. - 중첩 복합문 또는 FOR 루프
복합문의 중첩 수준을 반복하면서 규칙 5와 6을 다시 적용해요. 8. - 루틴 매개변수
표현식이 CREATE FUNCTION 문의 일부이면:
-
- 식별자를 매개변수 이름에 대응시켜요. 식별자가 한정되어 있으면, 한정자가 루틴의 이름과 일치해야 해요.
-
- 식별자가 한정되어 있으면, 규칙 1.c를 따라 매개변수의 필드나 map 키에 대응시켜요.
-
- 세션 변수
-
- 식별자를 세션 변수 이름에 대응시켜요.
식별자가 한정되어 있으면, 한정자는
session또는system.session이어야 해요. 식별자가 한정되지 않았으면, SQL 경로에system.session이 존재해야 해요(기본 경로에는 포함돼 있어요).
- 식별자를 세션 변수 이름에 대응시켜요.
식별자가 한정되어 있으면, 한정자는
-
- 식별자가 한정되어 있으면, 규칙 1.c를 따라 변수의 필드나 map 키에 대응시켜요.
제한 사항
비용이 많이 드는 상관 쿼리가 실행되는 것을 막기 위해, Spark는 지원되는 상관을 한 단계로 제한해요. 이 제한은 SQL 함수의 매개변수 참조에도 적용돼요.
예시
-- 컬럼과 필드 구분하기
> SELECT a FROM VALUES(1) AS t(a);
1
> SELECT t.a FROM VALUES(1) AS t(a);
1
> SELECT t.a FROM VALUES(named_struct('a', 1)) AS t(t);
1
-- 컬럼이 필드보다 우선해요
> SELECT t.a FROM VALUES(named_struct('a', 1), 2) AS t(t, a);
2
-- 암시적 측면 컬럼 별칭
> SELECT c1 AS a, a + c1 FROM VALUES(2) AS T(c1);
2 4
-- 지역 컬럼 참조가 측면 컬럼 별칭보다 우선해요
> SELECT c1 AS a, a + c1 FROM VALUES(2, 3) AS T(c1, a);
2 5
-- S.c3에 대한 스칼라 서브쿼리 상관
> SELECT (SELECT c1 FROM VALUES(1, 2) AS t(c1, c2)
WHERE t.c2 * 2 = c3)
FROM VALUES(4) AS s(c3);
1
-- 지역 참조가 상관보다 우선해요
> SELECT (SELECT c1 FROM VALUES(1, 2, 2) AS t(c1, c2, c3)
WHERE t.c2 * 2 = c3)
FROM VALUES(4) AS s(c3);
NULL
-- s.c3에 대한 명시적 스칼라 서브쿼리 상관
> SELECT (SELECT c1 FROM VALUES(1, 2, 2) AS t(c1, c2, c3)
WHERE t.c2 * 2 = s.c3)
FROM VALUES(4) AS s(c3);
1
-- EXISTS 술어에서 t.c2로의 상관
> SELECT c1 FROM VALUES(1, 2) AS T(c1, c2)
WHERE EXISTS(SELECT 1 FROM VALUES(2) AS S(c2)
WHERE S.c2 = T.c2);
1
-- t.c2로의 측면 상관 시도
> SELECT c1, c2, c3
FROM VALUES(1, 2) AS t(c1, c2),
(SELECT c3 FROM VALUES(3, 4) AS s(c3, c4)
WHERE c4 = c2 * 2);
[UNRESOLVED_COLUMN] `c2`
-- LATERAL 키워드로 측면 상관 성공
> SELECT c1, c2, c3
FROM VALUES(1, 2) AS t(c1, c2),
LATERAL(SELECT c3 FROM VALUES(3, 4) AS s(c3, c4)
WHERE c4 = c2 * 2);
1 2 3
-- SQL 함수의 매개변수 참조하기
> CREATE OR REPLACE TEMPORARY FUNCTION func(a INT) RETURNS INT
RETURN (SELECT c1 FROM VALUES(1) AS T(c1) WHERE c1 = a);
> SELECT func(1), func(2);
1 NULL
-- 컬럼이 매개변수보다 우선해요
> CREATE OR REPLACE TEMPORARY FUNCTION func(a INT) RETURNS INT
RETURN (SELECT a FROM VALUES(1) AS T(a) WHERE t.a = a);
> SELECT func(1), func(2);
1 1
-- 함수 이름으로 매개변수 한정하기
> CREATE OR REPLACE TEMPORARY FUNCTION func(a INT) RETURNS INT
RETURN (SELECT a FROM VALUES(1) AS T(a) WHERE t.a = func.a);
> SELECT func(1), func(2);
1 NULL
-- 측면 별칭이 상관 참조보다 우선해요
> SELECT (SELECT c2 FROM (SELECT 1 AS c1, c1 AS c2) WHERE c2 > 5)
FROM VALUES(6) AS t(c1)
NULL
-- 측면 별칭이 함수 매개변수보다 우선해요
> CREATE OR REPLACE TEMPORARY FUNCTION func(x INT)
RETURNS TABLE (a INT, b INT, c DOUBLE)
RETURN SELECT x + 1 AS x, x
> SELECT * FROM func(1)
2 2
-- 전부 한꺼번에
> CREATE OR REPLACE TEMPORARY VIEW lat(a, b) AS VALUES('lat.a', 'lat.b');
> CREATE OR REPLACE TEMPORARY VIEW frm(a) AS VALUES('frm.a');
> CREATE OR REPLACE TEMPORARY FUNCTION func(a INT, b int, c int)
RETURNS TABLE
RETURN SELECT t.*
FROM lat,
LATERAL(SELECT a, b, c
FROM frm) AS t;
> VALUES func('func.a', 'func.b', 'func.c');
a b c
----- ----- ------
frm.a lat.b func.c
객체 이름 해석
테이블, 뷰, 함수는 같은 해석 규칙을 따라요. 이것은 식별자가 몇 부분으로 되어 있는지에 달려 있어요.
완전 한정(3부분) — catalog.schema.object
참조는 고유하며 catalog.schema에서 조회돼요. system.builtin.object는 내장 함수를 식별하고, system.session.object는 임시 뷰, 함수, 또는 세션 변수를 식별해요.
부분 한정(2부분) — schema.object
식별자는 current_catalog로 한정돼요 — 즉 current_catalog.schema.object가 돼요 — 단, 앞부분이 session(함수의 경우 builtin)이 아닌 경우에만 그래요. 그 경우 Spark는 미니 경로를 사용해서 암시적 카탈로그를 고르고, 첫 번째 일치를 반환해요.
spark.sql.legacy.persistentCatalogFirst |
시도하는 미니 경로 순서 |
|---|---|
false(기본) |
시스템 네임스페이스(system.session.x / system.builtin.x)를 먼저, 그다음 현재 카탈로그의 session.x / builtin.x |
true(레거시) |
현재 카탈로그의 session.x / builtin.x를 먼저, 그다음 시스템 네임스페이스(system.session.x / system.builtin.x) |
한정되지 않음(1부분) — object
쿼리와 DML에서 Spark는 SQL 경로를 따라가 첫 번째 일치를 반환해요. DDL에서는 식별자가 current_catalog.current_schema로 한정돼요.
참고: 영구 뷰와 SQL UDF는 CREATE 시점의 SQL 경로를 고정해 둬요(capture). 뷰나 함수가 호출되면, 그 본문은 호출자의 현재 경로가 아니라 고정된 경로를 기준으로 이름(테이블, 뷰, 함수)을 해석해요. 본문 안의 current_schema()와 current_path()는 여전히 호출자의 맥락을 반환해요. SET PATH를 참조하세요.
테이블과 뷰 해석
테이블 참조는 영구 테이블 또는 뷰, 임시 뷰, 또는 공통 테이블 표현식(CTE)일 수 있어요.
해석은 객체 이름 해석을 따르되, 한정되지 않은 참조에 대해 한 가지가 더해져요. 참조가 WITH 절 안에 있으면, Spark는 먼저 가장 안쪽 WITH부터 바깥쪽으로 식별자를 CTE에 대응시켜요. 일치하는 CTE가 없으면 Spark는 SQL 경로를 따라가요.
관계를 해석할 수 없으면 Spark는 TABLE_OR_VIEW_NOT_FOUND를 발생시켜요. 이 오류에는 유효 검색 경로가 포함돼요. 예를 들어 searchPath = [system.builtin, system.session, spark_catalog.default].
예시
-- 시나리오 설정하기
> USE CATALOG spark_catalog;
> USE SCHEMA default;
> CREATE TABLE rel(c1 int);
> INSERT INTO rel VALUES(1);
-- rel에 대한 완전 한정 참조:
> SELECT c1 FROM spark_catalog.default.rel;
1
-- rel에 대한 부분 한정 참조:
> SELECT c1 FROM default.rel;
1
-- rel에 대한 한정되지 않은 참조:
> SELECT c1 FROM rel;
1
-- 이름이 충돌하는 임시 뷰 추가하기:
> CREATE TEMPORARY VIEW rel(c1) AS VALUES(2);
-- 한정되지 않은 참조에서는 임시 뷰가 영구 테이블보다 우선해요:
> SELECT c1 FROM rel;
2
-- 임시 뷰는 `session` 또는 `system.session`으로 한정할 수 있어요:
> SELECT c1 FROM session.rel;
2
> SELECT c1 FROM system.session.rel;
2
-- 다른 2부분 한정은 영구 테이블로 해석돼요:
> SELECT c1 FROM default.rel;
1
-- 공통 테이블 표현식에 대한 한정되지 않은 참조는 임시 뷰조차 이겨요:
> WITH rel(c1) AS (VALUES(3))
SELECT * FROM rel;
3
-- CTE가 중첩되면, 테이블 참조에 가장 가까운 일치가 우선해요.
> WITH rel(c1) AS (VALUES(3))
(WITH rel(c1) AS (VALUES(4))
SELECT * FROM rel);
4
-- CTE 대신 테이블을 해석하려면 한정하세요:
> WITH rel(c1) AS (VALUES(3))
(WITH rel(c1) AS (VALUES(4))
SELECT * FROM default.rel);
1
-- CTE가 보이려면 쿼리를 포함하고 있어야 해요
> SELECT * FROM (WITH cte(c1) AS (VALUES(1))
SELECT 1),
cte;
[TABLE_OR_VIEW_NOT_FOUND] The table or view `cte` cannot be found.
-- PATH가 한정되지 않은 관계 조회 순서를 결정해요
> CREATE SCHEMA db_a;
> CREATE SCHEMA db_b;
> CREATE TABLE db_a.t USING parquet AS SELECT 1 AS v;
> CREATE TABLE db_b.t USING parquet AS SELECT 2 AS v;
> SET PATH = spark_catalog.db_a, spark_catalog.db_b, system.builtin;
> SELECT v FROM t;
1
> SET PATH = spark_catalog.db_b, spark_catalog.db_a, system.builtin;
> SELECT v FROM t;
2
-- 3부분 `system.session.x`는 임시 스코프만 참조해요:
> SELECT * FROM system.session.no_such_view;
[TABLE_OR_VIEW_NOT_FOUND] ... `system`.`session`.`no_such_view` ...
함수 해석
함수 참조는 뒤에 오는 괄호로 인식되며 객체 이름 해석을 따라요.
함수를 해석할 수 없으면 Spark는 UNRESOLVED_ROUTINE을 발생시켜요. 이 오류에는 유효 검색 경로가 포함돼요. 예를 들어 searchPath = [system.builtin, system.session, spark_catalog.default].
예시
> USE CATALOG spark_catalog;
> USE SCHEMA default;
-- 내장 함수와 같은 이름의 함수 만들기
> CREATE FUNCTION concat(a STRING, b STRING) RETURNS STRING
RETURN b || a;
-- 한정되지 않은 참조는 내장 CONCAT으로 해석돼요
> SELECT concat('hello', 'world');
helloworld
-- 한정된 참조는 영구 함수로 해석돼요
> SELECT default.concat('hello', 'world');
worldhello
-- 영구 함수 만들기
> CREATE FUNCTION func(a INT, b INT) RETURNS INT
RETURN a + b;
-- 영구 함수는 한정하지 않고도 해석돼요
> SELECT func(4, 2);
6
-- 충돌하는 임시 함수 만들기
> CREATE FUNCTION func(a INT, b INT) RETURNS INT
RETURN a / b;
-- 임시 함수가 우선해요
> SELECT func(4, 2);
2
-- 영구 함수를 해석하려면 이제 한정이 필요해요
> SELECT spark_catalog.default.func(4, 3);
6
-- 내장 함수는 가려져 있어도 한정하면 항상 도달할 수 있어요.
-- system.session을 system.builtin보다 앞에 두어 일치하는 임시 `abs`가 내장 함수를 가리게 해요.
> SET PATH = system.session, system.builtin, spark_catalog.default;
> CREATE TEMPORARY FUNCTION abs(x INT) RETURNS INT RETURN x + 100;
-- 한정되지 않은 abs(-5)는 임시 함수로 해석돼요 (-5 + 100 = 95).
> SELECT abs(-5);
95
-- system.builtin.abs와 builtin.abs는 가려짐을 우회해 내장 함수에 도달해요.
> SELECT system.builtin.abs(-5);
5
> SELECT builtin.abs(-5);
5
-- session.abs는 명시적으로 임시 함수에 도달해요.
> SELECT session.abs(-5);
95
> DROP TEMPORARY FUNCTION abs;
> SET PATH = DEFAULT_PATH;
-- PATH가 한정되지 않은 루틴 조회 순서를 제어해요
> CREATE SCHEMA path_a;
> CREATE SCHEMA path_b;
> CREATE FUNCTION path_a.pick() RETURNS INT RETURN 10;
> CREATE FUNCTION path_b.pick() RETURNS INT RETURN 20;
> SET PATH = spark_catalog.path_a, spark_catalog.path_b, system.builtin;
> SELECT pick();
10
> SET PATH = spark_catalog.path_b, spark_catalog.path_a, system.builtin;
> SELECT pick();
20
-- 해석되지 않은 루틴은 유효 검색 경로를 나열해요
> SET PATH = spark_catalog.default, system.builtin;
> SELECT does_not_exist();
[UNRESOLVED_ROUTINE] ... searchPath: [`spark_catalog`.`default`, `system`.`builtin`] ...
출처: 문서