데이터 가져오기

데이터 가져오기 (Import Data)

Go 클라이언트에는 데이터를 DuckDB에 벌크로 넣는 두 가지 방식이 있어요. Go에서 생성한 행을 삽입하는 고성능 Appender와, Parquet·CSV·JSON 같은 데이터 파일에서 직접 읽는 방식이에요. 둘 다 아래에서 살펴볼게요. 몇 개 안 되는 행을 삽입할 때는 일반 [INSERT 문]({% link docs/current/clients/go/querying.md %}#sending-statements)으로 충분해요. 여기의 옵션들은 대량 데이터를 위한 거예요.

출처: 문서

본문

Appender

[Appender]({% link docs/current/data/appender.md %})는 Go에서 생성한 행을 삽입하는 가장 빠른 방법이에요. INSERT 문을 내는 것보다 훨씬 빨라요. Appender는 raw DuckDB 커넥션에서 동작하므로 *sql.DB가 아니라 Connector에서 만들어요. 커넥터에서 커넥션을 얻은 뒤 대상 테이블에 대한 appender를 duckdb.NewAppenderFromConn()으로 만들고 스키마(기본이면 빈 문자열)와 테이블 이름을 넘겨요.

connector, err := duckdb.NewConnector("test.db", nil)
if err != nil {
    log.Fatal(err)
}
defer connector.Close()

conn, err := connector.Connect(context.Background())
if err != nil {
    log.Fatal(err)
}
defer conn.Close()

// The table must already exist.
appender, err := duckdb.NewAppenderFromConn(conn, "", "users")
if err != nil {
    log.Fatal(err)
}
defer appender.Close()

// Each argument is one column value, in table column order.
if err := appender.AppendRow("Fred", int32(34)); err != nil {
    log.Fatal(err)
}

AppendRow()의 각 인자는 테이블 열 순서대로 된 열 값이고, 그 Go 타입은 열의 DuckDB 타입과 일치해야 해요. 예시는 INTEGER 열에 int32(34)를 넘겨요. 행마다 AppendRow()를 한 번 호출해요.

Warning appender는 행을 버퍼링해 청크 단위로 DuckDB에 플러시하므로, NOT NULL이나 외래 키 위반 같은 제약 조건 위반이 그 행을 추가한 AppendRow() 호출이 아니라 버퍼가 플러시될 때 드러날 수 있어요. 대기 중인 행을 신뢰하기 전에 Flush()를 호출하고 그 오류를 확인하고, 최종 플러시를 수행하는 Close()의 오류도 항상 확인하세요.

다음은 클라이언트의 appender 예시를 따르는 코드로, 같은 커넥터에서 연 *sql.DB로 테이블을 만들고, 행을 추가한 뒤 다시 읽어요.

package main

import (
    "context"
    "database/sql"
    "log"

    "github.com/duckdb/duckdb-go/v2"
)

func main() {
    c, err := duckdb.NewConnector("", nil)
    if err != nil {
        log.Fatal(err)
    }
    defer c.Close()

    con, err := c.Connect(context.Background())
    if err != nil {
        log.Fatal(err)
    }
    defer con.Close()

    db := sql.OpenDB(c)
    defer db.Close()
    if _, err := db.Exec(`CREATE TABLE users (name VARCHAR, age INTEGER)`); err != nil {
        log.Fatal(err)
    }

    a, err := duckdb.NewAppenderFromConn(con, "", "users")
    if err != nil {
        log.Fatal(err)
    }

    if err := a.AppendRow("Fred", int32(34)); err != nil {
        log.Fatal(err)
    }

    // Close flushes any buffered rows before closing the appender.
    if err := a.Close(); err != nil {
        log.Fatal(err)
    }

    var (
        name string
        age  int
    )
    row := db.QueryRowContext(context.Background(), `SELECT name, age FROM users`)
    if err := row.Scan(&name, &age); err != nil {
        log.Fatal(err)
    }
    log.Printf("User: name=%s, age=%d", name, age)
}

다른 스키마와 열 부분집합 대상 지정

패키지는 다른 카탈로그·스키마 대상 지정, 열 부분집합 선택, 또는 배치를 추가하면서 변환하는 더 많은 생성자를 제공해요. 각각은 첫 인자로 driver.Conn을 받아요.

  • duckdb.NewAppender(driverConn, catalog, schema, table) — 지정된 테이블의 모든 열에 추가해요.
  • duckdb.NewTableAppender(driverConn, query, catalog, schema, table, columnNames) — 버퍼링된 배치마다 INSERT, DELETE, UPDATE, MERGE INTO 쿼리를 실행해요. 대상 테이블의 이름 있는 열에서 입력 타입을 추론해요. 빈 열 이름 슬라이스를 넘기면 모든 열을 사용해요.
  • duckdb.NewQueryAppender(driverConn, query, temporaryTable, columnTypes, columnNames) — 배치마다 쿼리도 실행하지만, 호출자가 임시 입력 테이블의 이름, 열 타입, 열 이름을 지정할 수 있어요.

duckdb.NewAppenderWithColumns(driverConn, catalog, schema, table, columnNames)도 열의 부분집합을 추가하면서 다른 열은 기본값이나 NULL로 남길 수 있어요. 주로 하위 호환성 때문에 유지돼요. 쿼리 기반 appender가 더 성능이 좋으므로 이 경우엔 NewTableAppender를 선호하세요.

데이터 파일 읽기

DuckDB는 많은 파일 포맷을 SQL에서 직접 읽을 수 있어서, 외부 데이터를 로드하는 가장 간단한 방법인 경우가 많아요. Go 클라이언트는 그저 쿼리를 보내면 돼요. [Parquet]({% link docs/current/data/parquet/overview.md %}), [CSV]({% link docs/current/data/csv/overview.md %}), [JSON]({% link docs/current/data/json/overview.md %}) 리더가 [bundle 확장]({% link docs/current/clients/go/overview.md %}#bundled-extensions)에 포함돼 있어서 추가 설치가 필요 없어요. 일치하는 테이블 함수로 파일을 그자리에서 쿼리해요.

rows, err := db.QueryContext(ctx, `SELECT * FROM read_parquet('data.parquet')`)

같은 패턴으로 read_csv()는 CSV, read_json()은 JSON을 읽어요. 파일을 그 자리에서 쿼리하는 대신 테이블로 로드하려면 리더를 Exec로 보낸 [CREATE TABLE ... AS SELECT]({% link docs/current/sql/statements/create_table.md %}#create-table--as-select-ctas)나 [COPY]({% link docs/current/sql/statements/copy.md %}) 문으로 감싸면 돼요.

_, err := db.ExecContext(ctx, `CREATE TABLE data AS SELECT * FROM read_parquet('data.parquet')`)

더 알아보기 (Learn more)

  • [Appender]({% link docs/current/data/appender.md %}) — Go Appender가 감싸는 엔진 레벨 Appender 인터페이스.
  • [Data Import]({% link docs/current/data/overview.md %}) — DuckDB의 벌크 로딩 옵션 전체.
  • [Run Queries]({% link docs/current/clients/go/querying.md %}) — 파일 리더가 기반으로 하는 CREATE TABLECOPY 문 보내기.
  • [Connect]({% link docs/current/clients/go/connecting.md %}) — Appender가 동작하는 Connector와 커넥션 만들기.