Pandas & NumPy Academy · Lektion

Skrivning af DataFrames til databasetabeller

Gem en renset DataFrame i en ny eller eksisterende tabel med DataFrame.to_sql(), og styr parametrene if_exists og chunksize.

Lektion 3 af 413 trin

Skrivning af DataFrames til databasetabeller er en gratis Pandas & NumPy Academy-lektion på CoddyKit. Dette er lektion 3 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Pandas & NumPy Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Pandas & NumPy Academy-kurset indeholder 4 lektioner i alt.

Hvorfor skrive DataFrames til databaser?

Efter at have renset og transformeret data i Pandas har du ofte brug for at gemme resultaterne permanent i en database igen: for at gøre dem tilgængelige for andre programmer, dashboards eller teammedlemmer, for at gemme trinvise analyseresultater eller for at opbygge et datamart fra en rå datasø. DataFrame.to_sql() er Pandas' standardmetode til at skrive data til enhver database, der understøttes af SQLAlchemy, i ét enkelt kald.

Grundlæggende brug af to_sql()

df.to_sql('table_name', con=engine, if_exists='replace', index=False) skriver DataFramen til en databasetabel. Parameteren if_exists bestemmer, hvad der sker, hvis tabellen allerede findes: 'fail' udløser en fejl, 'replace' sletter og opretter tabellen igen, og 'append' tilføjer nye rækker uden at ændre de eksisterende. Angiv altid index=False, medmindre du specifikt vil gemme DataFramens indeks som en kolonne i databasen.

import pandas as pd
import sqlalchemy as sa

engine = sa.create_engine('sqlite:///results.db')

df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=5),
    'revenue': [1200.0, 980.5, 1450.0, 760.3, 1100.0],
    'region': ['North', 'South', 'East', 'West', 'North']
})

df.to_sql('daily_revenue', con=engine,
          if_exists='replace', index=False)
print('Table written successfully')

Forklaring af parameteren if_exists

De tre værdier for if_exists bruges i forskellige situationer. 'replace' er beregnet til udvikling: Slet den gamle tabel, og opret en ny — ændringer i skemaet sker automatisk, men alle gamle data går tabt. 'append' bruges til trinvise indlæsninger: Tilføj nye rækker til den eksisterende tabel uden at ændre dens struktur — det er nyttigt til daglige batchjob. 'fail' fungerer som en sikkerhedsforanstaltning: Brug den til at beskytte vigtige tabeller mod utilsigtet overskrivning fra en datapipeline med en fejl.

import pandas as pd
import sqlalchemy as sa

engine = sa.create_engine('sqlite:///results.db')

new_batch = pd.DataFrame({
    'date': ['2024-06-01', '2024-06-02'],
    'revenue': [1500.0, 1300.0],
    'region': ['North', 'East']
})

# Append new rows without losing existing data
new_batch.to_sql('daily_revenue', con=engine,
                 if_exists='append', index=False)
print('Appended new rows')

Styring af kolonners datatyper

Som standard knytter to_sql() automatisk Pandas-datatyper til SQLAlchemy-typer. Nogle gange er standarderne forkerte — en datetime64-kolonne kan for eksempel blive gemt som TEXT i SQLite. Brug parameteren dtype til at angive de præcise SQL-typer ved hjælp af SQLAlchemy-typeobjekter. Det sikrer korrekt lagring, korrekt indeksering og præcis typehåndtering, når dataene læses tilbage. Kontrollér altid skemaet efter skrivning med en hurtig PRAGMA table_info() eller inspector.get_columns().

import pandas as pd
import sqlalchemy as sa
from sqlalchemy import types

engine = sa.create_engine('sqlite:///results.db')

df = pd.DataFrame({
    'id': [1, 2, 3],
    'name': ['Alice', 'Bob', 'Carol'],
    'score': [0.95, 0.87, 0.91],
    'created_at': pd.to_datetime(['2024-01-01', '2024-01-02', '2024-01-03'])
})

df.to_sql('users', con=engine, if_exists='replace', index=False,
          dtype={'id': types.Integer(),
                 'score': types.Float(),
                 'created_at': types.DateTime()})

Skriv i blokke med chunksize

For store DataFrames forsøger to_sql() uden chunksize at indsætte alle rækker i én enkelt sætning, hvilket kan føre til en databasetimeout eller en hukommelsesfejl. Angiv chunksize=N for at indsætte N rækker pr. transaktion. Det giver databasen mulighed for at gennemføre ændringer trinvist og reducerer det maksimale hukommelsesforbrug. En chunksize på 10.000–50.000 rækker giver typisk en god balance mellem indsættelseshastighed og hukommelsesforbrug, men den optimale værdi afhænger af din database og netværksforsinkelse.

import pandas as pd
import sqlalchemy as sa
import numpy as np

