에포크 초가 담긴 BIGINT 열을 시각으로 바꾸려고 캐스팅했더니 막힌다.
Casting numeric types to timestamp is prohibited (hive.strict.timestamp.conversion)
숫자를 시각으로 바꿀 때 그 숫자가 초인지 밀리초인지 알 방법이 없다. Hive 2 까지는 CAST(ts AS TIMESTAMP) 를 초로 해석했는데, 자바 계열 시스템이 만든 값은 대개 밀리초라 1000배 어긋난 결과가 조용히 나왔다. 2001년 데이터가 51년 뒤로 찍히는 식이다.
Hive 3 은 이 암묵적 변환을 기본으로 금지했다. 오류를 내는 것이 조용히 틀린 값을 주는 것보다 낫다는 판단이다.
단위를 명시하는 함수를 쓴다. 이것이 정답이다.
-- 초 단위 에포크
SELECT from_unixtime(ts_sec);
SELECT CAST(from_unixtime(ts_sec) AS TIMESTAMP);
-- 밀리초 단위 에포크
SELECT from_unixtime(CAST(ts_ms / 1000 AS BIGINT));
-- 밀리초 자리까지 살리려면
SELECT from_utc_timestamp(ts_ms, 'Asia/Seoul');
from_unixtime 은 문자열을 돌려주므로, 시각 타입이 필요하면 한 번 더 캐스팅한다. 형식을 지정할 수도 있다.
SELECT from_unixtime(ts_sec, 'yyyy-MM-dd HH:mm:ss');
반대 방향은 unix_timestamp 다.
SELECT unix_timestamp('2026-09-20 10:00:00');
옛 질의를 당장 돌려야 하면 세션에서 끌 수 있다.
SET hive.strict.timestamp.conversion=false;
이 값은 임시 조치다. 끄면 Hive 2 때와 같이 숫자를 초로 해석하므로, 밀리초 데이터에는 여전히 틀린 값이 나온다. 오류가 사라졌다고 값이 맞아진 것이 아니다. 결과를 반드시 눈으로 확인한다.
SET hive.strict.timestamp.conversion=false;
SELECT ts_ms, CAST(ts_ms AS TIMESTAMP) FROM t LIMIT 5;
찍힌 연도가 1970년 근처거나 먼 미래면 단위를 잘못 본 것이다.
Hive 의 TIMESTAMP 는 시간대 정보를 담지 않는다. from_unixtime 은 세션 시간대로 해석해 문자열을 만들므로, 서버의 시간대 설정에 따라 결과가 달라진다. 클러스터와 클라이언트의 시간대가 다르면 같은 질의가 다른 값을 낸다.
UTC 기준으로 고정하려면 변환 함수에 시간대를 명시한다.
SELECT from_utc_timestamp(from_unixtime(ts_sec), 'Asia/Seoul');
Hive 3.1 부터는 시간대를 담는 TIMESTAMP WITH LOCAL TIME ZONE 타입이 있다. 여러 엔진이 같은 테이블을 읽는 환경에서는 이 타입을 쓰는 편이 혼선을 줄인다.
같은 계열의 설정이 몇 개 더 있다. 함께 알아 두면 비슷한 오류를 빨리 읽는다.
| 설정 | 막는 것 |
|---|---|
hive.strict.checks.cartesian.product |
조인 조건 없는 교차 조인 |
hive.strict.checks.type.safety |
BIGINT 와 문자열의 암묵 비교 |
hive.strict.checks.no.partition.filter |
파티션 테이블의 전체 스캔 |
hive.strict.checks.orderby.no.limit |
LIMIT 없는 전역 정렬 |
INT 로 저장한 에포크 초는 2038년에 넘친다. 새로 설계한다면 BIGINT 로 둔다.
Impala 는 같은 제약이 없고 CAST 를 다르게 해석한다. 두 엔진이 같은 테이블을 읽는다면 변환을 질의에서 하지 말고 적재 단계에서 시각 타입으로 확정해 두는 편이 안전하다.
SET 으로 세션 동작을 바꿀 때.