【问题标题】:How to export data as CSV format from SQL Server using sqlcmd?如何使用 sqlcmd 从 SQL Server 将数据导出为 CSV 格式?
【发布时间】:2010-09-30 08:04:01
【问题描述】:

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

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

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

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

【问题讨论】:

标签: sql-server file csv sqlcmd


【解决方案1】:

你可以这样运行:

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 个字符(这需要与最长行一样宽,否则它将换行到下一行)

【讨论】:

  • 这样做的注意事项是您的数据可能不包含任何逗号。
  • @SarelBotha,您可以使用 '""' + col1 + '""' AS col1 解决这个问题,用(双)双引号括起来,或者只调用存储过程。
  • @JIsaak 然后确保您的数据没有任何双引号或确保用两个双引号替换您的双引号。
  • 这个答案现在已经过时了。 PowerShell 脚本更加灵活,可以在 SQL Server 中作为作业代理运行。
  • 投反对票,因为这不会产生valid csv 格式的文件。查看使用Export-csv的答案。
【解决方案2】:

使用 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-Sqlcmd cmdlet,即使您只安装 SSMS 2016 也将拥有它。在此之前,SQL Server 2012 包含旧的 @ 987654321@,当第一次使用模块时(以及其他错误)会将当前目录更改为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 utility 而不是标准 PowerShell 主机。

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

-Query 参数的作用类似于 sqlcmd.exe 的-Q 参数。向它传递一个描述您要导出的数据的 SQL 查询。

-Database 参数的作用类似于 sqlcmd.exe 的-d 参数。将包含要导出的数据的数据库的名称传递给它。

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

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

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

-Path 参数的作用类似于 sqlcmd.exe 的 -o 参数。如果您在使用旧的 SQLPS 模块时遇到问题,则此值的完整路径是最安全的。

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

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

主要缺点是Invoke-Sqlcmd 在通过管道传递之前读取整个结果集。确保您有足够的内存用于要导出的整个结果集。

对于数十亿行,它可能无法顺利运行。如果这是一个问题,您可以尝试其他工具,或者使用 System.Data.SqlClient.SqlDataReader 类推出您自己的高效版本的Invoke-Sqlcmd

【讨论】:

  • 其他答案真的很糟糕,这是正确做到这一点的唯一方法。我希望它更明显。
  • 在 SQL 2008 R2 上,我必须运行“sqlps.exe”工具才能使用 Invoke-Sqlcmd。显然我需要 SQL 2012 才能使用 Import-Module? Anway 它在“sqlps.exe”中工作 - see this thread for details.
  • @Mister_Tom 好点。 SQLPS 模块是在 SQL 2012 中引入的。现在的答案解释了如何使示例适应旧版本。
  • @JasonMatney PowerShell 是 Windows 系统的新管理界面,但很多 SQL Server 建议在成为标准之前就已发布。传播这个词! :-)
  • 这个答案提供了有用的信息和一个强大而灵活的替代方法来处理这个问题,但是它确实完全无法回答原始问题,因为它是专门提出的。我也是一个 powershell 粉丝,但我们不要让传福音变成歇斯底里。 SQLCMD 不会很快消失。
【解决方案3】:
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 answer 与我使用的非常接近,但我发现 -W 是一个非常好的补充:当我在其他地方使用 CSV 时,我不需要修剪空白。

另见MSDN sqlcmd reference。它使/? 选项的输出感到羞耻。

【讨论】:

  • @sims "set nocount on" 在查询/输入文件的开头
  • 如何去除标题上的下划线?
  • @gugulethun :您可以在查询中进行联合,将列名放在第一行。
  • 这就像一个魅力,但如果你的列包含分隔符,我会得到一个损坏的 csv 文件......
  • 也将此评论添加到已接受的答案中,但是...您可以使用'""' + col1 + '""' AS col1 解决该问题,用(双引号)双引号括起来或只调用存储过程。
【解决方案4】:

这不是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 Utility 文档页面。

