iBetter Books
수정

DuckDB SQL 셀 기초

PART 03까지는 데이터를 Python에서만 다뤘습니다. 변수에 리스트나 딕셔너리를 담고, 필요하면 Pandas를 가져와 필터링했습니다. 이 챕터에서는 SQL을 꺼냅니다.

marimo는 SQL 셀을 내장하고 있습니다. 에디터에서 SQL 셀을 추가하면 내부적으로 mo.sql() 함수 호출로 변환되고, 쿼리는 DuckDB 위에서 실행됩니다. 별도로 DuckDB를 설치하지 않아도 됩니다. marimo에 이미 포함되어 있습니다.

mo.sql() 기본 사용

mo.sql()은 SQL 문자열을 받아 실행하고, 결과를 데이터프레임으로 반환합니다.

import marimo as moresult = mo.sql(f"SELECT 1 + 1 AS answer")result

위 코드를 셀에 두고 실행하면 answer 컬럼에 2가 담긴 데이터프레임이 표시됩니다. 반환값은 환경에 따라 Polars DataFrame 또는 Pandas DataFrame입니다. 정확한 타입은 SQL 출력 타입 설정으로 결정하며, 이 챕터 후반부에서 설명합니다.

CSV와 Parquet 파일 쿼리

DuckDB는 파일을 테이블처럼 직접 쿼리할 수 있습니다. Python 코드 한 줄 없이 SQL만으로 파일을 읽습니다.

import marimo as mo# CSV 파일을 그대로 쿼리 (DuckDB가 자동으로 읽음)sales = mo.sql(f"""    SELECT        region,        SUM(amount) AS total_sales,        COUNT(*) AS order_count    FROM 'data/sales.csv'    GROUP BY region    ORDER BY total_sales DESC""")sales
import marimo as mo# Parquet 파일도 동일하게 쿼리 가능orders = mo.sql(f"""    SELECT *    FROM 'data/orders.parquet'    WHERE status = 'completed'    LIMIT 100""")orders

DuckDB는 Parquet 파일을 컬럼나(columnar) 방식으로 읽습니다. 전체 파일을 메모리에 올리지 않고 필요한 컬럼과 행만 읽기 때문에, 수백 MB 파일도 빠르게 쿼리할 수 있습니다. 이 특성은 Ch 03에서 더 자세히 다룹니다.

컬럼 별칭(AS total_sales)에는 영문을 쓰는 것을 권장합니다. AS 전체_매출처럼 한글 등 비ASCII 별칭을 쓰면 marimo 0.23의 SQL 정적 분석기가 marimo check에서 warning[sql-parse-error] 경고를 낼 수 있습니다. 실행에는 영향이 없는 경고이며 쿼리 결과도 정상이지만, 경고를 피하려면 별칭을 영문으로 두면 됩니다.

Python 변수를 SQL에 삽입하기

mo.sql()은 f-string으로 감싸여 있습니다. Python 변수를 중괄호 {} 안에 넣으면 SQL 문자열 안으로 삽입됩니다.

import marimo as mo# Python 변수min_sales = 10000# f-string으로 변수 삽입result = mo.sql(f"""    SELECT region, total_sales    FROM 'data/sales.csv'    WHERE total_sales >= {min_sales}""")result

이 패턴은 강력하지만 주의가 필요합니다. 사용자 입력을 받아 SQL에 직접 삽입하면 SQL 인젝션 위험이 있습니다. 노트북이 개인 환경에서만 동작한다면 큰 문제가 없지만, marimo run으로 외부에 공개하는 경우에는 신뢰할 수 없는 값을 SQL에 직접 넣지 않도록 주의합니다.

위젯 값으로 반응형 SQL 만들기

Python 변수를 삽입할 수 있다면, 위젯의 .value도 삽입할 수 있습니다. 이것이 반응형 SQL의 핵심입니다.

import marimo as mo# 셀 1 — 지역 선택 드롭다운region = mo.ui.dropdown(    ["전체", "서울", "부산", "대구", "인천"],    value="전체",    label="지역 선택",)region
import marimo as mo# 셀 2 — 드롭다운 값에 따라 쿼리가 달라짐if region.value == "전체":    where_clause = "1=1"else:    where_clause = f"region = '{region.value}'"filtered = mo.sql(f"""    SELECT region, product, amount    FROM 'data/sales.csv'    WHERE {where_clause}    ORDER BY amount DESC    LIMIT 20""")filtered

region 위젯을 바꾸는 순간 region.value가 변하고, 셀 2가 DAG를 따라 자동으로 재실행됩니다. SQL이 새 조건으로 다시 실행되고, 결과 데이터프레임이 갱신됩니다. 새로고침 버튼은 없습니다.

SQL 출력 타입

mo.sql()이 반환하는 타입은 설정에 따라 달라집니다. marimo는 다섯 가지 출력 타입을 지원합니다.

타입 설명
auto 기본값. Polars가 설치되어 있으면 Polars DataFrame, 아니면 Pandas DataFrame
polars Polars DataFrame
pandas Pandas DataFrame
lazy-polars Polars LazyFrame (지연 평가)
native DuckDB lazy relation (지연 평가, 대용량 데이터에 권장)

기본값은 auto입니다. Polars를 설치했다면 Polars DataFrame을 받고, 그렇지 않으면 Pandas DataFrame을 받습니다. 교재 실습 코드는 가급적 타입을 명시해 환경에 따른 차이를 줄입니다.

출력 타입을 프로젝트 전체에 고정하려면 pyproject.toml에 설정합니다.

[tool.marimo.runtime]
default_sql_output = "polars"   # "native", "polars", "pandas", "lazy-polars", "auto"

이 설정이 있으면 모든 mo.sql() 호출이 해당 타입을 반환합니다.

UI에 표시될 때는 기본적으로 첫 10행만 렌더링됩니다. 전체 데이터를 Python에서 처리하려면 polarspandas 타입으로 명시적으로 받아야 합니다. native 타입은 DuckDB 내부의 지연 평가 객체로 남아 있어 추가 집계나 조인을 붙이기 유리합니다. 이 타입들의 실제 사용 방법은 Ch 02와 Ch 03에서 구체적으로 다룹니다.

SQL 셀과 Python 셀의 차이

marimo 에디터에서는 셀을 SQL 타입으로 직접 만들 수 있습니다. SQL 셀을 추가하면 에디터가 SQL 구문 강조와 자동 완성을 제공합니다. 내부적으로는 mo.sql(f"...") 호출로 변환되므로, 동작 방식은 Python 셀에서 mo.sql()을 직접 쓰는 것과 동일합니다.

이 교재에서는 두 방식을 혼용합니다. Python 코드와 SQL을 함께 보여줄 때는 Python 셀에서 mo.sql()을 직접 씁니다.

정리

  • mo.sql(f"...") 로 DuckDB 쿼리를 실행하고 결과를 데이터프레임으로 받습니다.
  • CSV, Parquet 파일을 SQL에서 테이블 이름처럼 직접 쿼리할 수 있습니다.
  • f-string 삽입으로 Python 변수와 위젯 값을 SQL에 넣어 반응형 쿼리를 만들 수 있습니다.
  • 사용자 입력을 SQL에 직접 삽입할 때는 SQL 인젝션을 주의합니다.
  • SQL 출력 타입은 auto(기본), polars, pandas, lazy-polars, native 중 선택하고, pyproject.toml에서 프로젝트 기본값을 설정할 수 있습니다.