Comment exporter des données au format CSV à partir de SQL Server à l'aide de sqlcmd?

136

Je peux assez facilement vider des données dans un fichier texte tel que:

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

Cependant, j'ai regardé les fichiers d'aide pour SQLCMDmais je n'ai pas vu d'option spécifiquement pour CSV.

Existe-t-il un moyen de vider les données d'une table dans un fichier texte CSV en utilisant SQLCMD?

Rayon
la source
Doit-il être via sqlcmd, ou pourriez-vous utiliser un autre programme tel que le suivant: codeproject.com/KB/aspnet/ImportExportCSV.aspx
Bernhard Hofmann
Ce n'est pas nécessaire, mais je voulais savoir avec certitude si sqlcmd pouvait réellement faire cela avant de plonger dans un autre utilitaire d'exportation. Une chose à mentionner est qu'il doit être scriptable.
Ray
Il existe un outil complémentaire SSMS 2008 qui produit une sortie CSV à partir de vos tables, qui peut être personnalisé selon les clauses where et order by. store.nmally.com/software/sql-server-management-studio-addons/…

Réponses:

140

Vous pouvez exécuter quelque chose comme ceci:

sqlcmd -S MyServer -d myDB -E -Q "select col1, col2, col3 from SomeTable" 
       -o "MyData.csv" -h-1 -s"," -w 700
  • -h-1 supprime les en-têtes de nom de colonne du résultat
  • -s"," définit le séparateur de colonnes sur,
  • -w 700 définit la largeur de la ligne à 700 caractères (cela devra être aussi large que la ligne la plus longue ou elle passera à la ligne suivante)
Scottm
la source
22
La mise en garde en procédant de cette manière est que vos données ne doivent pas contenir de virgules.
Sarel Botha
1
@SarelBotha, vous pouvez contourner ce problème avec des '""' + col1 + '""' AS col1guillemets doubles (doublés) ou simplement appeler une procédure stockée.
MisterIsaak du
2
@JIsaak Ensuite, assurez-vous que vos données ne comportent pas de guillemets doubles ou assurez-vous de remplacer vos guillemets par deux guillemets.
Sarel Botha
1
Quelqu'un pourrait-il clarifier ce qui doit être fait pour autoriser les virgules à l'intérieur des données? Doit-on entourer chaque colonne de '""' + ___ + '""'?
Ahmed
2
Cette réponse est désormais dépassée. Les scripts PowerShell sont plus flexibles et peuvent être exécutés dans SQL Server en tant qu'agent de travail.
Clinton Ward
75

Avec PowerShell, vous pouvez résoudre le problème proprement en envoyant Invoke-Sqlcmd dans 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 inclut le module SqlServer , qui contient l' Invoke-Sqlcmdapplet de commande, que vous aurez même si vous installez simplement SSMS 2016. Auparavant, SQL Server 2012 incluait l'ancien module SQLPS , qui modifiait le répertoire actuel en celui SQLSERVER:\où le module était utilisé pour la première fois (parmi d'autres bogues) donc pour cela, vous devrez changer la #Requiresligne ci-dessus en:

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

Pour adapter l'exemple à SQL Server 2008 et 2008 R2, supprimez #Requiresentièrement la ligne et utilisez l' utilitaire sqlps.exe au lieu de l'hôte PowerShell standard.

Invoke-Sqlcmd est l'équivalent PowerShell de sqlcmd.exe. Au lieu de texte, il génère des objets System.Data.DataRow .

Le -Queryparamètre fonctionne comme le -Qparamètre de sqlcmd.exe. Transmettez-lui une requête SQL décrivant les données que vous souhaitez exporter.

Le -Databaseparamètre fonctionne comme le -dparamètre de sqlcmd.exe. Transmettez-lui le nom de la base de données contenant les données à exporter.

Le -Serverparamètre fonctionne comme le -Sparamètre de sqlcmd.exe. Transmettez-lui le nom du serveur contenant les données à exporter.

Export-CSV est une applet de commande PowerShell qui sérialise des objets génériques au format CSV. Il est livré avec PowerShell.

Le -NoTypeInformationparamètre supprime la sortie supplémentaire qui ne fait pas partie du format CSV. Par défaut, l'applet de commande écrit un en-tête avec des informations de type. Il vous permet de connaître le type de l'objet lorsque vous le désérialisez plus tard avec Import-Csv, mais il confond les outils qui attendent un CSV standard.

Le -Pathparamètre fonctionne comme le -oparamètre de sqlcmd.exe. Un chemin complet pour cette valeur est le plus sûr si vous êtes bloqué à l'aide de l'ancien module SQLPS .

Le -Encodingparamètre fonctionne comme les paramètres -fou -ude sqlcmd.exe. Par défaut, Export-Csv génère uniquement des caractères ASCII et remplace tous les autres par des points d'interrogation. Utilisez plutôt UTF8 pour conserver tous les caractères et rester compatible avec la plupart des autres outils.

Le principal avantage de cette solution par rapport à sqlcmd.exe ou bcp.exe est que vous n'avez pas à pirater la commande pour générer un CSV valide. L'applet de commande Export-Csv gère tout pour vous.

Le principal inconvénient est qu'il Invoke-Sqlcmdlit l'ensemble des résultats avant de le transmettre le long du pipeline. Assurez-vous que vous disposez de suffisamment de mémoire pour l'ensemble du jeu de résultats que vous souhaitez exporter.

Cela peut ne pas fonctionner correctement pour des milliards de lignes. Si cela pose un problème, vous pouvez essayer les autres outils ou Invoke-Sqlcmdutiliser votre propre version efficace de la classe System.Data.SqlClient.SqlDataReader .

Iain Samuel McLean Elder
la source
5
Les autres réponses sont vraiment nulles, c'est la seule façon de le faire correctement. J'aurais aimé que ce soit plus évident.
Shagglez
1
Sur SQL 2008 R2, j'ai dû exécuter l'outil "sqlps.exe" pour utiliser Invoke-Sqlcmd. Apparemment, j'ai besoin de SQL 2012 pour utiliser Import-Module? Anway cela fonctionne dans "sqlps.exe" - voir ce fil pour plus de détails .
Mister_Tom
1
@Mister_Tom bon point. Le module SQLPS a été introduit avec SQL 2012. La réponse explique maintenant comment adapter l'exemple aux éditions plus anciennes.
Iain Samuel McLean Elder
1
@JasonMatney PowerShell est la nouvelle interface d'administration des systèmes Windows, mais de nombreux conseils SQL Server ont été publiés avant qu'il ne devienne standard. Faire connaitre! :-)
Iain Samuel McLean Elder
1
Cette réponse fournit des informations utiles et une manière ALTERNATIVE puissante et flexible de traiter le problème, mais elle ne parvient pas du tout à répondre à la question d'origine telle qu'elle a été spécifiquement posée. Je suis aussi fan de PowerShell, mais ne laissons pas l'évangélisation se transformer en hystérie. SQLCMD ne disparaîtra pas de sitôt.
barbecue le
69
sqlcmd -S myServer -d myDB -E -o "MyData.txt" ^
    -Q "select bar from foo" ^
    -W -w 999 -s","

