https://2stndard.tistory.com/notice/203
[발간예정][EPL과 유튜브로 배우는 DuckDB] 실습 코드와 데이터
EPL과 유튜브 데이터로 배우는 DuckDB에서 사용되는 실습 데이터와 코드를 제공합니다. EPL_DATA&samplefile.zip : 책에서 사용하는 영국 프리미어리그 데이터 셋과 샘플로 사용하는 파일espn.duckdb.zip : 책
2stndard.tistory.com
TL;DR : 이번 ‘DuckDB Tricks’ 에서는 테이블을 편리하게 관리할 수 있는 기능과 Parquet 및 CSV 파일의 성능 최적화 요령을 소개합니다.
데이터셋
이 블로그에서는 네덜란드 철도 운행 정보 데이터셋의 일부를 사용합니다. 이 데이터셋 중 2024년 1월부터 10월까지의 CSV 파일을 사용할 예정입니다: services-2024-01-to-10.zip. 예제를 따라 해보시려면, 실습을 진행하기 전에 데이터셋을 다운로드하여 압축을 풀어 두시기 바랍니다.
테이블에서 열 제외하기
먼저 CSV 파일에 있는 데이터를 살펴보겠습니다. 8월 CSV 파일의 내용을 DESCRIBE 문을 통해 확인해 보겠습니다.
DESCRIBE FROM 'services-2024-08.csv';
이 결과를 보면 열 이름과 열 유형이 포함된 표가 생성됩니다.
┌───────────────────────────────────────────────────────┐
│ services-2024-08.csv │
│ │
│ Service:RDT-ID bigint │
│ Service:Date date │
│ Service:Type varchar │
│ Service:Company varchar │
│ Service:Train number bigint │
│ Service:Completely cancelled boolean │
│ Service:Partly cancelled boolean │
│ Service:Maximum delay bigint │
│ Stop:RDT-ID bigint │
│ Stop:Station code varchar │
│ Stop:Station name varchar │
│ Stop:Arrival time timestamp with time zone │
│ Stop:Arrival delay bigint │
│ Stop:Arrival cancelled boolean │
│ Stop:Departure time timestamp with time zone │
│ Stop:Departure delay bigint │
│ Stop:Departure cancelled boolean │
└───────────────────────────────────────────────────────┘
SUMMARIZE 함수를 사용하면 이 파일에 대한 기술통계를 살펴볼 수 있습니다.
SUMMARIZE FROM 'services-2024-08.csv';
┌──────────────────────────────┬─────────────────────────┬───┬─────────┬─────────────────┐
│ column_name │ column_type │ … │ count │ null_percentage │
│ varchar │ varchar │ … │ int64 │ decimal(9,2) │
├──────────────────────────────┼─────────────────────────┼───┼─────────┼─────────────────┤
│ Service:RDT-ID │ BIGINT │ … │ 1846574 │ 0.00 │
│ Service:Date │ DATE │ … │ 1846574 │ 0.00 │
│ Service:Type │ VARCHAR │ … │ 1846574 │ 0.00 │
│ Service:Company │ VARCHAR │ … │ 1846574 │ 0.00 │
│ Service:Train number │ BIGINT │ … │ 1846574 │ 0.00 │
│ Service:Completely cancelled │ BOOLEAN │ … │ 1846574 │ 0.00 │
│ Service:Partly cancelled │ BOOLEAN │ … │ 1846574 │ 0.00 │
│ Service:Maximum delay │ BIGINT │ … │ 1846574 │ 0.00 │
│ Stop:RDT-ID │ BIGINT │ … │ 1846574 │ 0.00 │
│ Stop:Station code │ VARCHAR │ … │ 1846574 │ 0.00 │
│ Stop:Station name │ VARCHAR │ … │ 1846574 │ 0.00 │
│ Stop:Arrival time │ TIMESTAMP WITH TIME ZO… │ … │ 1846574 │ 11.07 │
│ Stop:Arrival delay │ BIGINT │ … │ 1846574 │ 11.07 │
│ Stop:Arrival cancelled │ BOOLEAN │ … │ 1846574 │ 11.07 │
│ Stop:Departure time │ TIMESTAMP WITH TIME ZO… │ … │ 1846574 │ 11.07 │
│ Stop:Departure delay │ BIGINT │ … │ 1846574 │ 11.07 │
│ Stop:Departure cancelled │ BOOLEAN │ … │ 1846574 │ 11.07 │
└──────────────────────────────┴─────────────────────────┴───┴─────────┴─────────────────┘
17 rows use .last to show entire result 12 columns (4 shown)
SUMMARIZE를 사용하면 데이터에 대한 10가지 통계값(min, max, approx_unique 등)을 얻을 수 있습니다. 결과에서 이 중 일부를 제외하고 싶다면 EXCLUDE 수정자를 사용할 수 있습니다. 예를 들어, min, max 및 사분위수 q25, q50, q75를 제외하려면 다음 명령어를 실행하면 됩니다:
SELECT * EXCLUDE(min, max, q25, q50, q75)
FROM (SUMMARIZE FROM 'services-2024-08.csv');
┌──────────────────────────────┬─────────────────────────┬───┬─────────┬─────────────────┐
│ column_name │ column_type │ … │ count │ null_percentage │
│ varchar │ varchar │ … │ int64 │ decimal(9,2) │
├──────────────────────────────┼─────────────────────────┼───┼─────────┼─────────────────┤
│ Service:RDT-ID │ BIGINT │ … │ 1846574 │ 0.00 │
│ Service:Date │ DATE │ … │ 1846574 │ 0.00 │
│ Service:Type │ VARCHAR │ … │ 1846574 │ 0.00 │
│ Service:Company │ VARCHAR │ … │ 1846574 │ 0.00 │
│ Service:Train number │ BIGINT │ … │ 1846574 │ 0.00 │
│ Service:Completely cancelled │ BOOLEAN │ … │ 1846574 │ 0.00 │
│ Service:Partly cancelled │ BOOLEAN │ … │ 1846574 │ 0.00 │
│ Service:Maximum delay │ BIGINT │ … │ 1846574 │ 0.00 │
│ Stop:RDT-ID │ BIGINT │ … │ 1846574 │ 0.00 │
│ Stop:Station code │ VARCHAR │ … │ 1846574 │ 0.00 │
│ Stop:Station name │ VARCHAR │ … │ 1846574 │ 0.00 │
│ Stop:Arrival time │ TIMESTAMP WITH TIME ZO… │ … │ 1846574 │ 11.07 │
│ Stop:Arrival delay │ BIGINT │ … │ 1846574 │ 11.07 │
│ Stop:Arrival cancelled │ BOOLEAN │ … │ 1846574 │ 11.07 │
│ Stop:Departure time │ TIMESTAMP WITH TIME ZO… │ … │ 1846574 │ 11.07 │
│ Stop:Departure delay │ BIGINT │ … │ 1846574 │ 11.07 │
│ Stop:Departure cancelled │ BOOLEAN │ … │ 1846574 │ 11.07 │
└──────────────────────────────┴─────────────────────────┴───┴─────────┴─────────────────┘
17 rows use .last to show entire result 7 columns (4 shown)
또는 NOT SIMILAR TO 연산자와 함께 COLUMNS 표현식을 사용할 수도 있습니다. 이 방법은 정규 표현식과 함께 사용할 수 있습니다:
SELECT COLUMNS(lambda c: c NOT SIMILAR TO 'min|max|q.*')
FROM (SUMMARIZE FROM 'services-2024-08.csv');
┌──────────────────────────────┬─────────────────────────┬───┬─────────┬─────────────────┐
│ column_name │ column_type │ … │ count │ null_percentage │
│ varchar │ varchar │ … │ int64 │ decimal(9,2) │
├──────────────────────────────┼─────────────────────────┼───┼─────────┼─────────────────┤
│ Service:RDT-ID │ BIGINT │ … │ 1846574 │ 0.00 │
│ Service:Date │ DATE │ … │ 1846574 │ 0.00 │
│ Service:Type │ VARCHAR │ … │ 1846574 │ 0.00 │
│ Service:Company │ VARCHAR │ … │ 1846574 │ 0.00 │
│ Service:Train number │ BIGINT │ … │ 1846574 │ 0.00 │
│ Service:Completely cancelled │ BOOLEAN │ … │ 1846574 │ 0.00 │
│ Service:Partly cancelled │ BOOLEAN │ … │ 1846574 │ 0.00 │
│ Service:Maximum delay │ BIGINT │ … │ 1846574 │ 0.00 │
│ Stop:RDT-ID │ BIGINT │ … │ 1846574 │ 0.00 │
│ Stop:Station code │ VARCHAR │ … │ 1846574 │ 0.00 │
│ Stop:Station name │ VARCHAR │ … │ 1846574 │ 0.00 │
│ Stop:Arrival time │ TIMESTAMP WITH TIME ZO… │ … │ 1846574 │ 11.07 │
│ Stop:Arrival delay │ BIGINT │ … │ 1846574 │ 11.07 │
│ Stop:Arrival cancelled │ BOOLEAN │ … │ 1846574 │ 11.07 │
│ Stop:Departure time │ TIMESTAMP WITH TIME ZO… │ … │ 1846574 │ 11.07 │
│ Stop:Departure delay │ BIGINT │ … │ 1846574 │ 11.07 │
│ Stop:Departure cancelled │ BOOLEAN │ … │ 1846574 │ 11.07 │
└──────────────────────────────┴─────────────────────────┴───┴─────────┴─────────────────┘
17 rows use .last to show entire result 7 columns (4 shown)
패턴 매칭을 이용한 열 이름 변경
열을 살펴보면, 이름에 공백과 세미콜론(;)이 포함되어 있음을 알 수 있습니다. 이러한 특수 문자는 열 이름을 큰따옴표로 묶어야 하므로 쿼리 작성을 다소 번거롭게 만듭니다. 예를 들어, 다음 쿼리에서는 "Service:Company"라고 작성해야 합니다:
SELECT DISTINCT "Service:Company" AS company,
FROM 'services-2024-08.csv'
ORDER BY company;
┌────────────┐
│ company │
│ varchar │
├────────────┤
│ Arriva │
│ Blauwnet │
│ Breng │
│ DB │
│ Eu Sleeper │
│ Eurobahn │
│ Keolis │
│ NMBS │
│ NS │
│ NS Int │
│ R-net │
│ RRReis │
│ VIAS │
│ ZLSM │
└────────────┘
14 rows
이번에는 COLUMNS 표현식을 사용하여 열 이름을 어떻게 변경할 수 있는지 살펴보겠습니다. 특수 문자(최대 2개)를 대체하려면 다음과 같이 쿼리를 작성하면 됩니다.
SELECT COLUMNS('(.*?)_*$') AS "\1"
FROM (
SELECT COLUMNS('(\w*)\W*(\w*)\W*(\w*)') AS "\1_\2_\3"
FROM 'services-2024-08.csv'
);
┌────────────────┬──────────────┬───┬──────────────────────┬──────────────────────────┐
│ Service_RDT_ID │ Service_Date │ … │ Stop_Departure_delay │ Stop_Departure_cancelled │
│ int64 │ date │ … │ int64 │ boolean │
├────────────────┼──────────────┼───┼──────────────────────┼──────────────────────────┤
│ 14095603 │ 2024-08-01 │ … │ 0 │ false │
│ 14095603 │ 2024-08-01 │ … │ 0 │ false │
│ 14095603 │ 2024-08-01 │ … │ NULL │ NULL │
│ 14095603 │ 2024-08-01 │ … │ 0 │ true │
│ 14095603 │ 2024-08-01 │ … │ 109 │ false │
│ · │ · │ … │ · │ · │
│ · │ · │ … │ · │ · │
│ · │ · │ … │ · │ · │
│ 14316715 │ 2024-08-31 │ … │ 3 │ false │
│ 14316715 │ 2024-08-31 │ … │ 2 │ false │
│ 14316715 │ 2024-08-31 │ … │ 1 │ false │
│ 14316715 │ 2024-08-31 │ … │ 0 │ false │
│ 14316715 │ 2024-08-31 │ … │ NULL │ NULL │
└────────────────┴──────────────┴───┴──────────────────────┴──────────────────────────┘
1.85 million rows (10 shown, 1846574 total) 17 columns (4 shown)
첫 번째 COLUMNS 표현식부터 시작하여 쿼리를 하나씩 살펴보겠습니다:
SELECT COLUMNS('(\w*)\W*(\w*)\W*(\w*)') AS "\1_\2_\3"
이 정규표현식에서 (\w*) 그룹을 사용하여 0개 이상의 단어 문자(word characters)를 캡처합니다.
여기서 단어 문자는 [0-9A-Za-z_]를 의미합니다.
반면 \W* 표현식은 0개 이상의 비단어 문자(non-word characters), 즉 [^0-9A-Za-z_]를 캡처합니다.
별칭(alias) 부분에서는 \i 형식으로 i번째 캡처 그룹을 참조합니다. 따라서 "\1_\2_\3" 는 각 단어 그룹만 유지한 뒤, 그룹 사이를 밑줄(_)로 연결하겠다는 의미입니다.
하지만 일부 컬럼 이름은 단어 사이에 공백이 있고, 일부는 그렇지 않기 때문에, 이 SELECT 문을 실행한 뒤에는 Service_Date_ 와 같이 끝에 불필요한 밑줄(_)이 붙은 컬럼 이름이 생성됩니다.
따라서 추가적인 후처리 단계가 필요합니다.
SELECT COLUMNS('(.*?)_*$') AS "\1"
여기서는 끝부분의 밑줄(_)을 제외한 문자 그룹을 캡처한 뒤, 컬럼 이름을 \1로 다시 지정합니다.
이렇게 하면 컬럼명 뒤에 붙어 있던 불필요한 밑줄이 제거됩니다.
또한 쿼리를 더 편리하게 작성하기 위해, 식별자(identifier)가 대소문자를 구분하지 않는 특성을 활용하여 컬럼 이름을 모두 소문자로 사용해 조회할 수 있습니다.
SELECT DISTINCT service_company
FROM (
SELECT COLUMNS('(.*?)_*$') AS "\1"
FROM (
SELECT COLUMNS('(\w*)\W*(\w*)\W*(\w*)') AS "\1_\2_\3"
FROM 'services-2024-08.csv'
)
)
ORDER BY service_company;
┌─────────────────┐
│ Service_Company │
│ varchar │
├─────────────────┤
│ Arriva │
│ Blauwnet │
│ Breng │
│ DB │
│ Eu Sleeper │
│ · │
│ · │
│ · │
│ NS Int │
│ R-net │
│ RRReis │
│ VIAS │
│ ZLSM │
└─────────────────┘
14 rows
(10 shown)
쿼리에서는 소문자를 사용했지만, 반환된 열 이름은 원래의 대소문자 구분을 그대로 유지합니다.
Globbing을 이용한 데이터 로드
이제 열 이름을 간소화할 수 있게 되었으니, 3개월 치 데이터를 모두 테이블에 로드해 보겠습니다:
CREATE OR REPLACE TABLE services AS
SELECT COLUMNS('(.*?)_*$') AS "\1"
FROM (
SELECT COLUMNS('(\w*)\W*(\w*)\W*(\w*)') AS "\1_\2_\3"
FROM 'services-2024-*.csv'
);
FROM services;
┌────────────────┬──────────────┬───┬──────────────────────┬──────────────────────────┐
│ Service_RDT_ID │ Service_Date │ … │ Stop_Departure_delay │ Stop_Departure_cancelled │
│ int64 │ date │ … │ int64 │ boolean │
├────────────────┼──────────────┼───┼──────────────────────┼──────────────────────────┤
│ 12690865 │ 2024-01-01 │ … │ 0 │ false │
│ 12690865 │ 2024-01-01 │ … │ 0 │ false │
│ 12690865 │ 2024-01-01 │ … │ 0 │ false │
│ 12690865 │ 2024-01-01 │ … │ 0 │ false │
│ 12690865 │ 2024-01-01 │ … │ 0 │ false │
│ · │ · │ … │ · │ · │
│ · │ · │ … │ · │ · │
│ · │ · │ … │ · │ · │
│ 14727486 │ 2024-10-31 │ … │ 14 │ false │
│ 14727486 │ 2024-10-31 │ … │ NULL │ NULL │
│ 14727659 │ 2024-10-31 │ … │ 0 │ false │
│ 14727659 │ 2024-10-31 │ … │ 0 │ false │
│ 14727659 │ 2024-10-31 │ … │ NULL │ NULL │
└────────────────┴──────────────┴───┴──────────────────────┴──────────────────────────┘
18.24 million rows (10 shown, 18237799 total) 17 columns (4 shown)
내부 FROM 절에서는 * 글롭(glob) 문법을 사용하여 모든 파일과 일치하도록 지정합니다.
DuckDB는 모든 파일이 동일한 스키마를 가지고 있다는 것을 자동으로 감지하고, 이를 하나의 테이블처럼 합쳐(union) 처리합니다.
그 결과, 이제 1월부터 10월까지의 모든 데이터를 포함하는 테이블이 생성되며, 전체 행 수는 거의 2천만 개에 달합니다.
Parquet 파일 재정렬
네덜란드 철도회사(Nederlandse Spoorwegen, NS)가 운영하는 Intercity Direct 열차의 평균 지연 시간을 분석하고 싶다고 가정해봅시다.
지연 시간은 각 열차 운행의 최종 목적지에서 측정된 값입니다.
이 분석은 .csv 파일에 대해 직접 수행할 수도 있지만, CSV 파일에는 스키마(schema)나 최소-최대 인덱스(min-max indexes)와 같은 메타데이터가 없기 때문에 성능이 제한될 수 있습니다.
이제 CLI 클라이언트에서 타이머를 켜서 성능을 측정해보겠습니다.
.timer on
SELECT avg("Stop:Arrival delay")
FROM 'services-*.csv'
WHERE "Service:Company" = 'NS'
AND "Service:Type" = 'Intercity direct'
AND "Stop:Departure time" IS NULL;
┌───────────────────────────┐
│ avg("Stop:Arrival delay") │
│ double │
├───────────────────────────┤
│ 3.00736052638916 │
└───────────────────────────┘
Run Time (s): real 6.684 user 8.890625 sys 3.593750
1천8백만개의 데이터에 대한 평균을 구하는 이 쿼리는 약 6.6초가 걸립니다.
이제 이미 DuckDB에 로드되어 있는 services 테이블에 대해 동일한 쿼리를 실행하면, 쿼리 속도가 훨씬 빨라집니다.
SELECT avg(Stop_Arrival_delay)
FROM services
WHERE Service_Company = 'NS'
AND Service_Type = 'Intercity direct'
AND Stop_Departure_time IS NULL;
┌─────────────────────────┐
│ avg(Stop_Arrival_delay) │
│ double │
├─────────────────────────┤
│ 3.00736052638916 │
└─────────────────────────┘
Run Time (s): real 0.142 user 0.421875 sys 0.015625
실행 시간은 약 0.14초입니다.
외부 바이너리 파일 형식을 사용하고 싶다면, 데이터베이스를 하나의 Parquet 파일로 내보낼 수도 있습니다.
EXPORT DATABASE 'railway' (FORMAT parquet);
그 다음에는 다음과 같이 해당 파일에 직접 쿼리를 수행할 수 있습니다.
SELECT avg(Stop_Arrival_delay)
FROM 'railway/services.parquet'
WHERE Service_Company = 'NS'
AND Service_Type = 'Intercity direct'
AND Stop_Departure_time IS NULL;
┌─────────────────────────┐
│ avg(Stop_Arrival_delay) │
│ double │
├─────────────────────────┤
│ 3.00736052638916 │
└─────────────────────────┘
Run Time (s): real 0.190 user 0.687500 sys 0.187500
이 형식의 실행 시간은 약 0.19초입니다.
DuckDB 자체 파일 형식보다는 다소 느리지만, 원본 CSV 파일을 읽는 것보다는 약 30배 빠릅니다.
만약 쿼리가 어떤 필드를 기준으로 필터링하는지 사전에 알고 있다면, 쿼리 성능을 향상시키기 위해 Parquet 파일의 정렬 순서를 재배치할 수 있습니다.
COPY
(FROM 'railway/services.parquet' ORDER BY Service_Company, Service_Type)
TO 'railway/services.parquet';
이제 이 parquet 파일을 사용해 동일한 쿼리를 실행해보겠습니다.
SELECT avg(Stop_Arrival_delay)
FROM 'railway/services.parquet'
WHERE Service_Company = 'NS'
AND Service_Type = 'Intercity direct'
AND Stop_Departure_time IS NULL;
┌─────────────────────────┐
│ avg(Stop_Arrival_delay) │
│ double │
├─────────────────────────┤
│ 3.00736052638916 │
└─────────────────────────┘
Run Time (s): real 0.038 user 0.078125 sys 0.000000
이 쿼리를 다시 실행하면 실행 속도가 눈에 띄게 빨라져 약 0.038초가 소요됩니다.
이는 zonemap(최소-최대 인덱스)을 활용하여 스캔해야 하는 데이터 양을 줄이는 부분 읽기(partial reading) 덕분입니다.
파일을 재정렬하면 DuckDB가 더 많은 데이터를 건너뛸 수 있게 되며, 그 결과 쿼리 실행 시간이 더욱 빨라집니다.
Hive Partitioning
쿼리 속도를 더욱 향상시키기 위해, 쿼리에서 사용하는 필터 조건에 맞는 디렉터리 구조를 디스크 상에 생성하는 Hive 파티셔닝(Hive partitioning)을 사용할 수 있습니다.
COPY services
TO 'services-parquet-hive'
(FORMAT parquet, PARTITION_BY (Service_Company, Service_Type));
services-parquet-hive
├── Service_Company=Arriva
│ ├── Service_Type=Extra%20trein
│ │ └── data_0.parquet
│ ├── Service_Type=Nachttrein
│ │ └── data_0.parquet
│ ├── Service_Type=Snelbus%20ipv%20trein
│ │ └── data_0.parquet
│ ├── Service_Type=Sneltrein
│ │ └── data_0.parquet
│ ├── Service_Type=Stopbus%20ipv%20trein
│ │ └── data_0.parquet
│ ├── Service_Type=Stoptrein
│ │ └── data_0.parquet
│ └── Service_Type=Taxibus%20ipv%20trein
│ └── data_0.parquet
├── Service_Company=Blauwnet
│ ├── Service_Type=Intercity
│ │ └── data_0.parquet
...
이제 hive_partitioning = true 옵션을 지정하여 Hive 파티셔닝된 데이터셋에 대해 쿼리를 실행할 수 있습니다.
SELECT avg(Stop_Arrival_delay)
FROM read_parquet(
'services-parquet-hive/**/*.parquet',
hive_partitioning = true
)
WHERE Service_Company = 'NS'
AND Service_Type = 'Intercity direct'
AND Stop_Departure_time IS NULL;
┌─────────────────────────┐
│ avg(Stop_Arrival_delay) │
│ double │
├─────────────────────────┤
│ 3.00736052638916 │
└─────────────────────────┘
Run Time (s): real 0.040 user 0.015625 sys 0.015625
이제 이 쿼리는 약 0.04초 정도만 소요됩니다.
DuckDB가 디렉터리 구조를 활용하여 읽어야 하는 데이터를 더욱 효과적으로 제한할 수 있기 때문입니다.
그리고 Hive 파티셔닝의 멋진 점은, 이런 방식이 CSV 파일에도 그대로 적용된다는 것입니다.
COPY services
TO 'services-csv-hive'
(FORMAT csv, PARTITION_BY (Service_Company, Service_Type));
Run Time (s): real 8.566 user 29.890625 sys 4.750000
SELECT avg(Stop_Arrival_delay)
FROM read_csv('services-csv-hive/**/*.csv', hive_partitioning = true)
WHERE Service_Company = 'NS'
AND Service_Type = 'Intercity direct'
AND Stop_Departure_time IS NULL;
┌─────────────────────────┐
│ avg(Stop_Arrival_delay) │
│ double │
├─────────────────────────┤
│ 3.00736052638916 │
└─────────────────────────┘
Run Time (s): real 0.314 user 0.312500 sys 0.015625
CSV 파일 자체에는 어떤 형태의 메타데이터도 없지만, DuckDB는 디렉터리 구조를 활용하여 관련 있는 디렉터리만 스캔할 수 있습니다.
그 결과 실행 시간은 약 0.3초 수준으로 줄어들며, 모든 CSV 파일을 읽는 방식과 비교하면 20배 이상 빠른 성능을 보여줍니다.
이처럼 다양한 형식과 결과 때문에 조금 혼란스럽게 느껴진다면 걱정하지 않아도 됩니다. 아래의 요약 표에서 전체 내용을 한눈에 정리해보겠습니다.
| Format | Query runtime |
| DuckDB file format | 0.14초 |
| CSV (vanilla) | 6.68초 |
| CSV (Hive-partitioned) | 0.31초 |
| Parquet (vanilla) | 0.019초 |
| Parquet (reordered) | 0.040초 |
| Parquet (Hive-partitioned) |
0.038초 |
<출처: https://duckdb.org/2024/11/29/duckdb-tricks-part-3>
'EPL과 유튜브 데이터로 배우는 DuckDB' 카테고리의 다른 글
| Airflow, DuckDB, Streamlit으로 StarCraft 2 데이터 탐색하기 (0) | 2026.05.20 |
|---|---|
| Quack: DuckDB의 클라이언트-서버 프로토콜 (0) | 2026.05.19 |
| DuckDB Tricks – Part 2 (0) | 2026.05.17 |
| DuckDB Tricks - Part 1 (0) | 2026.05.17 |
| DuckLake:레이크하우스 형식의 SQL (0) | 2026.05.16 |
댓글