如何使用sqlcmd从SQL Server以CSV格式导出数据?


136

我可以很容易地将数据转储到文本文件中,例如:

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

但是,我查看了帮助文件,SQLCMD但没有看到专门用于CSV的选项。

有没有办法使用将数据从表中转储到CSV文本文件中SQLCMD


必须通过sqlcmd执行此操作,还是可以使用其他程序,例如以下代码: codeproject.com/KB/aspnet/ImportExportCSV.aspx
Bernhard Hofmann,2009年

不一定要这样,但我想确定sqlcmd在进入其他导出实用程序之前是否确实可以执行此操作。要提到的一件事是它确实需要脚本化。

有一个SSMS 2008插件工具,可以从表中进行CSV输出,可以通过where和order by子句进行自定义。store.nmally.com/software/sql-server-management-studio-addons/…–

Answers:


140

您可以运行以下内容:

sqlcmd -S MyServer -d myDB -E -Q "select col1, col2, col3 from SomeTable" 
       -o "MyData.csv" -h-1 -s"," -w 700
  • -h-1 从结果中删除列名标题
  • -s"," 将列分隔符设置为,
  • -w 700 将行宽度设置为700个字符(此长度必须与最长的行一样宽,否则将换行到下一行)

22
这样做的警告是您的数据可能不包含任何逗号。
Sarel Botha

1
@SarelBotha,您可以使用将其'""' + col1 + '""' AS col1括起来(双引号)或仅调用存储过程来解决该问题。
MisterIsaak 2013年

2
@JIsaak然后确保您的数据没有任何双引号,或确保将您的双引号替换为两个双引号。
Sarel Botha

1
有人可以澄清要在数据内部使用逗号的方法吗?我们是否必须用'“”'+ ___ +'“”''包围每一列?
艾哈迈德(Ahmed)

2
这个答案现在已经过时了。PowerShell脚本更加灵活,可以作为作业代理在SQL Server中运行。
克林顿·沃德

75

使用PowerShell,可以通过将Invoke-Sqlcmd传递到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包含SqlServer模块,该模块包含Invoke-Sqlcmdcmdlet,即使您仅安装SSMS 2016也将拥有该cmdlet。在此之前,SQL Server 2012包含旧的SQLPS模块,该模块会将当前目录更改SQLSERVER:\为模块第一次使用(以及其他错误),因此,您需要将#Requires上面的行更改为:

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

若要使该示例适用于SQL Server 2008和2008 R2,请#Requires完全删除该行,并使用sqlps.exe实用程序而不是标准PowerShell主机。

Invoke-Sqlcmd是sqlcmd.exe的PowerShell等效项。而不是文本,而是输出System.Data.DataRow对象。

-Query参数的工作方式类似于-Qsqlcmd.exe 的参数。向其传递描述您要导出的数据的SQL查询。

-Database参数的工作方式类似于-dsqlcmd.exe 的参数。将包含要导出数据的数据库的名称传递给它。

-Server参数的工作方式类似于-Ssqlcmd.exe 的参数。将包含要导出数据的服务器的名称传递给它。

Export-CSV是将常规对象序列化为CSV的PowerShell cmdlet。它随PowerShell一起提供。

-NoTypeInformation参数抑制不属于CSV格式的额外输出。默认情况下,该cmdlet写入带有类型信息的标头。当您稍后使用来反序列化对象时,它可以让您知道对象的类型,但是会使Import-Csv期望标准CSV的工具感到困惑。

-Path参数的工作方式类似于-osqlcmd.exe 的参数。如果您使用旧的SQLPS模块,则此值的完整路径最安全。

-Encoding参数的工作方式类似于-f-usqlcmd.exe的参数。默认情况下,Export-Csv仅输出ASCII字符,并将所有其他字符替换为问号。请改用UTF8保留所有字符并与大多数其他工具保持兼容。

