SQL 특이점

SQL 특이점 (SQL Quirks)

모든 프로그래밍 언어와 라이브러리처럼, DuckDB도 나름의 특성과 불일치를 갖고 있어요. 어떤 것은 DuckDB 진화의 잔재이고, 어떤 것은 SQL 표준과 특히 PostgreSQL 방언을 따르려다 생긴 필연이에요. 나머지는 단순한 선호 차이일 수도 있고, 해야 할 일에 동의하면서도 아직 미루고 있는 것일 수도 있답니다. 이런 특이점을 아는 것이 최선이라, 아래에 예시 목록을 모아봤어요.

출처: 문서

본문

빈 그룹 집계

빈 그룹에서 집계 함수 sum, list, string_agg는 각각 0, [], '' 대신 NULL을 반환해요. 이는 SQL 표준이 정한 동작이며, 우리가 아는 모든 SQL 구현체가 따르고 있어요. 이 동작은 리스트 집계 list_sum에도 이어지지만, 빈 리스트에서 0을 반환하는 DuckDB 고유의 list_dot_product에는 적용되지 않아요.

0 기반 vs 1 기반 인덱싱

표준 SQL을 따르기 위해 1 기반 인덱싱이 거의 모든 곳에서 사용돼요. 예를 들어 배열·문자열 인덱싱과 슬라이싱, 그리고 윈도우 함수(row_number, rank, dense_rank)가 그렇죠. 다만 PostgreSQL과 마찬가지로 JSON 기능은 0 기반 인덱싱을 사용해요.

타입

UINT8 vs INT8

UINT8INT8은 서로 다른 폭의 정수 타입에 대한 별칭이에요.

  • UINT8_8-bit_ 부호 없는 정수라서 UTINYINT에 해당해요.
  • INT8_8-byte_ 부호 있는 정수라서 BIGINT에 해당해요.

설명: 숫자 타입 INTnUINTnn은 숫자의 폭을 바이트 또는 비트로 나타내요. INT1, INT2, INT4는 바이트 수에 해당하고, INT16, INT32, INT64는 비트 수에 해당해요. UINT 값에도 동일하게 적용됩니다. 다만 n = 8은 비트 수와 바이트 수 양쪽 모두로 유효한 값이에요. 부호 없는 값에 대해 UINT8UTINYINT(8비트)에 해당하고, 부호 있는 값에 대해 INT8BIGINT(8바이트)에 해당해요.

표현식

놀라울 수 있는 결과

표현식 결과 비고
-2^2 4.0 PostgreSQL 호환성 때문에 단항 마이너스가 지수 연산자보다 높은 우선순위를 가져요. 실수를 피하려면 -(2^2)처럼 추가 괄호나 pow 함수, 예: -pow(2, 2)를 쓰면 돼요.
't' = true true PostgreSQL과 호환.
1 = '1' true PostgreSQL과 호환.
1 = ' 1' true PostgreSQL과 호환.
1 = '01' true PostgreSQL과 호환.
1 = ' 01 ' true PostgreSQL과 호환.
1 = true true PostgreSQL과 호환되지 않음.
1 = '1.1' true PostgreSQL과 호환되지 않음.
1 IN (0, NULL) NULL 입력과 출력의 NULLUNKNOWN으로 생각하면 이해가 돼요.
1 in [0, NULL] false
concat('abc', NULL) abc PostgreSQL과 호환. list_concat도 비슷하게 동작.
'abc' || NULL NULL

NaN

'NaN'::FLOAT = 'NaN'::FLOAT'NaN'::FLOAT > 3은 IEEE-754를 위반하지만, 부동 소수점 데이터 타입이 다른 모든 데이터 타입처럼 전체 순서(total order)를 가진다는 뜻이에요 (greatest/least에 대한 결과에 주의하세요).

age 함수

age(x)current_timestamp - x가 아니라 current_date - x예요. PostgreSQL에서 계승된 또 다른 특이점이죠.

Extract 함수

list_extract/map_extract는 존재하지 않는 키에 대해 NULL을 반환해요. struct_extract는 struct의 키가 컬럼과 같기 때문에 오류를 던집니다.

절 (Clauses)

SELECT에서의 자동 컬럼 중복 제거

컬럼 이름은 첫 번째 등장이 나머지를 가리는 방식으로 중복 제거돼요.

CREATE TABLE tbl AS SELECT 1 AS a;
SELECT a FROM (SELECT *, 2 AS a FROM tbl);
a
1

컬럼 SELECT의 대소문자 비구분

대소문자를 구분하지 않기 때문에, file.parquet에서 원하는 컬럼 a보다 A라는 컬럼이 먼저 나오면 SELECT a FROM 'file.parquet'를 쓸 수 없어요.

USING SAMPLE

USING SAMPLE 절은 문법적으로 WHEREGROUP BY 절 뒤에 위치하지만(LIMIT 절과 같음), 의미상으로는 둘 다 앞에 적용돼요(LIMIT 절과는 달라요).

더 알아보기 (Learn more)

  • PostgreSQL 호환성 예외는 sql/dialect/postgresql_compatibility 문서를 참고해 주세요.