engine = sa.create_engine('sqlite:///results.db')

# Large DataFrame
df = pd.DataFrame({
    'id': range(500000),
    'value': np.random.randn(500000)
})

# Insert in chunks of 10,000 rows at a time
df.to_sql('large_table', con=engine,
          if_exists='replace',
          index=False,
          chunksize=10000)
print('Written 500,000 rows')

Upsert: indsæt eller opdatér

Pandas' to_sql() understøtter ikke upsert indbygget (indsæt, hvis ny, og opdatér, hvis den findes). Brug SQLAlchemy Core med en INSERT OR REPLACE- (SQLite) eller ON CONFLICT DO UPDATE-sætning (PostgreSQL) for at implementere upsert. Den almindelige løsning i Pandas er at skrive til en midlertidig stagingtabel med if_exists='replace', derefter køre rå SQL for at flette stagingtabellen ind i produktionstabellen og til sidst slette stagingtabellen.

import pandas as pd
import sqlalchemy as sa

engine = sa.create_engine('sqlite:///results.db')

new_data = pd.DataFrame({
    'id': [1, 2, 5],
    'value': [99.9, 88.8, 77.7]
})

# Write to staging table
new_data.to_sql('staging', con=engine,
                if_exists='replace', index=False)

# Merge into production (SQLite syntax)
with engine.connect() as conn:
    conn.execute(sa.text(
        'INSERT OR REPLACE INTO production SELECT * FROM staging'
    ))
    conn.commit()
print('Upsert complete')

Kontrol af skrivningen

Efter skrivning skal du altid kontrollere resultatet ved at læse en opsummerende optælling og et rækketal tilbage. Sammenlign dem med kilde-DataFramen. Det afslører fejl, der ellers ikke giver sig til kende, forårsaget af uoverensstemmelser mellem datatyper (f.eks. NaN i en heltalskolonne, som medfører delvise indsættelser) eller databasebegrænsninger (f.eks. overtrædelser af entydige nøgler, som i nogle konfigurationer stille springer rækker over). En hurtig SELECT COUNT(*) FROM table efter hvert kald til to_sql medfører kun en minimal ekstra belastning og forhindrer ubemærket datatab.

import pandas as pd
import sqlalchemy as sa

engine = sa.create_engine('sqlite:///results.db')

df = pd.DataFrame({'id': range(1000), 'value': range(1000)})
df.to_sql('my_table', con=engine, if_exists='replace', index=False)

# Verify
with engine.connect() as conn:
    count = conn.execute(sa.text('SELECT COUNT(*) FROM my_table')).scalar()
print(f'Source rows: {len(df)}, DB rows: {count}')
assert count == len(df), 'Row count mismatch!'

Tilføjelse af en primærnøgle efter skrivning

to_sql() skriver data, men tilføjer ikke primærnøgler eller databasebegrænsninger — den opretter en almindelig tabel. For en produktionstabel skal du tilføje primærnøglebegrænsningen separat ved hjælp af rå SQL, der udføres gennem SQLAlchemy. SQLite kræver, at tabellen oprettes igen for at tilføje begrænsninger efter oprettelsen, mens PostgreSQL understøtter ALTER TABLE ADD PRIMARY KEY. Alternativt kan du definere hele skemaet på forhånd og bruge if_exists='append' til at indsætte data i en eksisterende tabel med en korrekt definition.

import pandas as pd
import sqlalchemy as sa
from sqlalchemy import Table, Column, Integer, Float, MetaData

engine = sa.create_engine('sqlite:///results.db')
meta = MetaData()

# Define table with primary key
my_table = Table('defined_table', meta,
    Column('id', Integer, primary_key=True),
    Column('value', Float)
)
meta.create_all(engine)  # Create table with constraints

# Then insert data using append
df = pd.DataFrame({'id': range(5), 'value': [1.1, 2.2, 3.3, 4.4, 5.5]})
df.to_sql('defined_table', con=engine,
          if_exists='append', index=False)

Transaktionelle skrivninger

For at sikre datakonsistens skal du pakke to_sql() ind i en eksplicit transaktion. Hvis et trin i en skrivning til flere tabeller mislykkes, kan du rulle alle ændringer tilbage. Uden en transaktion kan delvise skrivninger efterlade databasen i en inkonsistent tilstand. SQLAlchemys forbindelseskontekststyring med conn.begin() muliggør manuel transaktionsstyring. Alternativt kan du bruge engine.begin() til en blok med automatisk gennemførelse, som ruller tilbage ved en undtagelse.

import pandas as pd
import sqlalchemy as sa

engine = sa.create_engine('sqlite:///results.db')

df_orders = pd.DataFrame({'id': [1, 2], 'amount': [100.0, 200.0]})
df_summary = pd.DataFrame({'total': [300.0], 'count': [2]})

