2015-04-14 26 views
13

Di seguito è riportato il mio codice con il quale vorrei un aiuto. Devo eseguirlo su oltre 1.300.000 righe, il che significa che richiede fino a 40 minuti per inserire ~ 300.000 righe.Come velocizzare l'inserimento di massa in MS SQL Server da CSV utilizzando pyodbc

I figure bulk insert è il percorso per accelerare? Oppure perché sto iterando sulle righe tramite la porzione for data in reader:?

#Opens the prepped csv file 
with open (os.path.join(newpath,outfile), 'r') as f: 
    #hooks csv reader to file 
    reader = csv.reader(f) 
    #pulls out the columns (which match the SQL table) 
    columns = next(reader) 
    #trims any extra spaces 
    columns = [x.strip(' ') for x in columns] 
    #starts SQL statement 
    query = 'bulk insert into SpikeData123({0}) values ({1})' 
    #puts column names in SQL query 'query' 
    query = query.format(','.join(columns), ','.join('?' * len(columns))) 

    print 'Query is: %s' % query 
    #starts curser from cnxn (which works) 
    cursor = cnxn.cursor() 
    #uploads everything by row 
    for data in reader: 
     cursor.execute(query, data) 
     cursor.commit() 

sto raccogliendo in modo dinamico le intestazioni delle colonne di proposito (come vorrei creare il codice più divinatorio possibile).

SpikeData123 è il nome della tabella.

+0

Una volta che il codice funziona correttamente, rimuovere la stampa dovrebbe renderlo più veloce. – zulq

risposta

19

BULK INSERT sarà quasi sicuramente molto più veloce della lettura del file di origine riga per riga e di un INSERIMENTO regolare per ogni riga. Tuttavia, sia BULK INSERT che BCP presentano una limitazione significativa per quanto riguarda i file CSV in quanto non possono gestire i qualificatori di testo (rif: here). Cioè, se il file CSV non stringhe non sei qualificato testo in esso ...

1,Gord Thompson,2015-04-15 
2,Bob Loblaw,2015-04-07 

... allora si può Inserimento di massa, ma se contiene i qualificatori di testo (perché alcuni valori di testo contiene le virgole) ...

1,"Thompson, Gord",2015-04-15 
2,"Loblaw, Bob",2015-04-07 

... quindi BULK INSERT non può gestirlo. Ancora, potrebbe essere più veloce nel complesso di pre-processo di un file CSV in un file pipe delimitato ...

1|Thompson, Gord|2015-04-15 
2|Loblaw, Bob|2015-04-07 

... o un file delimitato da tabulazioni (dove rappresenta il carattere di tabulazione) ..

1→Thompson, Gord→2015-04-15 
2→Loblaw, Bob→2015-04-07 

... e quindi BULK INSERIRE quel file. Per questi ultimi (delimitato da tabulazioni) file il codice BULK INSERT sarebbe simile a questa:

import pypyodbc 
conn_str = "DSN=myDb_SQLEXPRESS;" 
cnxn = pypyodbc.connect(conn_str) 
crsr = cnxn.cursor() 
sql = """ 
BULK INSERT myDb.dbo.SpikeData123 
FROM 'C:\\__tmp\\biTest.txt' WITH (
    FIELDTERMINATOR='\\t', 
    ROWTERMINATOR='\\n' 
    ); 
""" 
crsr.execute(sql) 
cnxn.commit() 
crsr.close() 
cnxn.close() 

Nota: Come già detto in un commento, l'esecuzione di un'istruzione BULK INSERT è applicabile solo se l'istanza di SQL Server può leggere direttamente il file sorgente. Per i casi in cui il file di origine si trova su un client remoto, vedere this answer.

+0

Grazie, Gord! Ho bisogno di aiuto per il follow-up, ma volevo dire grazie! – TangoAlee

+3