La dernière ligne contient des options spécifiques au CSV.

  • -W   supprimer les espaces de fin de chaque champ individuel
  • -s","   définit le séparateur de colonnes sur la virgule (,)
  • -w 999   définit la largeur de ligne à 999 caractères

La réponse de scottm est très proche de ce que j'utilise, mais je trouve que -Wc'est un ajout vraiment sympa: je n'ai pas besoin de couper les espaces lorsque je consomme le CSV ailleurs.

Consultez également la référence MSDN sqlcmd . Cela fait /?honte à la sortie de l' option.

ESV
la source
19
@sims "set nocount on" au début de la requête / du fichier d'entrée
d -_- b
7
Comment puis-je supprimer le soulignement sur les en-têtes?
ntombela
@gugulethun: Vous pouvez faire une union dans votre requête pour mettre le nom de la colonne sur la première ligne.
Nordes
2
Cela fonctionne comme un charme, mais si votre colonne contient le séparateur, j'obtiens un fichier csv corrompu ...
Peter
Ajout de ce commentaire à la réponse acceptée également mais ... vous pouvez contourner ce problème avec des '""' + col1 + '""' AS col1guillemets (doublés) ou simplement appeler une procédure stockée.
MisterIsaak du
59

N'est-ce pas bcpfait pour?

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

Exécutez ceci à partir de votre ligne de commande pour vérifier la syntaxe.

bcp /?

Par exemple:

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]

Veuillez noter que bcpne peut pas afficher les en-têtes de colonne.

Voir: page de documentation de l' utilitaire bcp .

Exemple de la page ci-dessus:

bcp.exe MyTable out "D:\data.csv" -T -c -C 65001 -t , ...
johndacostaa
la source
1
ServerName = YourcomputerName \ SQLServerName, alors seulement il s'exécute sinon erreur
Hammad Khan
1
Si vous souhaitez consulter la documentation complète de bcp.exe: technet.microsoft.com/en-us/library/ms162802.aspx
Robert Bernstein
5
Que faites-vous si vous devez également exporter les noms de colonnes en tant qu'en-tête? Existe-t-il une solution générique simple utilisant bcp?
Iain Samuel McLean Elder
@johndacosta merci beaucoup. Comment imprimeriez-vous également les en-têtes de colonne? Je ne vois nulle part un passage facile. Merci!
Rachael
1
bcpn'inclut pas l'en-tête (noms de colonne), mais c'est ~ 10X plus rapide que sqlcmd(d'après mon expérience). Pour un très gros volume de données, vous pouvez utiliser bcppour obtenir les données, et utiliser sqlcmd(sélectionner top 0 * de ...) pour obtenir l'en-tête, puis les combiner.
YJZ
12

Une note pour tous ceux qui cherchent à faire cela mais aussi les en-têtes de colonne, c'est la solution que j'ai utilisée dans un fichier batch:

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