try:
    with engine.begin() as conn:  # Auto-rollback on exception
        df_orders.to_sql('orders_v2', con=conn,
                         if_exists='replace', index=False)
        df_summary.to_sql('summary_v2', con=conn,
                          if_exists='replace', index=False)
    print('Both tables written atomically')
except Exception as e:
    print(f'Write failed, rolled back: {e}')

Ydeevne: metoder til masseindsættelse

Som standard indsætter to_sql() én række pr. SQL-sætning, hvilket er meget langsomt for store DataFrames. Angiv method='multi' for at bruge én INSERT med flere værdisæt — typisk 10-100 gange hurtigere. I PostgreSQL kan du angive en tilpasset method-funktion, der bruger COPY-protokollen (via psycopg2's copy_expert) til den absolut hurtigste masseindlæsning. Den optimale metode afhænger af din databaseversion og netværksopsætning.

import pandas as pd
import sqlalchemy as sa
import numpy as np
import time

engine = sa.create_engine('sqlite:///perf.db')
df = pd.DataFrame({'a': range(100000), 'b': np.random.randn(100000)})

# Default (one row per INSERT) — slow
start = time.time()
df.to_sql('test_default', con=engine, if_exists='replace', index=False)
print(f'Default: {time.time()-start:.2f}s')

# multi-row INSERT — faster
start = time.time()
df.to_sql('test_multi', con=engine, if_exists='replace',
          index=False, method='multi', chunksize=1000)
print(f'Multi: {time.time()-start:.2f}s')

Logning og revision af skrivninger

I produktionsdatapipelines skal du registrere, hvad der blev skrevet, og hvornår det skete, ved at vedligeholde en revisionslogtabel. Efter hver vellykkede to_sql() skal du indsætte en række i revisionsloggen med tabelnavn, rækketælling, tidsstempel og datapipelinekørslens ID. Det gør det nemt at opdage manglende kørsler, dobbeltskrivninger eller ændringer i skemaet over tid. Selve revisionsloggen er en Pandas-DataFrame, der skrives via to_sql — den samme teknik anvendt rekursivt til driftsovervågning.

import pandas as pd
import sqlalchemy as sa
from datetime import datetime

engine = sa.create_engine('sqlite:///results.db')

def write_with_audit(df, table_name, engine, run_id):
    df.to_sql(table_name, con=engine, if_exists='append', index=False)
    audit = pd.DataFrame([{
        'run_id': run_id,
        'table_name': table_name,
        'rows_written': len(df),
        'written_at': datetime.utcnow().isoformat()
    }])
    audit.to_sql('audit_log', con=engine, if_exists='append', index=False)
    print(f'Wrote {len(df)} rows to {table_name}')

df = pd.DataFrame({'id': [1, 2], 'val': [10, 20]})
write_with_audit(df, 'my_table', engine, run_id='run_001')

Hurtig kontrol

Test din forståelse af begreberne inden for dataanalyse fra denne lektion.

Opsummering af lektionen

I denne lektion har du lært, at df.to_sql() skriver en DataFrame til enhver SQLAlchemy-forbundet databasetabel, hvor parameteren if_exists styrer adfærden ved oprettelse, tilføjelse og erstatning, at chunksize og method='multi' forbedrer ydeevnen for store DataFrames, og at transaktionelle skrivninger med engine.begin() sikrer atomiske opdateringer af flere tabeller, som rulles tilbage ved fejl. Nu sammenligner vi Pandas og SQL for at forstå, hvornår hvert værktøj er det bedste valg.

Gratis at komme i gang

Lær Python med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
30
Lektioner
120

Ofte stillede spørgsmål

Er lektionen “Skrivning af DataFrames til databasetabeller” gratis?

Ja — hele teksten til “Skrivning af DataFrames til databasetabeller” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Pandas & NumPy Academy-kurset, skal du opgradere til CoddyKit PRO. Pandas & NumPy Academy-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Skrivning af DataFrames til databasetabeller”?

Gem en renset DataFrame i en ny eller eksisterende tabel med DataFrame.to_sql(), og styr parametrene if_exists og chunksize. Du øver dig i Pandas & NumPy Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Pandas & NumPy Academy?

Der kræves ingen tidligere erfaring. Pandas & NumPy Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 3 af 4.

Hvor lang tid tager lektionen “Skrivning af DataFrames til databasetabeller”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Pandas & NumPy Academy-lektion?

Ja. Alle Pandas & NumPy Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Forbindelse til en database med SQLAlchemy
  2. Kørsel af SQL-forespørgsler fra Pandas
  3. Skrivning af DataFrames til databasetabeller
  4. Pandas eller SQL: Valg af det rette værktøj
← Tilbage til Pandas & NumPy Academy