So che questo è un vecchio post, ma questa soluzione funziona solo se il file si trova sullo stesso server di SQL Server (o in una posizione in cui l'utente del servizio SQL Server è in grado di vedere). Quindi se il file si trova sulla mia workstation e SQL Server è altrove thans questa soluzione non funzionerà – Gabor

+1

@Gabor - Buon punto. Vedi [questa risposta] (https://stackoverflow.com/a/47057189/2144390) per un'alternativa. –

1

yes bulk insert è il percorso corretto per il caricamento di file di grandi dimensioni in un DB. A colpo d'occhio, direi che la ragione per cui impiega così tanto tempo è come hai detto che stai scorrendo su ogni riga di dati dal file, il che significa in effetti rimuovere i vantaggi dell'uso di un inserto di massa e renderlo come un normale inserto. Ricorda solo che, come suggerisce il nome, è usato per inserire mandrini di dati. Vorrei rimuovere il ciclo e riprovare.

Inoltre, vorrei ricontrollare la sintassi per l'inserimento in blocco perché non mi sembra corretto. controlla lo sql generato da pyodbc poiché ho la sensazione che potrebbe essere solo un normale inserto

In alternativa se è ancora lento proverei a usare l'inserimento di massa direttamente da sql e caricare l'intero file in un temp tabella con inserto di massa quindi inserire la colonna pertinente nelle tabelle giuste. oppure utilizzare un mix di inserimento bulk e bcp per ottenere le colonne specifiche inserite o OPENROWSET.

8

Come indicato in un commento a un'altra risposta, il comando T-SQL BULK INSERT funziona solo se il file da importare si trova sulla stessa macchina dell'istanza di SQL Server o si trova in una posizione di rete SMB/CIFS che SQL L'istanza del server può leggere. Pertanto potrebbe non essere applicabile nel caso in cui il file di origine si trovi su un client remoto.

pyodbc 4.0.19 ha aggiunto una funzionalità Cursor#fast_executemany che potrebbe essere utile in tal caso. fast_executemany è "off" per impostazione predefinita, e il seguente codice di prova ...

cnxn = pyodbc.connect(conn_str, autocommit=True) 
crsr = cnxn.cursor() 
crsr.execute("TRUNCATE TABLE fast_executemany_test") 

sql = "INSERT INTO fast_executemany_test (txtcol) VALUES (?)" 
params = [(f'txt{i:06d}',) for i in range(1000)] 
t0 = time.time() 
crsr.executemany(sql, params) 
print(f'{time.time() - t0:.1f} seconds') 

... ha preso circa 22 secondi per l'esecuzione sulla mia macchina di prova. La semplice aggiunta crsr.fast_executemany = True ...

cnxn = pyodbc.connect(conn_str, autocommit=True) 
crsr = cnxn.cursor() 
crsr.execute("TRUNCATE TABLE fast_executemany_test") 

crsr.fast_executemany = True # new in pyodbc 4.0.19 

sql = "INSERT INTO fast_executemany_test (txtcol) VALUES (?)" 
params = [(f'txt{i:06d}',) for i in range(1000)] 
t0 = time.time() 
crsr.executemany(sql, params) 
print(f'{time.time() - t0:.1f} seconds') 

... ridotto il tempo di esecuzione di poco più di 1 secondo.

+0

Come si inserisce da un 'DataFrame' usando questo metodo? Ho provato 'df.values.tolist()' come la sezione 'VALUES' della query SQL, ma non ha funzionato. Inoltre, dove sarebbe il file '.txt' o' .csv' nella tua risposta? –

+0

@CameronTaylor ** (1) ** re: DataFrame - Potrebbe essere necessario convertire i valori da oggetti 'numpy' a tipi nativi di Python come illustrato in [questa risposta] (https://stackoverflow.com/a/46098694/ 2.144.390). ** (2) ** re: posizione del file CSV - Dovrebbe essere un posto dove leggere l'applicazione Python. Da lì avresti messo le informazioni in memoria, creato una lista di tuple e poi chiamato '.executemany'. –

+0

per fast_executemany +1 – ilyas