PostgreSQL 호환성
PostgreSQL 호환성 (PostgreSQL Compatibility)
DuckDB의 SQL 방언은 PostgreSQL 방언의 규칙을 밀접하게 따르는. 몇 안 되는 예외가 이 페이지에 나열돼요.
출처: 문서
본문
부동소수점 산술 (Floating-Point Arithmetic)
DuckDB와 PostgreSQL은 0으로 나누기에서 부동소수점 산술을 다르게 처리해요. DuckDB는 0으로 나누기와 무한대 값을 포함한 연산 모두에서 IEEE 부동소수점 산술 표준 (IEEE 754)을 준수해요. PostgreSQL은 0으로 나누기에서 오류를 반환하지만 무한대 값 처리는 IEEE 754에 맞춰요. 차이를 보여주기 위해 다음 SQL 쿼리를 실행해 보세요:
SELECT 1.0 / 0.0 AS x;
SELECT 0.0 / 0.0 AS x;
SELECT -1.0 / 0.0 AS x;
SELECT 'Infinity'::FLOAT / 'Infinity'::FLOAT AS x;
SELECT 1.0 / 'Infinity'::FLOAT AS x;
SELECT 'Infinity'::FLOAT - 'Infinity'::FLOAT AS x;
SELECT 'Infinity'::FLOAT - 1.0 AS x;
| 표현식 | PostgreSQL | DuckDB | IEEE 754 |
|---|---|---|---|
| 1.0 / 0.0 | error | Infinity | Infinity |
| 0.0 / 0.0 | error | NaN | NaN |
| -1.0 / 0.0 | error | -Infinity | -Infinity |
| 'Infinity' / 'Infinity' | NaN | NaN | NaN |
| 1.0 / 'Infinity' | 0.0 | 0.0 | 0.0 |
| 'Infinity' - 'Infinity' | NaN | NaN | NaN |
| 'Infinity' - 1.0 | Infinity | Infinity | Infinity |
정수 나눗셈 (Division on Integers)
정수 나눗셈을 계산할 때 PostgreSQL은 정수 나눗셈을 수행하는 반면, DuckDB는 부동소수점 나눗셈을 수행해요:
SELECT 1 / 2 AS x;
PostgreSQL은 0을, DuckDB는 0.5를 반환해요.
DuckDB에서 정수 나눗셈을 수행하려면 // 연산자를 사용해요:
SELECT 1 // 2 AS x;
이것은 0을 반환해요.
Boolean과 Integer 값의 UNION
다음 쿼리는 PostgreSQL에서 실패하지만 DuckDB에서는 성공적으로 완료돼요:
SELECT true AS x
UNION
SELECT 2;
PostgreSQL은 오류를 반환해요:
ERROR: UNION types boolean and integer cannot be matched
DuckDB는 강제 캐스트를 수행하므로 쿼리를 완료하고 다음을 반환해요:
| x |
|---|
| 1 |
| 2 |
같음 검사에서의 암시적 캐스팅
DuckDB는 같음 검사에서 암시적 캐스팅을 수행해요(예: 문자열을 숫자와 boolean 값으로 변환). 따라서 PostgreSQL이 오류를 던지는 몇몇 경우에 DuckDB는 성공적으로 결과를 계산해요:
| 표현식 | PostgreSQL | DuckDB |
|---|---|---|
| '1.1' = 1 | error | true |
| '1.1' = 1.1 | true | true |
| 1 = 1.1 | false | false |
| true = 'true' | true | true |
| true = 1 | error | true |
| 'true' = 1 | error | error |
따옴표로 묶인 식별자의 대소문자 구분
PostgreSQL은 대소문자를 구분하지 않아요. PostgreSQL이 대소문자 구분 없음을 달성하는 방식은 SQL 내에서 따옴표로 묶지 않은 식별자를 소문자화하는 것이고, 따옴표는 대소문자를 보존해요. 예를 들어 다음 명령은 mytable이라는 테이블을 만들지만, 따옴표가 대소문자를 보존하므로 MyTaBLe을 쿼리하려 시도해요.
CREATE TABLE MyTaBLe (x INTEGER);
SELECT * FROM "MyTaBLe";
ERROR: relation "MyTaBLe" does not exist
PostgreSQL은 따옴표로 묶인 식별자만 대소문자를 구분하는 게 아니라 모든 식별자를 대소문자 구분으로 취급해요. 예를 들어 이것도 동작하지 않아요:
CREATE TABLE "PreservedCase" (x INTEGER);
SELECT * FROM PreservedCase;
ERROR: relation "preservedcase" does not exist
따라서 PostgreSQL의 대소문자 미구분은 다른 대소문자를 가진 따옴표로 묶인 식별자를 절대 사용하지 않을 때만 동작해요.
DuckDB의 경우 이 동작은 기본적으로 대소문자를 구분하는 다른 도구(예: Parquet, Pandas)와 인터페이스할 때 문제가 됐어요 — 모든 식별자가 항상 소문자화되기 때문이에요. 따라서 DuckDB는 시스템 전반에 걸쳐 식별자를 완전히 대소문자 구분 없게 만들면서도 [그들의 대소문자를 보존]({% link docs/current/sql/dialect/keywords_and_identifiers.md %}#rules-for-case-sensitivity)함으로써 대소문자 미구분을 달성해요.
DuckDB에서 위 스크립트들은 성공적으로 완료돼요:
CREATE TABLE MyTaBLe (x INTEGER);
SELECT * FROM "MyTaBLe";
CREATE TABLE "PreservedCase" (x INTEGER);
SELECT * FROM PreservedCase;
SELECT tbl FROM duckdb_tables();
| tbl |
|---|
| MyTaBLe |
| PreservedCase |
식별자를 소문자화하는 PostgreSQL의 동작은 [preserve_identifier_case 옵션]({% link docs/current/configuration/overview.md %}#local-configuration-options)으로 접근할 수 있어요:
SET preserve_identifier_case = false;
CREATE TABLE MyTaBLe (x INTEGER);
SELECT tbl FROM duckdb_tables();
| tbl |
|---|
| mytable |
하지만 시스템에서 식별자의 대소문자 미구분 매칭은 끌 수 없어요.
비교에 이중 등호 기호 사용
DuckDB는 =와 == 둘 다 같음 비교를 지원하는 반면, PostgreSQL은 =만 지원해요.
SELECT 1 == 1 AS t;
DuckDB는 true를 반환하고, PostgreSQL은 다음을 반환해요:
postgres=# SELECT 1 == 1 AS t;
ERROR: operator does not exist: integer == integer
LINE 1: SELECT 1 == 1 AS t;
==의 사용은 이식성이 제한적이므로 권장되지 않는다는 점을 주목해요.
테이블 Vacuum
PostgreSQL에서 VACUUM 구문은 테이블을 가비지 컬렉션하고 테이블을 분석해요.
DuckDB에서 [VACUUM 구문]({% link docs/current/sql/statements/vacuum.md %})은 통계를 재구성하는 데만 사용돼요.
공간을 회수하는 방법은 [“Reclaiming space” 페이지]({% link docs/current/operations_manual/footprint_of_duckdb/reclaiming_space.md %})를 참고해요.
문자열 (Strings)
버전 1.3.0부터 DuckDB는 중첩 데이터 구조에 직렬화된 문자열에서 ' 같은 문자를 이스케이프해요.
PostgreSQL은 이렇게 하지 않아요.
예를 들어 다음을 실행해 보세요:
SELECT ARRAY[''''];
PostgreSQL은 반환해요:
{'}
DuckDB는 반환해요:
['\'']
함수 (Functions)
regexp_extract 함수
PostgreSQL의 regexp_substr 함수와 달리, DuckDB의 regexp_extract는 매치가 없을 때 NULL 대신 빈 문자열을 반환해요.
to_date 함수
DuckDB는 PostgreSQL의 to_date 날짜 포매팅 함수를 지원하지 않아요.
대신 [strptime 함수]({% link docs/current/sql/functions/dateformat.md %}#strptime-examples)를 사용해 주세요.
date_part 함수
[date_part 함수]({% link docs/current/sql/functions/datepart.md %})가 추출하는 대부분의 부분은 정수로 반환돼요. DuckDB에는 무한 정수 값이 없으므로 무한 타임스탬프에 대해서는 NULL이 반환돼요.
스키마의 타입 이름 해석
[CREATE TABLE 구문]({% link docs/current/sql/statements/create_table.md %})의 경우 DuckDB는 테이블이 생성되는 스키마에서 타입 이름을 해석하려고 시도해요. 예를 들어:
CREATE SCHEMA myschema;
CREATE TYPE myschema.mytype AS ENUM ('as', 'df');
CREATE TABLE myschema.mytable (v mytype);
PostgreSQL은 마지막 구문에서 오류를 반환해요:
ERROR: type "mytype" does not exist
LINE 1: CREATE TABLE myschema.mytable (v mytype);
DuckDB는 구문을 실행하고 테이블을 성공적으로 생성하는데, 다음 쿼리로 확인할 수 있어요:
DESCRIBE myschema.mytable;
| column_name | column_type | null | key | default | extra |
|---|---|---|---|---|---|
| v | ENUM('as', 'df') | YES | NULL | NULL | NULL |
GROUP BY에서 함수적 의존성 활용
PostgreSQL은 다음 쿼리에서 i -> j 같은 함수적 의존성을 활용할 수 있어요:
CREATE TABLE tbl (i INTEGER, j INTEGER, PRIMARY KEY (i));
SELECT j
FROM tbl
GROUP BY i;
PostgreSQL은 이 쿼리를 실행해요.
DuckDB는 실패해요:
Binder Error:
column "j" must appear in the GROUP BY clause or must be part of an aggregate function.
Either add it to the GROUP BY list, or use "ANY_VALUE(j)" if the exact value of "j" is not important.
이를 우회하려면 다른 속성들을 추가하거나 [GROUP BY ALL 절]({% link docs/current/sql/query_syntax/groupby.md %}#group-by-all)을 사용해요.
정규식 매치 연산자의 동작
PostgreSQL은 [POSIX 정규식 매칭 연산자]({% link docs/current/sql/functions/pattern_matching.md %}) ~(대소문자 구분 부분 regex 매칭)와 ~*(대소문자 구분 없는 부분 regex 매칭)와 그 부정 변형 !~, !~*를 지원해요.
DuckDB에서 ~는 [regexp_full_match]({% link docs/current/sql/functions/text.md %}#regexp_full_matchstring-regex)와 동등하고 !~는 NOT regexp_full_match와 동등해요.
~*와 !~* 연산자는 지원되지 않아요.
아래 테이블은 PostgreSQL과 DuckDB에서 이 함수들 사이의 대응이 거의 존재하지 않음을 보여줘요. DuckDB에서 POSIX 정규식 매칭 연산자 사용을 피하는 것이 좋아요.
| 표현식 | PostgreSQL | DuckDB |
|---|---|---|
| `'aaa' ~ '(a | b)'` | true |
| `'AAA' ~* '(a | b)'` | true |
| `'aaa' !~ '(a | b)'` | false |
| `'AAA' !~* '(a | b)'` | false |
더 알아보기 (Learn more)
패턴 매칭 연산자의 사용법은 [Pattern Matching]({% link docs/current/sql/functions/pattern_matching.md %}) 문서를 참고해요.