Zum Inhalt springen

Konvertierung zwischen DuckDB und Python

Diese Seite beschreibt die Regeln für die Konvertierung von Python-Objekten nach DuckDB und von DuckDB-Ergebnissen nach Python.

Objektkonvertierung: Python-Objekt nach DuckDB

Dies ist eine Zuordnung von Python-Objekttypen zu DuckDB-Logical Types:

  • NoneNULL
  • boolBOOLEAN
  • datetime.timedeltaINTERVAL
  • strVARCHAR
  • bytearrayBLOB
  • memoryviewBLOB
  • decimal.DecimalDECIMAL / DOUBLE
  • uuid.UUIDUUID

Die übrigen Konvertierungsregeln sind wie folgt.

int

Da Ganzzahlen in Python beliebig groß sein können, ist für ints keine Eins-zu-eins-Konvertierung möglich. Stattdessen führen wir diese Casts der Reihe nach durch, bis einer gelingt:

  • BIGINT
  • INTEGER
  • UBIGINT
  • UINTEGER
  • DOUBLE

Bei Verwendung der DuckDB-Klasse Value kann ein Zieltyp gesetzt werden, der die Konvertierung beeinflusst.

float

Diese Casts werden der Reihe nach versucht, bis einer gelingt:

  • DOUBLE
  • FLOAT

datetime.datetime

Bei datetime prüfen wir pandas.isnull, falls verfügbar, und geben NULL zurück, wenn es true liefert. Wir prüfen gegen datetime.datetime.min und datetime.datetime.max, um entsprechend nach -inf und +inf zu konvertieren.

Wenn das datetime tzinfo hat, verwenden wir TIMESTAMPTZ, andernfalls wird daraus TIMESTAMP.

datetime.time

Wenn die time tzinfo hat, verwenden wir TIMETZ, andernfalls wird daraus TIME.

datetime.date

date wird in den Typ DATE konvertiert. Wir prüfen gegen datetime.date.min und datetime.date.max, um entsprechend nach -inf und +inf zu konvertieren.

bytes

bytes wird standardmäßig nach BLOB konvertiert; wenn es zum Erzeugen eines Value-Objekts vom Typ BITSTRING verwendet wird, wird es stattdessen auf BITSTRING abgebildet.

list

Aus list wird ein LIST-Typ des „zulässigsten“ Typs seiner Kinder, zum Beispiel:

my_list_value = [
12345,
"test"
]

Wird zu VARCHAR[], weil 12345 nach VARCHAR konvertiert werden kann, test aber nicht nach INTEGER.

[12345, test]

dict

Das Objekt dict kann je nach Struktur entweder nach STRUCT(...) oder nach MAP(..., ...) konvertiert werden. Wenn das dict eine Struktur ähnlich der folgenden hat:

import duckdb
my_map_dict = {
"key": [
1, 2, 3
],
"value": [
"one", "two", "three"
]
}
duckdb.values(my_map_dict)

Dann konvertieren wir es in eine MAP aus Schlüssel-Wert-Paaren der beiden zusammengezippten Listen. Das obige Beispiel wird zu einer MAP(INTEGER, VARCHAR):

┌─────────────────────────┐
│ {1=one, 2=two, 3=three} │
│ map(integer, varchar) │
├─────────────────────────┤
│ {1=one, 2=two, 3=three} │
└─────────────────────────┘

Wenn das dict von einer Funktion zurückgegeben wird, gibt die Funktion eine MAP zurück; daher muss der return_type der Funktion angegeben werden. Ein Rückgabetyp, der nicht nach MAP konvertiert werden kann, löst einen Fehler aus:

import duckdb
duckdb_conn = duckdb.connect()
def get_map() -> dict[str,list[str]|list[int]]:
return {
"key": [
1, 2, 3
],
"value": [
"one", "two", "three"
]
}
duckdb_conn.create_function("get_map", get_map, return_type=dict[int, str])
duckdb_conn.sql("select get_map()").show()
duckdb_conn.create_function("get_map_error", get_map)
duckdb_conn.sql("select get_map_error()").show()
┌─────────────────────────┐
│ get_map() │
│ map(bigint, varchar) │
├─────────────────────────┤
│ {1=one, 2=two, 3=three} │
└─────────────────────────┘
ConversionException: Conversion Error: Type VARCHAR can't be cast as UNION(u1 VARCHAR[], u2 BIGINT[]). VARCHAR can't be implicitly cast to any of the union member types: VARCHAR[], BIGINT[]

Die Namen der Felder sind entscheidend, und die beiden Listen müssen dieselbe Größe haben.

Andernfalls versuchen wir, es in ein STRUCT zu konvertieren.

import duckdb
my_struct_dict = {
1: "one",
"2": 2,
"three": [1, 2, 3],
False: True
}
duckdb.values(my_struct_dict)

Wird zu:

