Jak wyeksportować dane w formacie CSV z SQL Server przy użyciu narzędzia sqlcmd?

136

Mogę dość łatwo zrzucić dane do pliku tekstowego, takiego jak:

sqlcmd -S myServer -d myDB -E -Q "select col1, col2, col3 from SomeTable" 
     -o "MyData.txt"

Jednak przejrzałem pliki pomocy dla, SQLCMDale nie widziałem opcji specjalnie dla CSV.

Czy istnieje sposób na zrzucenie danych z tabeli do pliku tekstowego CSV za pomocą SQLCMD?

Promień
źródło
Musi to być za pośrednictwem sqlcmd, czy możesz użyć innego programu, takiego jak następujący: codeproject.com/KB/aspnet/ImportExportCSV.aspx
Bernhard Hofmann
Nie musi tak być, ale chciałem wiedzieć na pewno, czy sqlcmd może to zrobić, zanim zagłębię się w inne narzędzie eksportowe. Należy wspomnieć, że musi być skryptowalny.
Ray
Istnieje narzędzie dodatkowe SSMS 2008, które generuje dane wyjściowe w formacie CSV z tabel, które można dostosowywać według miejsca i kolejności według klauzul. store.nmally.com/software/sql-server-management-studio-addons/…

Odpowiedzi:

140

Możesz uruchomić coś takiego:

sqlcmd -S MyServer -d myDB -E -Q "select col1, col2, col3 from SomeTable" 
       -o "MyData.csv" -h-1 -s"," -w 700
  • -h-1 usuwa nagłówki nazw kolumn z wyniku
  • -s"," ustawia separator kolumn na,
  • -w 700 ustawia szerokość wiersza na 700 znaków (będzie to musiało być tak szerokie, jak najdłuższy wiersz, w przeciwnym razie zostanie zawinięte do następnej linii)
scottm
źródło
22
Zastrzeżenie związane z robieniem tego w ten sposób polega na tym, że Twoje dane nie mogą zawierać przecinków.
Sarel Botha
1
@SarelBotha, możesz obejść ten problem '""' + col1 + '""' AS col1, zawijając (podwojone) podwójne cudzysłowy lub po prostu wywołując procedurę składowaną.
MisterIsaak,
2
@JIsaak Następnie upewnij się, że Twoje dane nie mają żadnych podwójnych cudzysłowów lub zamień podwójne cudzysłowy na dwa podwójne cudzysłowy.
Sarel Botha
1
Czy ktoś mógłby wyjaśnić, co należy zrobić, aby w danych można było wprowadzić przecinki? Czy musimy otoczyć każdą kolumnę znakami '""' + ___ + '""'?
Ahmed
2
Ta odpowiedź jest teraz nieaktualna. Skrypty PowerShell są bardziej elastyczne i można je uruchamiać w programie SQL Server jako agent zadań.
Clinton Ward
75

Za pomocą PowerShell można starannie rozwiązać problem, przesyłając Invoke-Sqlcmd do Export-Csv.

#Requires -Module SqlServer
Invoke-Sqlcmd -Query "SELECT * FROM DimDate;" `
              -Database AdventureWorksDW2012 `
              -Server localhost |
Export-Csv -NoTypeInformation `
           -Path "DimDate.csv" `
           -Encoding UTF8

SQL Server 2016 zawiera moduł SqlServer , który zawiera polecenie Invoke-Sqlcmdcmdlet, które będziesz mieć nawet po zainstalowaniu programu SSMS 2016. Wcześniej SQL Server 2012 zawierał stary moduł SQLPS , który zmienił bieżący katalog na ten, w SQLSERVER:\którym moduł był jako pierwszy używany (wśród innych błędów), więc w tym celu musisz zmienić #Requirespowyższy wiersz na:

Push-Location $PWD
Import-Module -Name SQLPS
# dummy query to catch initial surprise directory change
Invoke-Sqlcmd -Query "SELECT 1" `
              -Database  AdventureWorksDW2012 `
              -Server localhost |Out-Null
Pop-Location
# actual Invoke-Sqlcmd |Export-Csv pipeline

Aby dostosować przykład do SQL Server 2008 i 2008 R2, usuń #Requirescałkowicie linię i użyj narzędzia sqlps.exe zamiast standardowego hosta PowerShell.