Cela génère les résultats initiaux (y compris les séparateurs ----, ---- entre les en-têtes et les données) dans un fichier temporaire, puis supprime cette ligne en la filtrant via findstr. Notez que ce n'est pas parfait car il est filtré -,-- cela ne fonctionnera pas s'il n'y a qu'une seule colonne dans la sortie, et il filtrera également les lignes légitimes qui contiennent cette chaîne.

Rudisme
la source
1
Utilisez plutôt le filtre suivant: findstr / r / v ^ \ - [, \ -] * $> output.csv Pour une raison quelconque, simle ^ [, \ -] * $ correspond à toutes les lignes.
Vladimir Korolev
3
Mettez toujours une expression régulière avec ^ entre guillemets doubles, ou vous obtenez des résultats étranges, car ^ est un caractère d'échappement pour cmd.exe. Les deux expressions régulières ci-dessus ne fonctionnent pas correctement, pour autant que je sache, mais c'est le cas: findstr / r / v "^ - [-,] * -. $" (Le. Avant le $ semble être nécessaire lors du test avec echo, mais peut-être pas pour la sortie sqlcmd)
JimG
Il y a aussi un problème avec les dates ayant 2 espaces entre la date et l'heure au lieu d'un. Lorsque vous essayez d'ouvrir le CSV dans Excel, il s'affiche sous la forme 00: 00.0. Un moyen simple de résoudre ce problème serait de rechercher et de remplacer tous les "" par "" sur place à l'aide de SED. La commande à ajouter à votre script serait: SED -i "s / / / g" output.csv. En savoir plus sur SED gnuwin32.sourceforge.net/packages/sed.htm
PollusB
c'est la bonne réponse, fonctionne parfaitement avec l'ajout de @ JimG. J'ai utilisé un point-virgule pour le séparateur et un fichier sql pour l'entrée, le fichier contenait la partie noncount on et ansi_warning off, et le commutateur -W pour la suppression de l'espace
robotik
1

Cette réponse s'appuie sur la solution de @ iain-aîné, qui fonctionne bien sauf pour le cas de la grande base de données (comme indiqué dans sa solution). La table entière doit tenir dans la mémoire de votre système, et pour moi ce n'était pas une option. Je soupçonne que la meilleure solution utiliserait System.Data.SqlClient.SqlDataReader et un sérialiseur CSV personnalisé ( voir ici pour un exemple ) ou un autre langage avec un pilote MS SQL et une sérialisation CSV. Dans l'esprit de la question originale qui cherchait probablement une solution sans dépendance, le code PowerShell ci-dessous a fonctionné pour moi. Il est très lent et inefficace, en particulier pour instancier le tableau $ data et appeler Export-Csv en mode ajout pour chaque ligne $ 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
la source
1

Option alternative avec BCP:

exec master..xp_cmdshell 'BCP "sp_who" QUERYOUT C:\av\sp_who.txt -S MC0XENTC -T -c '
Arnav
la source
1

Livré généralement sqlcmdavec un bcputilitaire (dans le cadre de mssql-tools) qui exporte au format CSV par défaut.

Usage:

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

Par exemple:

bcp.exe MyTable out data.csv

Pour vider toutes les tables dans les fichiers CSV correspondants, voici le script 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
la source
0

Une réponse ci-dessus l'a presque résolu pour moi, mais elle ne crée pas correctement un CSV analysé.

Voici ma version:

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

Quelqu'un qui dit que sqlcmdc'est obsolète en faveur d'une alternative PowerShell oublie que ce sqlcmdn'est pas seulement pour Windows. Je suis sous Linux (et sous Windows, j'évite de toute façon PS).

Cela dit, je trouve bcpplus facile.

Hack-R
la source
0

Depuis 2 raisons suivantes, vous devez exécuter ma solution dans CMD:

  1. Il peut y avoir des guillemets doubles dans la requête
  2. Le nom d'utilisateur et le mot de passe de connexion sont parfois nécessaires pour interroger une instance SQL Server distante

    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
la source
-2

Vous pouvez le faire de manière piratée. Attention en utilisant le sqlcmdhack. Si les données comportent des guillemets ou des virgules, vous rencontrerez des problèmes.

Vous pouvez utiliser un simple script pour le faire correctement:

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

Source: Ecriture de la sortie SQL au format CSV avec VBScript .

Sarel Botha
la source
1
Une explication du vote défavorable serait bien. Ma réponse est correcte: vous ne pouvez pas faire cela avec sqlcmd. J'ai également proposé une autre façon d'accomplir la tâche.
Sarel Botha le
4
Le vote négatif est que vous POUVEZ clairement faire ce que OP demande avec sqlcmd.
Brian Driscoll
2
@BrianDriscoll, Il n'a pas dit que l' on ne pouvait pas le faire avec sqlcmd, nous disions simplement que cela sqlcmdn'échappait pas correctement à la virgule, ce qui était à peine utilisable pour une sortie CSV sérieuse .
Sebastian
J'ai dit cela, mais j'étais fatigué des votes négatifs, alors j'ai édité ma réponse. Je vais rendre mon montage plus véridique.
Sarel Botha