与sqlcmd.exe或bcp.exe相比,此解决方案的主要优点是您无需修改​​命令即可输出有效的CSV。Export-Csv cmdlet会为您处理所有这一切。

主要的缺点是Invoke-Sqlcmd在沿管道传递整个结果集之前先读取它。确保为要导出的整个结果集有足够的内存。

对于数十亿行,它可能无法正常运行。如果存在问题,则可以尝试使用其他工具,或Invoke-Sqlcmd使用 System.Data.SqlClient.SqlDataReader类推出自己的高效版本。


5
老实说,其他答案很糟糕,这是正确执行此操作的唯一方法。我希望这更加明显。
Shagglez 2014年

1
在SQL 2008 R2上,我必须运行“ sqlps.exe”工具才能使用Invoke-Sqlcmd。显然我需要SQL 2012才能使用Import-Module?它可以在“ sqlps.exe”中运行- 有关详细信息,请参见此线程
Mister_Tom'2

1
@Mister_Tom好点。SQLPS模块是在SQL 2012中引入的。现在的答案说明了如何使该示例适用于旧版本。
伊恩·塞缪尔·麦克莱恩

1
@JasonMatney PowerShell是Windows系统的新管理界面,但是在成为标准之前已发布了许多SQL Server建议。传播这个词!:-)
伊恩·塞缪尔·麦克莱恩

1
该答案提供了有用的信息以及强大而灵活的替代方法来处理该问题,但是,它确实不能完全回答最初提出的原始问题。我也很喜欢Powershell,但不要让福音变成歇斯底里。SQLCMD不会很快消失。
烧烤

69
sqlcmd -S myServer -d myDB -E -o "MyData.txt" ^
    -Q "select bar from foo" ^
    -W -w 999 -s","

最后一行包含CSV特定的选项。

  • -W   从每个字段中删除尾随空格
  • -s","   将列分隔符设置为逗号(,)
  • -w 999   将行宽度设置为999个字符

scottm的答案与我使用的答案非常接近,但是我发现s-W确是一个很好的补充:当我在其他地方使用CSV时,不需要修剪空格。

另请参见MSDN sqlcmd参考。它将/?选项的输出置于耻辱之中。


19
@sims在查询/输入文件的开头“将nocount设置为开”
d -_- b,2010年

7
如何删除标题上的下划线?
ntombela 2010年

@gugulethun:您可以在查询中进行并集以将列名放在第一行。
2011年

2
这就像一个符咒,但是如果您的列包含分隔符,我会得到一个损坏的csv文件...
Peter

也将此注释添加到了可接受的答案中,但是...您可以使用'""' + col1 + '""' AS col1,用(双引号)双引号引起来解决该问题,或者仅调用存储过程。
MisterIsaak 2013年

59

这不是bcp故意的吗?

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

从命令行运行此命令以检查语法。

bcp /?

例如:

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]

请注意,bcp不能输出列标题。

请参阅:bcp实用程序文档页面。

上一页的示例:

bcp.exe MyTable out "D:\data.csv" -T -c -C 65001 -t , ...

1
ServerName = YourcomputerName \ SQLServerName,只有这样它才会执行否则错误
Hammad Khan

1
如果您想查看bcp.exe的完整文档,请访问:technet.microsoft.com/en-us/library/ms162802.aspx
Robert Bernstein

5
如果还需要将列名称导出为标题怎么办?是否有使用bcp的简单通用解决方案?
伊恩·塞缪尔·麦克莱恩

@johndacosta非常感谢。您还将如何打印列标题?我看不到任何地方都可以轻松切换到它。谢谢!
拉切尔

1
bcp不包含标题(列名),但比sqlcmd(根据我的经验)快10倍。对于真正的大数据,您可以使用bcp获取数据,并使用sqlcmd(选择top 0 * from ...)获取标头,然后将它们组合在一起。
YJZ

12

给任何想这样做但也有列标题的人的说明,这是我使用批处理文件的解决方案:

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