Invoke-Sqlcmd jest odpowiednikiem programu PowerShell dla sqlcmd.exe. Zamiast tekstu generuje obiekty System.Data.DataRow .

-QueryParametr działa jak -Qparametr sqlcmd.exe. Przekaż mu zapytanie SQL opisujące dane, które chcesz wyeksportować.

-DatabaseParametr działa jak -dparametr sqlcmd.exe. Podaj nazwę bazy danych zawierającej dane do wyeksportowania.

-ServerParametr działa jak -Sparametr sqlcmd.exe. Podaj nazwę serwera zawierającego dane do wyeksportowania.

Export-CSV to polecenie cmdlet programu PowerShell, które serializuje obiekty ogólne do CSV. Jest dostarczany z PowerShell.

Ten -NoTypeInformationparametr pomija dodatkowe dane wyjściowe, które nie są częścią formatu CSV. Domyślnie cmdlet zapisuje nagłówek z informacjami o typie. Pozwala poznać typ obiektu podczas późniejszej deserializacji Import-Csv, ale myli narzędzia, które oczekują standardowego pliku CSV.

-PathParametr działa jak -oparametr sqlcmd.exe. Pełna ścieżka dla tej wartości jest najbezpieczniejsza, jeśli utkniesz przy użyciu starego modułu SQLPS .

-EncodingParametr działa jak -flub -uparametrów sqlcmd.exe. Domyślnie Export-Csv generuje tylko znaki ASCII i zastępuje wszystkie inne znakami zapytania. Zamiast tego użyj UTF8, aby zachować wszystkie znaki i zachować zgodność z większością innych narzędzi.

Główną zaletą tego rozwiązania w porównaniu z programem sqlcmd.exe lub bcp.exe jest to, że nie trzeba hakować polecenia, aby wyświetlić prawidłowy plik CSV. Polecenie cmdlet Export-Csv obsługuje to wszystko za Ciebie.

Główną wadą jest to, że Invoke-Sqlcmdczyta cały zestaw wyników przed przekazaniem go w potoku. Upewnij się, że masz wystarczająco dużo pamięci na cały zestaw wyników, który chcesz wyeksportować.

Może nie działać płynnie dla miliardów wierszy. Jeśli to jest problem, można spróbować innych narzędzi, lub toczyć własną wydajną wersję Invoke-Sqlcmdużywając System.Data.SqlClient.SqlDataReader klasę.

Iain Samuel McLean Elder
źródło
5
Inne odpowiedzi szczerze są do niczego, to jedyny sposób, aby zrobić to poprawnie. Chciałbym, żeby było to bardziej oczywiste.
Shagglez,
1
W SQL 2008 R2 musiałem uruchomić narzędzie „sqlps.exe”, aby użyć Invoke-Sqlcmd. Najwyraźniej potrzebuję SQL 2012, aby używać Import-Module? W każdym razie działa w ramach "sqlps.exe" - szczegóły w tym wątku .
Mister_Tom,
1
@Mister_Tom dobra uwaga. Moduł SQLPS został wprowadzony wraz z SQL 2012. W odpowiedzi wyjaśniono teraz, jak dostosować przykład do starszych wersji.
Iain Samuel McLean Elder
1
@JasonMatney PowerShell to nowy interfejs administracyjny do systemów Windows, ale wiele porad dotyczących SQL Server zostało opublikowanych zanim stał się standardem. Szerz nowinę! :-)
Iain Samuel McLean Elder
1
Ta odpowiedź dostarcza użytecznych informacji oraz potężnego i elastycznego ALTERNATYWNEGO sposobu rozwiązania problemu, jednak całkowicie nie daje odpowiedzi na pierwotne pytanie, tak jak zostało to konkretnie zadane. Jestem też fanem PowerShell, ale nie pozwólmy, aby ewangelizacja zamieniła się w histerię. SQLCMD w najbliższym czasie nie zniknie.
grill
69
sqlcmd -S myServer -d myDB -E -o "MyData.txt" ^
    -Q "select bar from foo" ^
    -W -w 999 -s","

