2015-11-20 13 views
5

Ho 74 DataFrames Pandas relativamente grandi (circa 34.600 righe e 8 colonne) che sto cercando di inserire in un database SQL Server il più rapidamente possibile. Dopo aver fatto qualche ricerca, ho appreso che la buona funzione pandas.to_sql non è buona per inserti così grandi in un database SQL Server, che era l'approccio iniziale che ho preso (molto lento - quasi un'ora per completare l'applicazione vs circa 4 minuti . quando si utilizzano database mysql)Write Large Pandas DataFrames su database SQL Server

This article, e molti altri posti StackOverflow sono stati utili a me che punta nella direzione giusta, ma ho colpito un posto di blocco:

sto cercando di utilizzare core SQLAlchemy piuttosto che il ORM per ragioni spiegate nel link sopra. Quindi, io sono la conversione del dataframe ad un dizionario, usando pandas.to_dict e poi facendo un execute() e insert():

self._session_factory.engine.execute(
    TimeSeriesResultValues.__table__.insert(), 
    data) 
# 'data' is a list of dictionaries. 

Il problema è che inserto non sta ottenendo alcun valore - appaiono come una serie di parentesi vuota e io ottenere questo errore:

(pyodbc.IntegretyError) ('23000', "[23000] [FreeTDS][SQL Server]Cannot 
insert the value NULL into the column... 

ci sono valori nella lista dei dizionari che ho passato in, quindi non riesco a capire il motivo per cui i valori non vengono visualizzati.

EDIT:

Ecco l'esempio che sto andando fuori di:

def test_sqlalchemy_core(n=100000): 
    init_sqlalchemy() 
    t0 = time.time() 
    engine.execute(
     Customer.__table__.insert(), 
     [{"name": 'NAME ' + str(i)} for i in range(n)] 
    ) 
    print("SQLAlchemy Core: Total time for " + str(n) + 
     " records " + str(time.time() - t0) + " secs") 
+0

* circa 4 minuti quando si utilizza database MySQL * ... in modo che il 'to_sql()' è una soluzione praticabile solo la connessione è più lento in MSSQL rispetto a MySQL? Quale API ODBC stai usando? Il server di database è locale o remoto? Prendi in considerazione l'importazione di una tabella temporanea e poi esegui la migrazione al tavolo finale. – Parfait

+0

@Parfait: l'utilizzo di '' 'to_sql()' '' produce prestazioni accettabili con MySQL, ma non con MSSQL. Sto usando pyodbc. Il database è remoto, quindi scrivere in file CSV e quindi fare un inserimento di massa tramite codice SQL non funzionerà in questa situazione. Inoltre, gli utenti necessitano di privilegi di amministrazione di massa, il che potrebbe non essere sempre possibile per gli utenti di questa applicazione. – denvaar

+1

Considerare di bypassare il driver odbc e utilizzare un'API Python rigorosa - [pmyssl] (http://www.pymssql.org/en/latest/) E l'API ODBC MySQL? pymysql? Stessa struttura tabella e tipi di dati in entrambi? Stesso numero di record? Veramente indagare su questo. Entrambi sono RDMS aziendali di alto livello e non devono eseguire un intervallo di questo intervallo (4 minuti contro ~ 60 minuti). – Parfait

risposta

7

ho una triste notizia per voi, SQLAlchemy in realtà non implementa importazioni di massa per SQL Server, è in realtà solo andando a fare le stesse istruzioni INSERT singolo lento che sta facendo to_sql. Direi che la cosa migliore è provare a scrivere qualcosa usando lo strumento da riga di comando bcp. Ecco uno script che ho usato in passato, ma non ci sono garanzie:

from subprocess import check_output, call 
import pandas as pd 
import numpy as np 
import os 

pad = 0.1 
tablename = 'sandbox.max.pybcp_test' 
overwrite=True 
raise_exception = True 
server = 'P01' 
trusted_connection= True 
username=None 
password=None 
delimiter='|' 
df = pd.read_csv('D:/inputdata.csv', encoding='latin', error_bad_lines=False) 



def get_column_def_sql(col): 
    if col.dtype == object: 
     width = col.str.len().max() * (1+pad) 
     return '[{}] varchar({})'.format(col.name, int(width)) 
    elif np.issubdtype(col.dtype, float): 
     return'[{}] float'.format(col.name) 
    elif np.issubdtype(col.dtype, int): 
     return '[{}] int'.format(col.name) 
    else: 
     if raise_exception: 
     raise NotImplementedError('data type {} not implemented'.format(col.dtype)) 
     else: 
     print('Warning: cast column {} as varchar; data type {} not implemented'.format(col, col.dtype)) 
     width = col.str.len().max() * (1+pad) 
     return '[{}] varchar({})'.format(col.name, int(width)) 

def create_table(df, tablename, server, trusted_connection, username, password, pad):   
    if trusted_connection: 
     login_string = '-E' 
    else: 
     login_string = '-U {} -P {}'.format(username, password) 

    col_defs = [] 
    for col in df: 
     col_defs += [get_column_def_sql(df[col])] 

    query_string = 'CREATE TABLE {}\n({})\nGO\nQUIT'.format(tablename, ',\n'.join(col_defs))  
    if overwrite == True: 
     query_string = "IF OBJECT_ID('{}', 'U') IS NOT NULL DROP TABLE {};".format(tablename, tablename) + query_string 


    query_file = 'c:\\pybcp_tempqueryfile.sql' 
    with open (query_file,'w') as f: 
     f.write(query_string) 

    if trusted_connection: 
     login_string = '-E' 
    else: 
     login_string = '-U {} -P {}'.format(username, password) 

    o = call('sqlcmd -S {} {} -i {}'.format(server, login_string, query_file), shell=True) 
    if o != 0: 
     raise BaseException("Failed to create table") 
    # o = call('del {}'.format(query_file), shell=True) 


def call_bcp(df, tablename): 
    if trusted_connection: 
     login_string = '-T' 
    else: 
     login_string = '-U {} -P {}'.format(username, password) 
    temp_file = 'c:\\pybcp_tempqueryfile.csv' 

    #remove the delimiter and change the encoding of the data frame to latin so sql server can read it 
    df.loc[:,df.dtypes == object] = df.loc[:,df.dtypes == object].apply(lambda col: col.str.replace(delimiter,'').str.encode('latin')) 
    df.to_csv(temp_file, index = False, sep = '|', errors='ignore') 
    o = call('bcp sandbox.max.pybcp_test2 in c:\pybcp_tempqueryfile.csv -S "localhost" -T -t^| -r\n -c') 
+0

Grazie per la risposta - non so se tutto ciò che riguarda la creazione di un file funzionerà comunque per questa particolare situazione. – denvaar

+0

Avete ulteriori informazioni sul motivo per cui non lo supportano? – denvaar