SQL 소개

SQL 소개

Polars는 SQL과의 상호작용을 지원하지만, 더 읽기 쉽고 표현력 있는 코드를 만들려면 표현식 문법에 익숙해지는 걸 권장해요. DataFrame 인터페이스가 주력이기 때문에 신규 기능도 보통 표현식 API에 먼저 추가됩니다. 다만 이미 기존 SQL 코드베이스가 있거나 SQL 사용이 더 편하다면, Polars도 이를 지원해 줍니다.

출처: 공식문서

실행 방식

별도의 SQL 엔진은 존재하지 않아요. Polars는 SQL 쿼리를 표현식으로 번역한 뒤 자체 엔진으로 실행합니다. 이 방식 덕분에 네이티브 DataFrame 라이브러리로서의 성능과 확장성 장점을 유지하면서도, 사용자에게는 SQL로 작업할 수 있는 능력을 제공할 수 있어요.

컨텍스트

Polars는 SQLContext 객체로 SQL 쿼리를 관리합니다. 컨텍스트에는 DataFrameLazyFrame의 식별자 이름과 그에 대응하는 데이터셋의 매핑이 들어 있어요. 아래 예제는 SQLContext를 시작하는 모습입니다:

ctx = pl.SQLContext()

DataFrame 등록하기

SQLContext 초기화 시 DataFrame을 등록하는 방법은 여러 가지가 있습니다.

  • 전역 네임스페이스의 모든 LazyFrameDataFrame 객체를 등록한다.
  • 딕셔너리 매핑 또는 kwargs로 명시적으로 등록한다.
df = pl.DataFrame({"a": [1, 2, 3]})
lf = pl.LazyFrame({"b": [4, 5, 6]})

# Register all dataframes in the global namespace: registers both "df" and "lf"
ctx = pl.SQLContext(register_globals=True)

# Register an explicit mapping of identifier name to frame
ctx = pl.SQLContext(frames={"table_one": df, "table_two": lf})

# Register frames using kwargs; dataframe df as "df" and lazyframe lf as "lf"
ctx = pl.SQLContext(df=df, lf=lf)

Pandas DataFrame은 먼저 Polars로 변환한 뒤 등록할 수도 있습니다.

import pandas as pd

df_pandas = pd.DataFrame({"c": [7, 8, 9]})
ctx = pl.SQLContext(df_pandas=pl.from_pandas(df_pandas))

Pandas

Numpy로 백업된 Pandas DataFrame을 변환하면 비용이 꽤 들 수 있어요. 하지만 Pandas DataFrame이 이미 Arrow로 백업돼 있다면 변환 비용이 훨씬 저렴하고(어떤 경우엔 거의 공짜에 가깝습니다) 거의 없을 수 있어요.

SQLContext가 초기화된 뒤에는 추가 DataFrame을 등록하거나 기존 DataFrame을 해제할 수 있습니다.

  • register
  • register_globals
  • register_many
  • unregister

쿼리 실행과 결과 수집

SQL 쿼리는 항상 lazy 모드로 실행되어 쿼리 플래닝 최적화의 전체 세트를 활용합니다. 그래서 결과를 수집하는 방법은 두 가지입니다.

  • SQLContext에서 eager 파라미터를 True로 설정한다. 그러면 execute 호출 결과인 LazyFrame을 Polars가 자동으로 collect합니다.
  • execute로 쿼리를 실행할 때 eager를 True로 설정하거나, collect로 결과를 명시적으로 수집한다.

SQL 쿼리는 SQLContextexecute를 호출해 실행합니다.

pokemon = pl.scan_csv("docs/assets/data/pokemon.csv")
with pl.SQLContext(register_globals=True, eager=True) as ctx:
    df_small = ctx.execute("SELECT * from pokemon LIMIT 5")
    print(df_small)