Ostatnia linia zawiera opcje specyficzne dla CSV.

  • -W   usuń końcowe spacje z każdego pojedynczego pola
  • -s","   ustawia separator kolumn na przecinek (,)
  • -w 999   ustawia szerokość wiersza na 999 znaków

odpowiedź scottm jest bardzo zbliżona do tego, czego używam, ale uważam, że -Wjest to naprawdę fajny dodatek: nie muszę przycinać białych znaków, gdy konsumuję plik CSV w innym miejscu.

Zobacz także dokumentację MSDN sqlcmd . To /?zawstydza wyjście opcji.

ESV
źródło
19
@sims "set nocount on" na początku zapytania / pliku wejściowego
d -_- b
7
Jak mogę usunąć podkreślenie z nagłówków?
ntombela
@gugulethun: W zapytaniu możesz utworzyć sumę, aby umieścić nazwę kolumny w pierwszym wierszu.
Nordes
2
Działa to jak urok, ale jeśli kolumna zawiera separator, otrzymuję uszkodzony plik csv ...
Peter
Dodałem ten komentarz do zaakceptowanej odpowiedzi, ale ... możesz obejść ten problem '""' + col1 + '""' AS col1, zawijając (podwojone) podwójne cudzysłowy lub po prostu wywołaj procedurę składowaną.
MisterIsaak,
59

Czy to nie bcpbyło przeznaczone?

bcp "select col1, col2, col3 from database.schema.SomeTable" queryout  "c:\MyData.txt"  -c -t"," -r"\n" -S ServerName -T

Uruchom to z linii poleceń, aby sprawdzić składnię.

bcp /?

Na przykład:

usage: bcp {dbtable | query} {in | out | queryout | format} datafile
  [-m maxerrors]            [-f formatfile]          [-e errfile]
  [-F firstrow]             [-L lastrow]             [-b batchsize]
  [-n native type]          [-c character type]      [-w wide character type]
  [-N keep non-text native] [-V file format version] [-q quoted identifier]
  [-C code page specifier]  [-t field terminator]    [-r row terminator]
  [-i inputfile]            [-o outfile]             [-a packetsize]
  [-S server name]          [-U username]            [-P password]
  [-T trusted connection]   [-v version]             [-R regional enable]
  [-k keep null values]     [-E keep identity values]
  [-h "load hints"]         [-x generate xml format file]
  [-d database name]

Pamiętaj, że bcpnie można wyświetlać nagłówków kolumn.

Zobacz: strona dokumentacji programu bcp Utility .

Przykład z powyższej strony:

bcp.exe MyTable out "D:\data.csv" -T -c -C 65001 -t , ...
johndacostaa
źródło
1
ServerName = YourcomputerName \ SQLServerName, tylko wtedy wykonuje błąd w przeciwnym razie
Hammad Khan
1
Jeśli chcesz zobaczyć pełną dokumentację bcp.exe
Robert Bernstein
5
Co zrobić, jeśli chcesz wyeksportować nazwy kolumn również jako nagłówek? Czy istnieje proste, ogólne rozwiązanie wykorzystujące bcp?
Iain Samuel McLean Starszy
@johndacosta wielkie dzięki. Jak wydrukowałeś również nagłówki kolumn? Nigdzie nie widzę łatwego przejścia na to. Dzięki!
Rachael
1
bcpnie zawiera nagłówka (nazw kolumn), ale jest ~ 10 razy szybszy niż sqlcmd(z mojego doświadczenia). W przypadku naprawdę dużych zbiorów danych możesz użyć, bcpaby pobrać dane i użyć sqlcmd(wybierz górne 0 * z ...), aby uzyskać nagłówek, a następnie połączyć je.
YJZ
12

Uwaga dla każdego, kto chce to zrobić, ale ma również nagłówki kolumn, oto rozwiązanie, w którym użyłem pliku wsadowego:

sqlcmd -S servername -U username -P password -d database -Q "set nocount on; set ansi_warnings off; sql query here;" -o output.tmp -s "," -W
type output.tmp | findstr /V \-\,\- > output.csv
del output.tmp