这会将初始结果(包括标头和数据之间的----,----分隔符)输出到临时文件中,然后通过findstr将其过滤掉以删除该行。请注意,它不是完美的,因为它会被过滤掉-,-–如果输出中只有一列,它将无法正常工作,并且还会过滤掉包含该字符串的合法行。


1
请改用以下过滤器:findstr / r / v ^ \-[,\-] * $> output.csv由于某种原因,simle ^ [,\-] * $匹配所有行。
弗拉基米尔·科罗廖夫

3
总是在正则表达式中用^括在双引号中,否则会得到奇怪的结果,因为^是cmd.exe的转义字符。据我所知,上述两个正则表达式均无法正常运行,但这确实可以做到:findstr / r / v“ ^-[-,] *-。$”(使用进行测试时,似乎需要使用$之前的。回声,但可能不适用于sqlcmd输出)
JimG 2012年

日期在日期和时间之间有两个空格而不是一个空格也是一个问题。当您尝试在Excel中打开CSV时,它显示为00:00.0。解决此问题的一种简单方法是使用SED搜索所有“”并将其替换为“”。要添加到脚本中的命令为:SED -i“ s / / g” output.csv。更多关于SED gnuwin32.sourceforge.net/packages/sed.htm
PollusB

这是正确的答案,与@JimG的添加项完美配合。我使用分号作为分隔符,并使用sql文件作为输入,该文件包含noncount on和ansi_warning off部分,以及-W开关用于空间删除
robotik 2015年

1

这个答案建立在@ iain-elder的解决方案的基础上,该解决方案除了大型数据库案例(在他的解决方案中指出)外,效果很好。整个表需要适合您系统的内存,对我来说这不是一个选择。我怀疑最好的解决方案是使用System.Data.SqlClient.SqlDataReader和自定义CSV序列化程序(请参阅此处的示例)或具有MS SQL驱动程序和CSV序列化的另一种语言。本着可能要寻找无依赖解决方案的原始问题的精神,以下PowerShell代码对我有用。这非常慢且效率低下,特别是在实例化$ data数组并以附加模式为每条$ chunk_size行调用Export-Csv时。

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

1

BCP的备用选项:

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

1

通常sqlcmd附带bcp实用程序(作为的一部分mssql-tools),默认情况下会导出为CSV。

用法:

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

例如:

bcp.exe MyTable out data.csv

要将所有表转储到相应的CSV文件中,请使用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

0

上面的答案几乎为我解决了它,但是它没有正确创建已解析的CSV。

这是我的版本:

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

有人说过sqlcmd一些支持PowerShell的替代品已经过时了,而忘记了这sqlcmd不仅适用于Windows。我在Linux上(而在Windows上无论如何我都避免使用PS)。

说了这么多,我确实发现bcp容易些。


0

由于以下两个原因,您应该在CMD中运行我的解决方案:

  1. 查询中可能会有双引号
  2. 有时需要登录用户名和密码来查询远程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

-2

您可以采用骇客的方式进行。小心使用sqlcmdhack。如果数据用双引号或逗号引起,则会遇到麻烦。

您可以使用简单的脚本正确执行此操作:

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' 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@me.here"           ' 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!")

来源:使用VBScript将SQL输出写入CSV


1
对下降投票的解释会很好。我的回答是正确的:您无法使用sqlcmd执行此操作。我还提供了另一种完成任务的方法。
Sarel Botha

4
不好的理由是因为您显然可以使用sqlcmd来执行OP所要求的操作。
Brian Driscoll 2012年

2
@BrianDriscoll,他没有说不能使用sqlcmd,我们只是在说明一个事实,sqlcmd即不能正确地转义逗号,因此几乎不能用于任何严重的 CSV输出。
塞巴斯蒂安

我确实这么说,但是厌倦了反对票,所以我编辑了答案。我将使编辑更加真实。
Sarel Botha
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.