┌────────────────────────────────────────────────────────────────────┐
│ {'1': 'one', '2': 2, 'three': [1, 2, 3], 'False': true} │
│ struct("1" varchar, "2" integer, three integer[], "false" boolean) │
├────────────────────────────────────────────────────────────────────┤
│ {'1': one, '2': 2, 'three': [1, 2, 3], 'False': true} │
└────────────────────────────────────────────────────────────────────┘

Wenn das dict von einer Funktion zurückgegeben wird, gibt die Funktion aufgrund der automatischen Konvertierung eine MAP zurück. Um ein STRUCT zurückzugeben, muss der return_type angegeben werden:

import duckdb
from duckdb.sqltypes import BOOLEAN, INTEGER, VARCHAR
from duckdb import list_type, struct_type
duckdb_conn = duckdb.connect()
my_struct_dict = {
1: "one",
"2": 2,
"three": [1, 2, 3],
False: True
}
def get_struct() -> dict[str|int|bool,str|int|list[int]|bool]:
return my_struct_dict
duckdb_conn.create_function("get_struct_as_map", get_struct)
duckdb_conn.sql("select get_struct_as_map()").show()
duckdb_conn.create_function("get_struct", get_struct, return_type=struct_type({
1: VARCHAR,
"2": INTEGER,
"three": list_type(INTEGER),
False: BOOLEAN
}))
duckdb_conn.sql("select get_struct()").show()
┌──────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ get_struct_as_map() │
│ map(union(u1 varchar, u2 bigint, u3 boolean), union(u1 varchar, u2 bigint, u3 bigint[], u4 boolean)) │
├──────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ {1=one, 2=2, three=[1, 2, 3], false=true} │
└──────────────────────────────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────┐
│ get_struct() │
│ struct("1" varchar, "2" integer, three integer[], "false" boolean) │
├────────────────────────────────────────────────────────────────────┤
│ {'1': one, '2': 2, 'three': [1, 2, 3], 'False': true} │
└────────────────────────────────────────────────────────────────────┘

Jeder key des Dictionarys wird in einen String konvertiert.

tuple

tuple wird standardmäßig nach LIST konvertiert; wenn es zum Erzeugen eines Value-Objekts vom Typ STRUCT verwendet wird, wird es stattdessen nach STRUCT konvertiert.

numpy.ndarray und numpy.datetime64

ndarray und datetime64 werden konvertiert, indem tolist() aufgerufen und das Ergebnis davon konvertiert wird.

Ergebniskonvertierung: DuckDB-Ergebnisse nach Python

Der Python-Client von DuckDB bietet mehrere zusätzliche Methoden, mit denen Daten effizient abgerufen werden können.

NumPy

  • fetchnumpy() holt die Daten als Dictionary von NumPy-Arrays

Pandas

  • df() holt die Daten als Pandas DataFrame
  • fetchdf() ist ein Alias von df()
  • fetch_df() ist ein Alias von df()
  • fetch_df_chunk(vector_multiple) holt einen Teil der Ergebnisse in ein DataFrame. Die Anzahl der in jedem Chunk zurückgegebenen Zeilen ist die Vektorgröße (standardmäßig 2048) * vector_multiple (standardmäßig 1).

Apache Arrow

Deprecated fetch_arrow_table() und fetch_record_batch() sind veraltet. Verwenden Sie stattdessen to_arrow_table() und to_arrow_reader().

Polars

  • pl() holt die Daten als Polars DataFrame

Beispiele

Unten einige Beispiele für diese Funktionalität. Weitere Beispiele finden Sie in den Python-Anleitungen.

Als Pandas DataFrame holen:

df = con.execute("SELECT * FROM items").fetchdf()
print(df)
item value count
0 jeans 20.0 1
1 hammer 42.2 2
2 laptop 2000.0 1
3 chainsaw 500.0 10
4 iphone 300.0 2

Als Dictionary von NumPy-Arrays holen:

arr = con.execute("SELECT * FROM items").fetchnumpy()
print(arr)
{'item': masked_array(data=['jeans', 'hammer', 'laptop', 'chainsaw', 'iphone'],
mask=[False, False, False, False, False],
fill_value='?',
dtype=object), 'value': masked_array(data=[20.0, 42.2, 2000.0, 500.0, 300.0],
mask=[False, False, False, False, False],
fill_value=1e+20), 'count': masked_array(data=[1, 2, 1, 10, 2],
mask=[False, False, False, False, False],
fill_value=999999,
dtype=int32)}

Als Arrow-Tabelle holen. Anschließend nur zur schöneren Ausgabe nach Pandas konvertieren:

tbl = con.execute("SELECT * FROM items").to_arrow_table()
print(tbl.to_pandas())
item value count
0 jeans 20.00 1
1 hammer 42.20 2
2 laptop 2000.00 1
3 chainsaw 500.00 10
4 iphone 300.00 2