Spowoduje to wyświetlenie początkowych wyników (w tym separatorów ----, ---- między nagłówkami i danymi) do pliku tymczasowego, a następnie usunięcie tej linii, odfiltrowując ją za pomocą funkcji findstr. Zwróć uwagę, że nie jest doskonały, ponieważ odfiltrowuje -,-- nie zadziała, jeśli w wyniku jest tylko jedna kolumna, a także odfiltruje prawidłowe wiersze zawierające ten ciąg.

Rudyzm
źródło
1
Zamiast tego użyj następującego filtru: findstr / r / v ^ \ - [, \ -] * $> output.csv Z jakiegoś powodu proste ^ [, \ -] * $ dopasowuje wszystkie wiersze.
Vladimir Korolev
3
Zawsze umieszczaj wyrażenie regularne z ^ w podwójnych cudzysłowach, w przeciwnym razie otrzymasz dziwne wyniki, ponieważ ^ jest znakiem ucieczki dla cmd.exe. O ile wiem, oba powyższe wyrażenia regularne nie działają poprawnie, ale to działa: findstr / r / v "^ - [-,] * -. $" (Przed $ wydaje się być potrzebne podczas testowania z echo, ale może nie dla wyjścia sqlcmd)
JimG
Istnieje również problem z datami zawierającymi 2 spacje między datą a godziną zamiast jednej. Podczas próby otwarcia pliku CSV w programie Excel jest wyświetlany jako 00: 00.0. Prostym sposobem rozwiązania tego problemu byłoby wyszukanie i zastąpienie wszystkich elementów „” na miejscu przy użyciu SED. Polecenie, które należy dodać do skryptu, wyglądałoby tak: SED -i "s / / / g" output.csv. Więcej informacji o SED gnuwin32.sourceforge.net/packages/sed.htm
PollusB
to jest prawidłowa odpowiedź, doskonale współpracuje z dodatkiem @ JimG. Użyłem średnika jako separatora i pliku sql jako danych wejściowych, plik zawierał część noncount on i ansi_warning off oraz przełącznik -W do usuwania spacji
robotik
1

Ta odpowiedź opiera się na rozwiązaniu @ iain-Elder, które działa dobrze, z wyjątkiem przypadku dużej bazy danych (jak wskazano w jego rozwiązaniu). Cały stół musi zmieścić się w pamięci twojego systemu, a dla mnie nie było to możliwe. Podejrzewam, że najlepszym rozwiązaniem byłoby użycie System.Data.SqlClient.SqlDataReader i niestandardowego serializatora CSV ( zobacz tutaj przykład ) lub innego języka ze sterownikiem MS SQL i serializacją CSV. W duchu pierwotnego pytania, które prawdopodobnie szukało rozwiązania bez zależności, poniższy kod PowerShell zadziałał dla mnie. Jest bardzo powolny i nieefektywny, szczególnie w przypadku tworzenia instancji tablicy danych $ i wywoływania Export-Csv w trybie dopisywania dla każdej linii $ chunk_size.

$chunk_size = 10000
$command = New-Object System.Data.SqlClient.SqlCommand
$command.CommandText = "SELECT * FROM <TABLENAME>"
$command.Connection = $connection
$connection.open()
$reader = $command.ExecuteReader()

$read = $TRUE
while($read){
    $counter=0
    $DataTable = New-Object System.Data.DataTable
    $first=$TRUE;
    try {
        while($read = $reader.Read()){

            $count = $reader.FieldCount
            if ($first){
                for($i=0; $i -lt $count; $i++){
                    $col = New-Object System.Data.DataColumn $reader.GetName($i)
                    $DataTable.Columns.Add($col)
                }
                $first=$FALSE;
            }

            # Better way to do this?
            $data=@()
            $emptyObj = New-Object System.Object
            for($i=1; $i -le $count; $i++){
                $data +=  $emptyObj
            }

            $reader.GetValues($data) | out-null
            $DataRow = $DataTable.NewRow()
            $DataRow.ItemArray = $data
            $DataTable.Rows.Add($DataRow)
            $counter += 1
            if ($counter -eq $chunk_size){
                break
            }
        }
        $DataTable | Export-Csv "output.csv" -NoTypeInformation -Append
    }catch{
        $ErrorMessage = $_.Exception.Message
        Write-Output $ErrorMessage
        $read=$FALSE
        $connection.Close()
        exit
    }
}
$connection.close()
jeffmax
źródło
1