上页示例:

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

【讨论】:

  • ServerName = YourcomputerName\SQLServerName, 只有这样它才会执行否则错误
  • 如果您想查看 bcp.exe 的完整文档:technet.microsoft.com/en-us/library/ms162802.aspx
  • 如果您还需要将列名导出为标题怎么办?是否有使用 bcp 的简单通用解决方案?
  • @johndacosta 非常感谢。您还将如何打印列标题?我没有看到任何地方都可以轻松切换到它。谢谢!
  • bcp 不包含标题(列名),但它比sqlcmd 快约10 倍(根据我的经验)。对于非常大的数据,可以使用bcp获取数据,使用sqlcmd(select top 0 * from ...)获取header,然后合并。
【解决方案5】:

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

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 过滤掉该行来删除该行。请注意,它并不完美,因为它会过滤掉-,-——如果输出中只有一列,它将无法工作,它还会过滤掉包含该字符串的合法行。

【讨论】:

  • 使用以下过滤器代替: findstr /r /v ^\-[,\-]*$ > output.csv 出于某种原因,simle ^[,\-]*$ 匹配所有行。跨度>
  • 始终将正则表达式与 ^ 放在双引号内,否则您会得到奇怪的结果,因为 ^ 是 cmd.exe 的转义字符。据我所知,上面的两个正则表达式都不能正常工作,但这确实如此: findstr /r /v "^-[-,]*-.$" (在测试时似乎需要 $ 之前的 . echo,但可能不适用于 sqlcmd 输出)
  • 还有一个问题,日期和时间之间有 2 个空格而不是 1 个空格。当您尝试在 Excel 中打开 CSV 时,它显示为 00:00.0。解决此问题的一种简单方法是使用 SED 就地搜索并替换所有“”和“”。添加到脚本的命令是:SED -i "s/ / /g" output.csv。更多关于 SED gnuwin32.sourceforge.net/packages/sed.htm
  • 这是正确的答案,与@JimG 的添加完美配合。我使用分号作为分隔符,使用 sql 文件作为输入,该文件包含 noncount on 和 ansi_warning off 部分,以及用于空间删除的 -W 开关
【解决方案6】:

这个答案建立在@iain-elder 的解决方案之上,除了大型数据库案例(如他的解决方案中所指出的那样)外,该解决方案效果很好。整个表需要适合您系统的内存,对我来说这不是一个选择。我怀疑最好的解决方案是使用 System.Data.SqlClient.SqlDataReader 和自定义 CSV 序列化程序 (see here for an example) 或其他具有 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()

【讨论】:

    【解决方案7】:

    BCP 的替代选项:

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

    【讨论】:

      【解决方案8】:

      通常sqlcmd 附带bcp utility(作为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
      

      【讨论】:

      【解决方案9】:

      上面的答案几乎为我解决了,但它没有正确创建解析的 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 更容易。

      【讨论】:

        【解决方案10】:

        由于以下两个原因,您应该在 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
          

        【讨论】:

          【解决方案11】:

          你可以用一种骇人听闻的方式来做到这一点。小心使用sqlcmd hack。如果数据有双引号或逗号,你会遇到麻烦。

          您可以使用一个简单的脚本来正确地完成它:

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

          来源:Writing SQL output to CSV with VBScript.

          【讨论】:

          • 对否决票的解释会很好。我的回答是正确的:你不能用 sqlcmd 做到这一点。我还提供了另一种完成任务的方法。
          • 否决票是因为您显然可以使用 sqlcmd 完成 OP 的要求。
          • @BrianDriscoll,他并没有说一个不能sqlcmd做到这一点,我们只是在说明sqlcmd没有正确逃避逗号,因此几乎不能用于任何 严重 CSV 输出。
          • 我确实说过,但厌倦了反对票,所以我编辑了我的答案。我会让我的编辑更真实。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-03-16
          • 2010-11-30
          • 1970-01-01
          • 1970-01-01
          • 2021-04-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多