Python i PANDAS: Połączenie do bazy danych MSSQL

26-lut-2019

Pyton jest dobrym środowiskiem do analizy danych, ale te dane skąś trzeba brać i gdzieś trzeba je zapisywać. Tu z pomocą przyjdzie moduł pyodbc. Oto przykład gdzie:

  • pobieramy dane z bazy danych AdventureWorks
  • zapisujemy te dane do obiektu data frame
  • tworzymy tabelę w bazie danych tempdb
  • zapisujemy wybrane dane z dataframe do tej tabeli
import pandas as pd
import pyodbc

sql_conn = pyodbc.connect('DRIVER={ODBC Driver 13 for SQL Server};\
                            SERVER=Dragon01;\
                            DATABASE=Adventureworks;\
                            Trusted_Connection=yes') 
query = "select CurrencyCode, Name, ModifiedDate from Sales.Currency"
df = pd.read_sql(query, sql_conn)
print(df.head(3))

sql_conn = pyodbc.connect('DRIVER={ODBC Driver 13 for SQL Server};\
                            SERVER=Dragon01;\
                            DATABASE=tempdb;\
                            Trusted_Connection=yes')
cursor = sql_conn.cursor()

cursor.execute("IF OBJECT_ID('dbo.currencies') IS NOT NULL  DROP TABLE dbo.currencies")
sql_conn.commit()

cursor.execute("CREATE TABLE dbo.currencies(\
                             code VARCHAR(3),\
                             name VARCHAR(30))")
sql_conn.commit()

for index,row in df.iterrows():
  cursor.execute("INSERT INTO dbo.currencies(code,name) values (?,?)",
                      row['CurrencyCode'], row['Name']) 
  sql_conn.commit()

cursor.close()
sql_conn.close()

Po kolei:

-ładujemy moduły

import pandas as pd
import pyodbc

-tworzymy obiekt połączenia – należy w odpowiedni sposób wykonać to połączenie. Jeśli Trusted_Connection=yes, to nie trzeba podawać user, bo zostanie wykorzystane uwierzytelnienie windows

sql_conn = pyodbc.connect('DRIVER={ODBC Driver 13 for SQL Server};\
                            SERVER=Dragon01;\
                            DATABASE=Adventureworks;\
                            Trusted_Connection=yes')

zapytanie zapisujemy w postaci napisu:

query = "select CurrencyCode, Name, ModifiedDate from Sales.Currency"

dane pobiera się do data frame wykonując polecenie w ramach tego połączenia do bazy danych

df = pd.read_sql(query, sql_conn)
print(df.head(3))

A teraz w drugą stronę. Mamy już obiekt df i chcemy go zapisać w bazie tempdb. Interesują nas tylko 2 kolumny

-Znowu inicjuję połączenie, bo łączyć się będę do bazy tempdb

sql_conn = pyodbc.connect('DRIVER={ODBC Driver 13 for SQL Server};\
                            SERVER=Dragon01;\
                            DATABASE=tempdb;\
                            Trusted_Connection=yes')

Polecenia jakie będą wykonywane to zapisy, które nie zwracają rekordów, więc trzeba mieć obiekt, który coś wykonuje, a takim obiektem jest cursor:

cursor = sql_conn.cursor()

Ja chcę móc wielokrotnie puszczać ten skrypt, więc sprawdzam czy jest w tempdb tabela dbo.currencies i jak jest to ją usuwam. Polecenie execute mówi co będzie wykonywane, a commit rzeczywiście wykonuje to polecenie:

cursor.execute("IF OBJECT_ID('dbo.currencies') IS NOT NULL  DROP TABLE dbo.currencies")
sql_conn.commit()

a teraz tworzę tą tabelę na nowo:

cursor.execute("CREATE TABLE dbo.currencies(\
                             code VARCHAR(3),\
                             name VARCHAR(30))")
sql_conn.commit()

Teraz przechodzę przez wszystkie wiersze data frame i każdy z nich zapisuję poleceniem insert w tabeli. Każdy insert jest commitowany:

for index,row in df.iterrows():
  cursor.execute("INSERT INTO dbo.currencies(code,name) values (?,?)",
                      row['CurrencyCode'], row['Name']) 
  sql_conn.commit()

Na końcu zamykamy cursor i połączenie (istotne ze względu na ograniczoną ilość zasobów – tu połączenia SQL)

U mnie zadziałało, zobacz jak będzie u ciebie. Zapisywanie rekord po rekordzie jest nieoptymalne. Zapisanie całego data frame w jednym commit może zapełnić log transakcyjny bazy danych. Idealnie byłoby to robić w paczkach np po 1000 lub po 10000 insertów w jednym commit.

Komentarze:

  1. Rafał Kraik napisał,

    Propozycja ładowania całego data frame od razu:

    import urllib
    from sqlalchemy import create_engine

    params = urllib.parse.quote_plus(„DRIVER={SQL Server};SERVER=server_sql;DATABASE=baza;UID=user;trusted_connection=yes”)
    engine = create_engine(„mssql+pyodbc:///?odbc_connect=%s” % params)

    df.to_sql(’tabela_testowa’, engine, if_exists=’replace’)

Autor: Rafał Kraik