Alternatywna opcja z BCP:

exec master..xp_cmdshell 'BCP "sp_who" QUERYOUT C:\av\sp_who.txt -S MC0XENTC -T -c '
arnav
źródło
1

Zwykle sqlcmdzawiera bcpnarzędzie (jako część mssql-tools), które domyślnie eksportuje do CSV.

Stosowanie:

bcp {dbtable | query} {in | out | queryout | format} datafile

Na przykład:

bcp.exe MyTable out data.csv

Aby zrzucić wszystkie tabele do odpowiednich plików CSV, oto skrypt Bash :

#!/usr/bin/env bash
# Script to dump all tables from SQL Server into CSV files via bcp.
# @file: bcp-dump.sh
server="sql.example.com" # Change this.
user="USER" # Change this.
pass="PASS" # Change this.
dbname="DBNAME" # Change this.
creds="-S '$server' -U '$user' -P '$pass' -d '$dbname'"
sqlcmd $creds -Q 'SELECT * FROM sysobjects sobjects' > objects.lst
sqlcmd $creds -Q 'SELECT * FROM information_schema.routines' > routines.lst
sqlcmd $creds -Q 'sp_tables' | tail -n +3 | head -n -2 > sp_tables.lst
sqlcmd $creds -Q 'SELECT name FROM sysobjects sobjects WHERE xtype = "U"' | tail -n +3 | head -n -2 > tables.lst

for table in $(<tables.lst); do
  sqlcmd $creds -Q "exec sp_columns $table" > $table.desc && \
  bcp $table out $table.csv -S $server -U $user -P $pass -d $dbname -c
done
kenorb
źródło
0

Powyższa odpowiedź prawie rozwiązała problem, ale nie tworzy poprawnie przeanalizowanego pliku CSV.

Oto moja wersja:

sqlcmd -S myurl.com -d MyAzureDB -E -s, -W -i mytsql.sql | findstr /V /C:"-" /B > parsed_correctly.csv

Ktoś, kto mówi, że sqlcmdjest przestarzały na korzyść jakiejś alternatywy PowerShell, zapomina, że sqlcmdnie jest to tylko dla systemu Windows. Jestem na Linuksie (a na Windowsie i tak unikam PS).

Powiedziawszy to wszystko, wydaje mi się bcpłatwiejsze.

Hack-R
źródło
0

Z dwóch powodów powinieneś uruchomić moje rozwiązanie w CMD:

  1. Zapytanie może zawierać podwójne cudzysłowy
  2. Nazwa użytkownika i hasło logowania są czasami niezbędne do wysłania zapytania do zdalnej instancji SQL Server

    sqlcmd -U [your_User]  -P[your_password] -S [your_remote_Server] -d [your_databasename]  -i "query.txt" -o "output.csv" -s"," -w 700
Mohsen Abasi
źródło
-2

Możesz to zrobić w hackerski sposób. Ostrożnie wykorzystując sqlcmdhack. Jeśli dane zawierają podwójne cudzysłowy lub przecinki, napotkasz kłopoty.

Możesz użyć prostego skryptu, aby zrobić to poprawnie:

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Data Exporter                                                 '
'                                                               '
' Description: Allows the output of data to CSV file from a SQL '
'       statement to either Oracle, SQL Server, or MySQL        '
' Author: C. Peter Chen, http://dev-notes.com                   '
' Version Tracker:                                              '
'       1.0   20080414 Original version                         '
'   1.1   20080807 Added email functionality                '
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
option explicit
dim dbType, dbHost, dbName, dbUser, dbPass, outputFile, email, subj, body, smtp, smtpPort, sqlstr

'''''''''''''''''
' Configuration '
'''''''''''''''''
dbType = "oracle"                 ' Valid values: "oracle", "sqlserver", "mysql"
dbHost = "dbhost"                 ' Hostname of the database server
dbName = "dbname"                 ' Name of the database/SID
dbUser = "username"               ' Name of the user
dbPass = "password"               ' Password of the above-named user
outputFile = "c:\output.csv"      ' Path and file name of the output CSV file
email = "[email protected]"           ' Enter email here should you wish to email the CSV file (as attachment); if no email, leave it as empty string ""
  subj = "Email Subject"          ' The subject of your email; required only if you send the CSV over email
  body = "Put a message here!"    ' The body of your email; required only if you send the CSV over email
  smtp = "mail.server.com"        ' Name of your SMTP server; required only if you send the CSV over email
  smtpPort = 25                   ' SMTP port used by your server, usually 25; required only if you send the CSV over email