shape: (5, 13)
┌─────┬───────────────────────┬────────┬────────┬───┬─────────┬───────┬────────────┬───────────┐
│ #   ┆ Name                  ┆ Type 1 ┆ Type 2 ┆ … ┆ Sp. Def ┆ Speed ┆ Generation ┆ Legendary │
│ --- ┆ ---                   ┆ ---    ┆ ---    ┆   ┆ ---     ┆ ---   ┆ ---        ┆ ---       │
│ i64 ┆ str                   ┆ str    ┆ str    ┆   ┆ i64     ┆ i64   ┆ i64        ┆ bool      │
╞═════╪═══════════════════════╪════════╪════════╪═══╪═════════╪═══════╪════════════╪═══════════╡
│ 1   ┆ Bulbasaur             ┆ Grass  ┆ Poison ┆ … ┆ 65      ┆ 45    ┆ 1          ┆ false     │
│ 2   ┆ Ivysaur               ┆ Grass  ┆ Poison ┆ … ┆ 80      ┆ 60    ┆ 1          ┆ false     │
│ 3   ┆ Venusaur              ┆ Grass  ┆ Poison ┆ … ┆ 100     ┆ 80    ┆ 1          ┆ false     │
│ 3   ┆ VenusaurMega Venusaur ┆ Grass  ┆ Poison ┆ … ┆ 120     ┆ 80    ┆ 1          ┆ false     │
│ 4   ┆ Charmander            ┆ Fire   ┆ null   ┆ … ┆ 50      ┆ 65    ┆ 1          ┆ false     │
└─────┴───────────────────────┴────────┴────────┴───┴─────────┴───────┴────────────┴───────────┘

여러 소스에서 쿼리 실행

여러 소스에서도 SQL 쿼리를 쉽게 실행할 수 있어요. 아래 예제에서는 다음을 등록합니다.

  • CSV 파일 (lazy로 로드)
  • NDJSON 파일 (lazy로 로드)
  • Pandas DataFrame

그리고 SQL로 이들을 조인합니다. Lazy 읽기를 사용하면 파일에서 필요한 행과 컬럼만 불러올 수 있어요.

같은 방식으로 클라우드 데이터레이크(S3, Azure Data Lake)도 등록할 수 있습니다. PyArrow 데이터셋이 데이터레이크를 가리키면 Polars가 scan_pyarrow_dataset으로 읽어낼 수 있어요.

# Input data:
# products_masterdata.csv with schema {'product_id': Int64, 'product_name': String}
# products_categories.json with schema {'product_id': Int64, 'category': String}
# sales_data is a Pandas DataFrame with schema {'product_id': Int64, 'sales': Int64}

with pl.SQLContext(
    products_masterdata=pl.scan_csv("docs/assets/data/products_masterdata.csv"),
    products_categories=pl.scan_ndjson("docs/assets/data/products_categories.json"),
    sales_data=pl.from_pandas(sales_data),
    eager=True,
) as ctx:
    query = """
    SELECT
        product_id,
        product_name,
        category,
        sales
    FROM
        products_masterdata
    LEFT JOIN products_categories USING (product_id)
    LEFT JOIN sales_data USING (product_id)
    """
    print(ctx.execute(query))
shape: (5, 4)
┌────────────┬──────────────┬────────────┬───────┐
│ product_id ┆ product_name ┆ category   ┆ sales │
│ ---        ┆ ---          ┆ ---        ┆ ---   │
│ i64        ┆ str          ┆ str        ┆ i64   │
╞════════════╪══════════════╪════════════╪═══════╡
│ 4          ┆ Product D    ┆ Category 2 ┆ 250   │
│ 5          ┆ Product E    ┆ Category 3 ┆ 300   │
│ 1          ┆ Product A    ┆ Category 1 ┆ 100   │
│ 2          ┆ Product B    ┆ Category 1 ┆ 200   │
│ 3          ┆ Product C    ┆ Category 2 ┆ 150   │
└────────────┴──────────────┴────────────┴───────┘

호환성

Polars는 완전한 SQL 스펙을 지원하지는 않지만, 가장 흔한 문장 유형의 부분 집합을 지원합니다.

방언

가능한 한 Polars는 PostgreSQL 구문 정의와 함수 동작을 따르는 것을 목표로 합니다.

예를 들어 지원되는 기능의 일부 목록은 이렇습니다.

  • CREATE 문 작성: CREATE TABLE xxx AS ...
  • WHERE, ORDER, LIMIT, GROUP BY, UNION, JOIN 절을 포함한 SELECT 문 작성
  • WITH tablename AS 같은 공통 테이블 표현식(CTE) 작성
  • 쿼리 설명: EXPLAIN SELECT ...
  • 등록된 테이블 나열: SHOW TABLES
  • 테이블 삭제: DROP TABLE [IF EXISTS] tablename
  • 테이블 비우기: TRUNCATE TABLE [IF EXISTS] tablename

아직 지원되지 않는 기능은 다음과 같습니다.

  • INSERT, UPDATE, DELETE
  • ANALYZE 같은 메타 쿼리

다음 섹션들에서는 각 문장에 대해 더 자세히 다룰 예정입니다.

더 알아보기 (Learn more)