2015-04-14 14 views
13

Poniżej znajduje się mój kod, który chciałbym pomóc. Muszę uruchomić to ponad 1 300 000 wierszy, co oznacza, że ​​trwa do 40 minut wstawienia ~ 300 000 wierszy.Jak przyspieszyć wstawianie zbiorcze do MS SQL Server z CSV przy użyciu pyodbc

I figure bulk insert jest drogą do przyspieszenia? Czy to dlatego, że Iterowanie przez wiersze za pośrednictwem części 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() 

Dynamicznie wybieram nagłówki kolumn celowo (jako że chciałbym stworzyć najbardziej pytonowy kod).

SpikeData123 to nazwa tabeli.

+0

Gdy wiesz, że twój kod działa poprawnie, usuń wydruk, który powinien przyspieszyć. – zulq

Odpowiedz

19

BULK INSERT prawie na pewno będzie dużo szybszy niż czytanie pliku źródłowego rząd po rzędzie i robienie regularnego INSERT dla każdego rzędu. Jednak zarówno BULK INSERT, jak i BCP mają znaczne ograniczenia dotyczące plików CSV, ponieważ nie mogą obsługiwać kwalifikatorów tekstu (patrz: here). Oznacza to, że jeśli plik CSV nie nie zostały zakwalifikowane ciągi tekstowe w nim ...

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

... wtedy można BULK INSERT, ale jeśli zawiera kwalifikatorów tekstowych (bo niektóre wartości tekstowe zawiera przecinki) ...

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

... następnie BULK INSERT nie może sobie z tym poradzić. Mimo to, może to być szybciej ogólny na wstępne przetworzenie takiego pliku CSV do pliku rury rozdzielany ...

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

... lub plik tabulatorami (gdzie reprezentuje znak tabulatora) ..

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

... a następnie BULK WSTAWIĆ ten plik. W tym ostatnim przypadku (tabulatorami) złożyć kod BULK INSERT będzie wyglądać mniej więcej tak:

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() 

Uwaga: Jak wspomniano w komentarzu, wykonując BULK INSERT oświadczenie ma zastosowanie tylko wtedy, gdy instancja SQL Server można bezpośrednio odczytać Plik źródłowy. W przypadkach, gdy plik źródłowy znajduje się na zdalnym kliencie, zobacz this answer.

+0

Dziękuję Gord! Potrzebuję pomocy, ale chciałem podziękować! – TangoAlee

+3

Wiem, że jest to stary post, ale to rozwiązanie działa tylko wtedy, gdy plik znajduje się na tym samym serwerze co program SQL Server (lub w miejscu, w którym użytkownik usługi SQL Server może zobaczyć). Więc jeśli plik znajduje się na mojej stacji roboczej i SQL Server jest gdzie indziej, to rozwiązanie nie zadziała – Gabor

+1

@Gabor - Dobra uwaga. Zobacz [tę odpowiedź] (https://stackoverflow.com/a/47057189/2144390), aby uzyskać alternatywę. –

1

tak bulk insert jest właściwą ścieżką do ładowania dużych plików do DB. Na pierwszy rzut oka powiem, że powodem, dla którego zajmuje to tyle czasu, jest to, że wspomniałeś, że przechodzisz przez każdy wiersz danych z pliku, co w rzeczywistości oznacza usunięcie zalet używania wkładki luzem i uczynienie jej jak normalną wkładką. Pamiętaj tylko, że jak sama nazwa wskazuje, służy do wstawiania uchwytów danych. Chciałbym usunąć pętlę i spróbować ponownie.

Również sprawdziłbym twoją składnię dla wkładki zbiorczej, ponieważ nie wygląda to dla mnie poprawnie. sprawdź sql, który jest generowany przez pyodbc, ponieważ mam wrażenie, że może on tylko wykonywać normalną wstawkę Alternatywnie, jeśli nadal jest wolna chciałbym spróbować użyć bulk insert bezpośrednio z sql i albo załadować cały plik do temp tabela z wkładem luzem, następnie wstaw odpowiednią kolumnę do właściwych tabel. lub użyj kombinacji insertów zbiorczych i bcp, aby wstawić określone kolumny lub OPENROWSET.

8

Jak wspomniano w komentarzu do innej odpowiedzi, komenda T-SQL BULK INSERT zadziała tylko wtedy, gdy plik do zaimportowania znajduje się na tym samym komputerze co instancja programu SQL Server lub znajduje się w lokalizacji sieci SMB/CIFS, w której znajduje się SQL Instancja serwera może czytać. Dlatego może nie mieć zastosowania w przypadku, gdy plik źródłowy znajduje się na zdalnym kliencie.

pyodbc 4.0.19 dodano funkcję Cursor#fast_executemany, która może być pomocna w takim przypadku. fast_executemany jest „off” domyślnie i następujący kod testu ...

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') 

... trwało około 22 sekund, aby wykonać na moim komputerze testowym. Po prostu dodanie 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') 

... zmniejszono czas wykonywania do nieco ponad 1 sekundy.

+0

Jak wstawiłbyś z 'DataFrame' używając tej metody? Próbowałem 'df.values.tolist()' jako sekcji "VALUES" zapytania SQL, ale to nie zadziałało. Poza tym, gdzie w odpowiedzi powinien znaleźć się plik '.txt' lub' .csv'? –

+0

@CameronTaylor ** (1) ** re: DataFrame - Konieczne może być przekonwertowanie wartości z obiektów 'numpy' na natywne typy Pythona, jak pokazano w [tej odpowiedzi] (https://stackoverflow.com/a/46098694/ 2144390). ** (2) ** re: Lokalizacja pliku CSV - Musiałby być w jakimś miejscu, w którym aplikacja Python może czytać. Stamtąd można pobrać informacje do pamięci, utworzyć listę krotek, a następnie wywołać '.executemany'. –

+0

dla fast_executemany +1 – ilyas

Powiązane problemy