터를 닦지 않고 든다
INTRODUCTION TO MOTHERDUCK
지금까지는 내 기계에 설치한 DuckDB로 데이터를 다루는 법을 익혔다. 데이터가 지역에 있든 원격 서버에 있든 그러했다. 내 기계에서 DuckDB를 쓰는 일에는 DuckDB를 설치하고 Anaconda 같은 개발 환경을 갖추는 일이 딸린다. 그런데 기계를 갖추는 번거로움 없이 데이터를 빠르게 분석하고 싶다면 어떻게 하는가. 여기서 MotherDuck이 등장한다.
MotherDuck은 DuckDB 위에 세운 서버 없는(serverless) 클라우드 분석 플랫폼이다. 클라우드 기반의 관리 가능성, 확장성, 진전된 분석 능력을 제공해 DuckDB를 확장한다. 요컨대 이 책에서 익힌 모든 연산을 자기 기계를 갖추지 않고 MotherDuck으로 수행할 수 있다.
많은 데이터 분석 사용자에게 복잡한 데이터 기반 시설이 늘 필요한 것은 아니다. 그들이 흔히 필요한 것은 데이터를 손쉽게 호스팅하고 공유할 수 있는 서버 없는 데이터 웨어하우스다. MotherDuck으로는 데이터를 클라우드에 힘들이지 않고 올려 곧바로 질의할 수 있다. Parquet, CSV, JSON, Iceberg, Delta Lake 같은 여러 데이터 형식을 지원한다. 게다가 이중 실행(Dual Execution)이라는 기능을 통해 질의의 일부는 지역에서, 다른 일부는 클라우드에서 실행할 수 있다.
또한 MotherDuck은 사용자마다 분리되고 격리된 컴퓨트 인스턴스를 배정해, 조직의 관리를 간결하게 만들고 비용을 줄인다. 이 컴퓨트 인스턴스는 개별적으로 확장되어 수 테라바이트에 걸친 작업 부하를 감당한다.
이 장에서 익히는 것은 일곱이다. 데이터베이스를 올리고 만드는 일, 남과 나누는 일, 나눈 데이터베이스를 붙이는 일, 질의하는 일, AI로 SQL 문을 쓰는 일, 지역 DuckDB 데이터베이스를 MotherDuck과 잇는 일, 그리고 지역과 원격을 함께 다루는 하이브리드 질의다.
MotherDuck의 운영 기반 시설은 Amazon Web Services에 호스팅되며, 서비스는 주로 Kubernetes가 제어하는 컨테이너로 실행된다. “서버 없음”이란 서버가 없다는 뜻이 아니라 사용자가 서버를 돌보지 않는다는 뜻이다.
가입과 요금제
MotherDuck을 쓰려면 무료 30일 시험 사용으로 시작한다. 시험 기간이 끝난 뒤에는 무료 계정을 이어 쓰거나 유료 요금제로 올릴 수 있다.
- https://motherduck.com을 찾는다.
- 30-DAY TRIAL 버튼을 누른다.
- 로그인 방식을 고른다. Google 계정, GitHub 계정, 또는 전자우편과 비밀번호를 쓸 수 있다.
- 로그인하면 MotherDuck의 웹 UI로 들어간다. 왼쪽에는 Notebooks와 Attached databases 같은 절이 담긴 패널이 있고, 오른쪽에는 SQL 질의를 적는 노트북이 있다.
이 인터페이스가 Jupyter Notebook을 닮았다는 점이 눈에 띈다. 낯익은 환경에서 매끄럽게 옮겨 다니며 질의를 쓸 수 있게 하려는 것이다. 제7장에서 노트북에 SQL 셀을 넣는 도구를 익혔는데, 여기서는 그 노트북 자체가 클라우드로 올라간다.
화면 위쪽의 “Learn more” 버튼을 누르면 MotherDuck이 제공하는 두 요금제를 볼 수 있다. 30일 시험이 끝나면 “Switch to Free” 버튼으로 무료 요금제로 옮길 수 있고, 그것이 제공하는 것보다 더 많은 자원이 필요하면 곧바로 표준 요금제로 올릴 수 있다. 두 요금제의 주된 차이는 컴퓨트 유닛(CU)과 저장 선택지에 있다. 표준 요금제는 추가 CU와 저장 공간을 구입할 수 있고, 무료 요금제는 그런 선택지를 제공하지 않는다.
이 장은 절차의 절반이 웹 UI를 누르는 순서이고 결과는 스크린숏 도판이다. 그래서 이 페이지는 명령의 구문과 절차의 순서를 정확히 옮기는 데 힘을 쏟았다. 다행히 DuckDB CLI 절의 박스 출력은 지면에 온전히 인쇄되어 있어 그대로 옮겼고, 하이브리드 질의의 결과는 두 표를 대조해 검산했다.
표기 규칙은 앞 여덟 장과 같다. 복원한 부분은 점선 밑줄, 복원하지 않은 부분은 …, 검토자가 대조해 맞춘 값은 점선을 두른 글자다.
아울러 이 권은 아홉 권을 닫는 권이므로, 여덟 권 내내 낙관에만 쓰던 주칠(朱漆)을 표지 색으로 올렸다. 그래서 이 권만 낙관이 먹빛이다.
이미 붙어 있는 두 채
GETTING STARTED — ATTACHED DATABASES
로그인하면 창의 왼쪽에 두 개의 주요 절이 보인다. Notebooks와 Attached databases다. 계정에는 기본적으로 두 개의 데이터베이스가 붙어 있다.
my_db
read & write자기 데이터를 올리고 관리할 수 있는 데이터베이스다. 읽기와 쓰기 권한을 모두 가진다.
sample_data
read only · shared실험과 연습을 위해 여러 표를 담은 표본 데이터베이스다. 아이콘에 사람 그림이 붙어 있어 여러 사용자가 접근하는 공유 데이터베이스임을 알린다. 여러 사용자가 함께 쓰므로 읽기 권한만 가진다.
각 데이터베이스 안에서는 표를 하나 이상의 스키마로 정리할 수 있다. 스키마는 관련된 표들의 논리적 묶음으로, 데이터를 구조화하고 효과적으로 관리하는 데 도움이 된다. 데이터베이스 이름에 마우스를 올리면 그 유형과 권한이 표시된다.
제3장에서 ATTACH로 별채를 붙이고 USE로 갈아탔던 일이 여기서는 화면 왼쪽의 목록으로 나타난다. 그때 명령줄에서 별표로 활성 데이터베이스를 표시했던 것이, 이제는 아이콘과 권한 표시로 바뀐다. 도구가 바뀌어도 다루는 대상은 같다.
표를 올리고 스키마를 짓는다
ADDING TABLES · CREATING SCHEMAS
자기 데이터를 MotherDuck에 올리려면 왼쪽 패널 위쪽의 Add Data 버튼을 누른다. 올릴 파일을 고르라는 안내가 나오고, CSV 파일을 고르면 새 셀이 만들어져 그 내용이 표시된다. 원서의 예에서는 airlines.csv다.
-- ① 자동으로 만들어진 문장 — 목적지가 없다 CREATE OR REPLACE TABLE airlines AS SELECT * FROM read_csv_auto(['airlines.csv']); -- ② 목적지를 지정한다 — 데이터베이스 · 스키마 · 표 CREATE OR REPLACE TABLE my_db.main.airlines AS SELECT * FROM read_csv_auto(['airlines.csv']);
이 대비가 이 절의 요점이다. 자동으로 만들어진 문장에는 표 이름만 있고, 어느 데이터베이스의 어느 스키마에 넣을지가 비어 있다. 구름 위에는 집이 여러 채이므로 번지를 적어야 한다. 이름 세 토막, 곧 데이터베이스.스키마.표가 그 번지다.
Run 버튼을 눌러 질의를 실행하면 main 스키마 아래에 airlines 표가 나열된다. 필요 없어지면 DROP TABLE로 지운다.
새로 만든 airlines 표가 보이지 않으면 페이지를 새로 고친다.
-- 표를 지운다 DROP TABLE my_db.main.airlines; -- 새 스키마를 만든다. 데이터베이스 이름 뒤에 스키마 이름을 적는다 CREATE SCHEMA my_db.Titanic -- 그 스키마 아래로 새 데이터셋을 올린다 CREATE OR REPLACE TABLE my_db.Titanic.Titanic_train AS SELECT * FROM read_csv_auto(['Titanic_train.csv']); -- 스키마를 지운다 DROP SCHEMA Titanic; -- 안에 든 것까지 함께 지운다 DROP SCHEMA Titanic CASCADE
스키마 아래에 데이터베이스가 남아 있으면 그 스키마를 지울 수 없다. 표를 먼저 지운 다음 스키마를 지우거나, DROP … CASCADE 문을 쓴다.
덧붙여 두면, 원서 본문은 “스키마 아래에 데이터베이스가 있으면 지울 수 없다”고 적는다. 계층으로 보면 스키마 아래에 있는 것은 표이므로, 이는 “표”의 오기로 읽힌다. 바로 다음 문장이 “표를 먼저 지우라”고 적고 있어 뜻은 분명하다.
복사하지 않고 나눈다
SHARING DATABASES
MotherDuck의 두드러진 기능 하나는 조직 안에서든 공개적으로든 데이터베이스를 사용자와 공유하는 능력이다. 예컨대 수백만 행의 큰 데이터셋이 있다면, 팀원마다 그 데이터셋을 자기 계정에 적재하는 대신 한 번 올려 두고 접근만 나누면 된다.
데이터베이스를 공유하면 사용자는 그것으로 가는 링크를 만든다. 사본이 만들어지지 않으므로 효율적인 데이터 관리와 협업이 보장된다. 여기가 이 절의 핵심이다. 나눈다는 것이 곧 복제한다는 뜻이 아니라는 점, 제8장에서 원격 파일을 내려받지 않고 그 자리에서 묻던 그 원리가 사람과 사람 사이로 확장된 셈이다.
- 데이터베이스 이름 옆의 세로 점 셋을 누르고 Share를 고른다.
- 공유 이름을 지정하고, 조직 구성원과 나눌지 아니면 아무 MotherDuck 사용자와 나눌지 고른다. 원서의 예에서는 공유 이름을 “my_db_WeiMengLee”로 하고 “Anyone with the share link”를 골랐다.
- Create share 버튼을 누른다. 공유가 만들어지고 SQL 문이 표시된다.
- “Shares I've created” 절에 새로 만든 공유가 나타난다.
첫째 접근 선택지인 “Anyone in my organization”은 조직의 모든 구성원과 데이터를 나누게 한다. 작고 협업이 활발한 데이터 팀에서 흔한 사용 사례다.
공유된 데이터베이스는 읽기 전용이다. 사용자는 그것을 고칠 수 없다.
-- Run this snippet to attach database ATTACH 'md:_share/my_db_WeiMengLee/79ec5e33-a605… -- 받는 쪽에 이미 my_db가 있을 수 있으므로 이름을 바꾸는 편이 낫다 ATTACH 'md:_share/my_db_WML/79ec5e33-a605-4ee2-a3…
이름을 바꾸는 이 배려가 실무의 흔적이다. 받는 쪽에도 my_db가 있을 것이므로, 남의 집을 내 마당에 붙일 때는 문패를 갈아 달아야 한다. 제2장의 ATTACH … as가 여기서 같은 구실을 한다.
-- 원본이 바뀌었으면 변경을 전파한다 UPDATE SHARE my_db_WeiMengLee; -- 지금 나누고 있는 데이터베이스를 본다 LIST SHARES
UPDATE SHARE가 따로 있다는 사실이 공유의 성격을 알려 준다. 링크는 자동으로 최신을 가리키지 않는다. 원본을 고쳤으면 전파를 명시적으로 지시해야 한다. 나눈 것과 나눌 것을 나누어 두는 방식이며, 남이 보고 있는 판본을 내가 임의로 바꾸지 않는다는 예의이기도 하다.
특정 사용자에게서 접근을 회수하려면 세로 점 셋을 누르고 Alter를 골라 접근 권한을 바꾼다. 공유를 아예 그만두려면 공유 옆의 세로 점 셋을 누르고 Drop을 고른다. 공유를 내리면 다른 사용자는 더 이상 그것에 접근하거나 그것을 나눌 수 없다.
짓고 떼어 낸다
CREATING AND DETACHING DATABASES
계정에 기본으로 딸려 오는 my_db 외에 자기 데이터베이스를 만들 수 있다.
-- 새 데이터베이스를 만든다. 만들면 main 스키마가 자동으로 딸려 온다 CREATE DATABASE flights_db; -- 현재 활성 데이터베이스로 삼는다 USE flights_db; -- 쓰지 않는 데이터베이스를 떼어 낸다 DETACH flights_db;
현재로서는 SQL 문으로만 새 데이터베이스를 만들 수 있다. 웹 UI를 통한 데이터베이스 생성은 지금 시점에 지원되지 않는다.
여러 데이터베이스를 다룰 때는 지금 쓰지 않는 것을 떼어 두는 편이 유익하다. 활성 데이터베이스에 집중하게 되고, 붙어 있는 데이터베이스에 배정된 메모리와 그 밖의 자원이 풀린다. 지역이든 공유된 원격이든 마찬가지다. 다시 붙이려면 떼어 낸 데이터베이스 옆의 세로 점 셋을 누르고 Attach를 고른다.
떼어 낸다는 개념이 파일을 지우는 것과 다르다는 점을 짚어 둘 만하다. 별채는 그대로 있고 회랑만 걷어 내는 일이다. 제3장의 ATTACH와 USE가 이 장에 와서 DETACH라는 짝을 얻는다.
질의하고, 오른쪽을 본다
QUERYING YOUR DATABASE
처음 MotherDuck에 로그인했을 때 셀에 들어 있던 기본 질의를 실행하면, sample_data 데이터베이스의 nyc 스키마 안 service_requests 표에서 URL과 각각의 개수를 뽑아낸다.
창의 오른쪽에는 결과의 네 필드에 대한 통계가 보인다. agency, complaint_type, landmark, resolution_description이다. 필드를 누르면 상세 통계가 표시된다. 예컨대 agency는 문자열 필드이므로 고유한 기관 이름별 개수의 분포를 보이고, 숫자 필드라면 최대 개수와 최소 개수 같은 요약 통계를 표시한다.
이 오른쪽 패널이 이 절의 실질이다. 질의를 실행하면 결과만 오는 것이 아니라 결과에 대한 요약이 함께 온다. 제4장의 describe()와 제7장의 %sqlcmd profile이 하던 일을 여기서는 부르지 않아도 해 준다.
앞서 올린 Titanic_train 표로 질의를 시도한다. 나이와 승선지 필드가 널이 아닌 모든 승객을 가져온다.
SELECT Survived, PClass, Sex, Age FROM my_db.Titanic.titanic_train WHERE age NOT NULL AND embarked NOT NULL
| 필드 | 오른쪽에 나타나는 것 |
|---|---|
| Survived | 생존자와 비생존자의 수를 보이는 히스토그램 |
| Pclass | 등급별 승객 수를 보이는 히스토그램 |
| Sex | 남성과 여성 승객 수를 보이는 막대 차트 |
| Age | 나이의 분포와 그 필드의 요약 통계를 함께 표시 |
제7장에서 %sqlplot histogram --column age --bins 10 --table titanic이라 적어야 했던 그림이, 여기서는 필드 이름을 누르는 것으로 나온다. 편의가 한 걸음 더 나아간 자리다. 다만 제7장의 절충이 여기서도 그대로 남는다. 손이 닿는 만큼만 정할 수 있다.
물으면 문장을 지어 준다
WRITING SQL USING AI
MotherDuck의 흥미로운 기능 하나는 AI 능력이다. 이 AI 함수와 프라그마는 자동화된 과제 수행과 분석 작업 흐름 최적화를 위한 진전된 도구를 제공한다. 질의 생성을 간결하게 하고 데이터 분석 과제를 자동화하며, 데이터베이스 환경 안에서 곧바로 기계학습 알고리듬을 활용하게 한다.
둘의 차이를 밝혀 둔다. MotherDuck의 함수는 SQL 질의 안에서 특정 과제나 계산을 수행하고, 프라그마는 과제를 직접 실행하지 않으면서 데이터베이스의 동작과 최적화를 통제하는 지시를 제공한다.
| 함수 · 프라그마 | 하는 일 |
|---|---|
| prompt_sql() | 물음에 대한 SQL 문을 생성하는 함수 |
| prompt_query | 데이터에 관한 물음에 답하는 프라그마 |
| prompt_fixup() | SQL 질의를 교정하고 고치는 함수 |
| prompt_fix_line() | SQL 질의를 한 줄씩 교정하고 고치는 함수 |
| prompt_schema() | 데이터베이스의 내용을 이해하도록 돕는 함수 |
| prompt_explain() | SQL 질의를 이해하도록 돕는 함수 |
목록을 훑어보면 여섯 항목이 두 갈래로 갈린다. 짓는 것(prompt_sql, prompt_query)과 읽고 고치는 것(prompt_fixup, prompt_fix_line, prompt_schema, prompt_explain)이다. 넷이 후자라는 사실이 뜻깊다. 문장을 지어 주는 일보다 이미 있는 문장을 설명하고 고치는 일에 무게가 실려 있다.
첫째 예 — 홀로 탄 승객 중 몇이 살았는가
타이타닉에서 혼자 탄 승객 가운데 몇이 생존했는지 알고 싶다고 하자. CALL 문으로 prompt_sql() 함수를 부른다. 묻기 전에 USE 문으로 활성 데이터베이스를 정해 두어야 한다.
USE my_db; CALL prompt_sql("How many people who were alone s… -- 생성된 문장을 실행한다 SELECT COUNT(*) FROM Titanic.Titanic_train WHERE… -- 결과는 163이다
둘째 예 — 연령대별 생존 확률
연령대마다 생존 확률을 계산해 어느 연령대의 생존율이 가장 높았는지 알아본다. 이 예에서는 생성된 SQL 문이 지면에 온전히 인쇄되어 있다.
CALL prompt_sql("Calculate the survival chance fo… -- 함수가 돌려준 SQL 문 SELECT CASE WHEN Age < 10 THEN '0-9' WHEN Age BETWEEN 10 AND 19 THEN '10-19' WHEN Age BETWEEN 20 AND 29 THEN '20-29' WHEN Age BETWEEN 30 AND 39 THEN '30-39' WHEN Age BETWEEN 40 AND 49 THEN '40-49' WHEN Age BETWEEN 50 AND 59 THEN '50-59' WHEN Age BETWEEN 60 AND 69 THEN '60-69' WHEN Age >= 70 THEN '70+' ELSE 'Unknown' END AS age_group, AVG(Survived) * 100 AS survival_chance FROM Titanic.Titanic_train GROUP BY age_group ORDER BY age_group;
제7장에서 사람이 손으로 쓴 연령대 질의는 0-9부터 50-59까지 여섯 구간에 '60+'를 두었고, 부질의로 AgeGroup을 만든 뒤 겉에서 집계했다. 여기서 AI가 지은 문장은 구간을 60-69와 '70+'로 더 나누고, ELSE 'Unknown'을 두어 나이가 없는 행을 따로 담았으며, 부질의 없이 GROUP BY age_group으로 별칭을 곧바로 묶는다.
눈여겨볼 것은 AVG(Survived) * 100이다. 생존 여부가 0과 1이므로 평균이 곧 비율이 된다. 제5장이 세 번 되풀어 가르친 그 교훈, 세는 것보다 나누는 것이 결론을 정한다는 말을 이 한 줄이 문법으로 구현한다.
셋째와 넷째 예 — 값만 받기, 설명 받기
SQL 문이 아니라 결과만 원한다면 prompt_query 프라그마를 쓴다. airlines 표에 항공사가 몇 개인지 물으면 14를 돌려준다. 제2장에서부터 여덟 장에 걸쳐 함께 온 그 열네 항공사다.
-- 문장 없이 값만 받는다 PRAGMA prompt_query('How many airlines are there… -- 결과는 14다
마지막으로, 앞의 나이와 생존에 관한 SQL 질의가 무엇을 하는지 알고 싶다면 prompt_explain() 함수를 쓴다. 원서에 인쇄된 설명은 다음과 같다.
이 SQL 질의는 타이타닉의 승객을 나이에 따라 집단(‘0-9’, ‘10-19’, ‘20-29’ 등)으로 분류하고, 각 연령대의 평균 생존율을 계산해 백분율로 제시한다. 승객의 나이 범위를 나타내는 age_group을 만들고, 각 연령대에 대해 Survived 열을 평균해 survival_chance를 계산한다. 그 결과를 age_group으로 묶고 age_group으로 정렬한다.
이 설명이 정확하다는 점은 앞의 SQL 문과 대조해 확인된다. 다만 눈금을 하나 달아 두자면, 설명은 질의가 무엇을 하는지는 옳게 말하지만 그 결과를 어떻게 읽어야 하는지는 말하지 않는다. ELSE 'Unknown' 집단이 나이가 없는 행을 담고 있어 그 집단의 survival_chance는 다른 집단과 같은 뜻으로 읽을 수 없다는 것, 그런 판단은 여전히 사람의 몫이다.
명령줄로 구름에 붙는다
USING MOTHERDUCK THROUGH THE DUCKDB CLI
웹 UI 대신 명령줄에서 곧바로 질의를 실행하고 싶을 때가 있다. 계속하기 전에 DuckDB를 최신 판본으로 갱신한다. DuckDB 1.0.0 이상의 안정 릴리스를 써야 한다.
제3장에서 다룬 그 프롬프트가 여기서 돌아온다. 다만 이번에는 ATTACH가 파일이 아니라 'md:'를 받는다.
$ duckdb v1.0.0 1f98600c2c Enter ".help" for usage hints. Connected to a transient in-memory database. Use ".open FILENAME" to reopen on a persistent da… D ATTACH 'md:'; Attempting to automatically open the SSO authoriz… 1. Please open this link to login into your accou… 2. Enter the following code: CQMP-DQHV
Token successfully retrieved ✅ You can display the token and store it as an environment variable to avoid having to log in again: PRAGMA PRINT_MD_TOKEN; 100% ▕███████████████████████████████████████████… D
제7장과 제8장에서 되풀어 나온 그 물음이 여기서 세 번째로 등장한다. 열쇠를 어디에 두는가. 제7장은 환경 변수·.ini·keyring이었고, 제8장은 CONFIG·CREDENTIAL_CHAIN이었고, 여기서는 SSO로 받은 토큰을 환경 변수에 둔다. 세 도구가 같은 문제를 각자의 어법으로 푼다.
D SHOW databases; ┌───────────────┐ │ database_name │ │ varchar │ ├───────────────┤ │ memory │ │ my_db │ │ sample_data │ └───────────────┘ D SELECT current_database(); ┌────────────────────┐ │ current_database() │ │ varchar │ ├────────────────────┤ │ memory │ └────────────────────┘ D SELECT current_schema(); ┌──────────────────┐ │ current_schema() │ │ varchar │ ├──────────────────┤ │ main │ └──────────────────┘
D SELECT Survived, PClass, Sex, Age FROM my_db.Titanic.titanic_train WHERE age NOT NULL AND embarked NOT NULL; ┌──────────┬────────┬─────────┬────────┐ │ Survived │ Pclass │ Sex │ Age │ │ int64 │ int64 │ varchar │ double │ ├──────────┼────────┼─────────┼────────┤ │ 0 │ 3 │ male │ 22.0 │ │ 1 │ 1 │ female │ 38.0 │ │ 1 │ 3 │ female │ 26.0 │ │ · │ · │ · │ · │ │ · │ · │ · │ · │ │ · │ · │ · │ · │ │ 1 │ 1 │ female │ 19.0 │ │ 1 │ 1 │ male │ 26.0 │ │ 0 │ 3 │ male │ 32.0 │ ├──────────┴────────┴─────────┴────────┤ │ 712 rows (40 shown) 4 columns │ └──────────────────────────────────────┘ -- 데이터베이스와 스키마를 함께 정해 두면 표 이름만으로 부를 수 있다 D USE my_db.Titanic; D SELECT count(*) FROM titanic_train; ┌──────────────┐ │ count_star() │ │ int64 │ ├──────────────┤ │ 891 │ └──────────────┘
| 물음 | 값 | 읽어 낼 것 |
|---|---|---|
| 표 전체의 행 수 | 891 | 타이타닉 훈련 집합의 전체 승객 수 |
| 나이와 승선지가 널이 아닌 행 | 712 | 두 필드가 모두 채워진 승객 |
| 둘의 차 | 179 | 나이나 승선지 가운데 하나 이상이 비어 있는 행 |
USE my_db.Titanic 한 줄이 데이터베이스와 스키마를 동시에 정한다는 점을 눈여겨본다. 그러면 세 토막이던 이름이 한 토막으로 줄어든다. 번지를 미리 적어 두면 이름만 부를 수 있다는 것, 앞 절에서 목적지를 지정해야 했던 사정의 반대편이다.
명령줄로 짓기
-- 지역 CSV를 구름의 my_db.main에 올린다 D CREATE OR REPLACE TABLE my_db.main.airports AS FROM read_csv_auto(['airports.csv']); -- 새 데이터베이스 · 스키마 · 표를 짓는다 D CREATE DATABASE new_db; D USE new_db; D CREATE SCHEMA new_schema; D USE new_schema; D CREATE TABLE new_schema.example_table ( id INTEGER, name TEXT ); D INSERT INTO new_schema.example_table (id, name) VALUES (1, 'Sample Data'); D SELECT * FROM new_schema.example_table; ┌───────┬─────────────┐ │ id │ name │ │ int32 │ varchar │ ├───────┼─────────────┤ │ 1 │ Sample Data │ └───────┴─────────────┘
airports.csv 파일이 DuckDB CLI를 띄운 그 디렉터리에 있어야 한다. 질의문은 구름을 가리키지만 파일 이름은 여전히 내 디스크를 가리킨다. 이 절이 하이브리드 질의의 예고인 셈이다.
합수 · 지역과 구름이 한 문장에서
PERFORMING HYBRID QUERIES
MotherDuck의 아주 멋진 기능 하나는 하이브리드 질의를 수행하는 능력이다. 지역 DuckDB 데이터베이스를 MotherDuck의 데이터베이스와 나란히 질의하게 한다.
앞서 MotherDuck에 올려 둔 airlines 표의 내용을 본다. 열네 행 두 열이다. 제3장에서 CLI로 처음 만났고 제5장에서 조인의 재료가 되었던 그 목록이, 이번에는 구름 위에서 돌아온다.
| IATA_CODE | AIRLINE |
|---|---|
| varchar | varchar |
| UA | United Air Lines Inc. |
| AA | American Airlines Inc. |
| US | US Airways Inc. |
| F9 | Frontier Airlines Inc. |
| B6 | JetBlue Airways |
| OO | Skywest Airlines Inc. |
| AS | Alaska Airlines Inc. |
| NK | Spirit Air Lines |
| WN | Southwest Airlines Co. |
| DL | Delta Air Lines Inc. |
| EV | Atlantic Southeast Airlines |
| HA | Hawaiian Airlines Inc. |
| MQ | American Eagle Airlines Inc. |
| VX | Virgin America |
| 14 rows 2 columns · SELECT * from my_db.main.airlines; | |
다른 한편에는 제2장에서 처음 쓴 flights.csv가 있다. 이를 지역 DuckDB 데이터베이스로 적재하되, 처음 열 행과 두 열만 가져온다.
D SELECT AIRLINE, FLIGHT_NUMBER FROM './flights.csv' LIMIT 10; ┌─────────┬───────────────┐ │ AIRLINE │ FLIGHT_NUMBER │ │ varchar │ int64 │ ├─────────┼───────────────┤ │ AS │ 98 │ │ AA │ 2336 │ │ US │ 840 │ │ AA │ 258 │ │ AS │ 135 │ │ DL │ 806 │ │ NK │ 612 │ │ US │ 2013 │ │ AA │ 1112 │ │ DL │ 1173 │ ├─────────┴───────────────┤ │ 10 rows 2 columns │ └─────────────────────────┘
이 표에는 항공사 코드만 있어 이름이 없다. 각 행에 항공사 이름을 표시하려면, 이름을 가진 airlines 표와 조인해야 한다. 그런데 그 표는 구름 위에 있다. 하이브리드 질의가 그 일을 한다.
D SELECT f.AIRLINE, f.FLIGHT_NUMBER, a.AIRLINE FROM (SELECT AIRLINE, FLIGHT_NUMBER FROM './flights.csv' LIMIT 10) AS f JOIN my_db.main.airlines AS a ON f.AIRLINE = a.IATA_CODE;
| AIRLINE | FLIGHT_NUMBER | AIRLINE |
|---|---|---|
| varchar | int64 | varchar |
| AS | 98 | Alaska Airlines Inc. |
| AA | 2336 | American Airlines Inc. |
| US | 840 | US Airways Inc. |
| AA | 258 | American Airlines Inc. |
| AS | 135 | Alaska Airlines Inc. |
| DL | 806 | Delta Air Lines Inc. |
| NK | 612 | Spirit Air Lines |
| US | 2013 | US Airways Inc. |
| AA | 1112 | American Airlines Inc. |
| DL | 1173 | Delta Air Lines Inc. |
| 10 rows 3 columns · 짝을 찾지 못한 코드는 없다 | ||
하이브리드 질의의 결과는 지역 DuckDB 데이터베이스와 MotherDuck에 호스팅된 표의 데이터를 결합해, 항공사 코드와 그에 대응하는 온전한 항공사 이름을 함께 담은 종합적인 시야를 제공한다. 이 접근은 MotherDuck의 하이브리드 질의 능력을 활용해, 서로 다른 원천의 데이터를 한 번의 질의 실행으로 매끄럽게 통합하고 분석한다.
여기서 짚어 둘 것은 질의문에 어느 쪽인지가 드러나지 않는다는 점이다. './flights.csv'는 내 디스크이고 my_db.main.airlines는 구름인데, 문법상 둘은 그냥 조인의 양쪽 항이다. 서론에서 말한 이중 실행(Dual Execution)이 그것을 감당한다. 질의의 어느 부분을 어디서 실행할지는 도구가 정한다.
두 물이 만나는 자리에서 물빛이 한동안 갈려 보이다 이내 한 물이 되는 것처럼, 두 원천의 경계는 결과표에 남지 않는다. 답사기의 마지막 장면으로 이만한 것이 없다.