sqlStr = "select user from dual"  ' SQL statement you wish to execute
'''''''''''''''''''''
' End Configuration '
'''''''''''''''''''''



dim fso, conn

'Create filesystem object 
set fso = CreateObject("Scripting.FileSystemObject")

'Database connection info
set Conn = CreateObject("ADODB.connection")
Conn.ConnectionTimeout = 30
Conn.CommandTimeout = 30
if dbType = "oracle" then
    conn.open("Provider=MSDAORA.1;User ID=" & dbUser & ";Password=" & dbPass & ";Data Source=" & dbName & ";Persist Security Info=False")
elseif dbType = "sqlserver" then
    conn.open("Driver={SQL Server};Server=" & dbHost & ";Database=" & dbName & ";Uid=" & dbUser & ";Pwd=" & dbPass & ";")
elseif dbType = "mysql" then
    conn.open("DRIVER={MySQL ODBC 3.51 Driver}; SERVER=" & dbHost & ";PORT=3306;DATABASE=" & dbName & "; UID=" & dbUser & "; PASSWORD=" & dbPass & "; OPTION=3")
end if

' Subprocedure to generate data.  Two parameters:
'   1. fPath=where to create the file
'   2. sqlstr=the database query
sub MakeDataFile(fPath, sqlstr)
    dim a, showList, intcount
    set a = fso.createtextfile(fPath)

    set showList = conn.execute(sqlstr)
    for intcount = 0 to showList.fields.count -1
        if intcount <> showList.fields.count-1 then
            a.write """" & showList.fields(intcount).name & ""","
        else
            a.write """" & showList.fields(intcount).name & """"
        end if
    next
    a.writeline ""

    do while not showList.eof
        for intcount = 0 to showList.fields.count - 1
            if intcount <> showList.fields.count - 1 then
                a.write """" & showList.fields(intcount).value & ""","
            else
                a.write """" & showList.fields(intcount).value & """"
            end if
        next
        a.writeline ""
        showList.movenext
    loop
    showList.close
    set showList = nothing

    set a = nothing
end sub

' Call the subprocedure
call MakeDataFile(outputFile,sqlstr)

' Close
set fso = nothing
conn.close
set conn = nothing

if email <> "" then
    dim objMessage
    Set objMessage = CreateObject("CDO.Message")
    objMessage.Subject = "Test Email from vbs"
    objMessage.From = email
    objMessage.To = email
    objMessage.TextBody = "Please see attached file."
    objMessage.AddAttachment outputFile

    objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
    objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserver") = smtp
    objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = smtpPort

objMessage.Configuration.Fields.Update

    objMessage.Send
end if

'You're all done!!  Enjoy the file created.
msgbox("Data Writer Done!")

Źródło: zapisywanie wyników SQL do CSV za pomocą VBScript .

Sarel Botha
źródło
1
Wyjaśnienie negatywnego głosu byłoby miłe. Moja odpowiedź jest poprawna: nie możesz tego zrobić za pomocą sqlcmd. Zaproponowałem też alternatywny sposób wykonania zadania.
Sarel Botha,
4
Wynik negatywny jest taki, że wyraźnie MOŻESZ zrobić to, o co prosi OP, używając sqlcmd.
Brian Driscoll
2
@BrianDriscoll, On nie powiedział, że nie da się tego zrobić sqlcmd, po prostu stwierdziliśmy, że sqlcmdnie jest to właściwe uniknięcie przecinka, przez co jest ledwo nadające się do użycia w przypadku poważnych wyników CSV.
Sebastian,
Powiedziałem to, ale zmęczyły mnie głosy przeciw, więc zredagowałem odpowiedź. Sprawię, że moja zmiana będzie bardziej zgodna z prawdą.
Sarel Botha