【问题标题】:Converting data types when using Import-CSV使用 Import-CSV 时转换数据类型
【发布时间】:2020-05-02 18:06:25
【问题描述】:

我需要构建一个过程,其中包括将 CSV 文件读入 SQL Server 表。过去我使用过 SQL 批量插入,但我正在使用的 CSV 文件在某些​​字段中会包含逗号(不可预测),并且我的 sql server 版本(2016)不支持处理这些(我收集到 2017 年, FIELDQUOTE 参数解决了这个问题)。

This post 谈到了这个问题,并把我转到了this method of using Powershell with Import-CSV。我每次运行的数据集都非常小,所以“使用 PowerShell 进行简单的 CSV 导入”应该没问题。但是我对数据类型很感兴趣。

当我在下面使用此方法时,我收到错误,因为它无法将字符串转换为十进制或数据时间。但是,如果我将我的 sql 表配置为仅包含字符串类型字段,则数据会正确导入。它似乎忽略了标题行。我知道 Import-CSV 默认使用第一行作为标题,但我在目标表中得到的(当所有字段都设置为 varchar(n) 时)只有第 2 行及以后。

有没有办法让我使用这种方法,但将字段设置为正确的格式,以便它们可以进入正确定义的 sql 表?如果必须,我可以使用它作为我让它工作(所有目标列都设置为 varchar(n)),但我希望正确定义 sql 列。我的 CSV 标题名称是可预测的,并且我知道我希望每个列数据类型是什么。许多将保留为字符串,但有些需要更改为十进制(19,4)或日期时间。我想我正在其他地方阅读,在那里我可以输入 where-object 命令或其他东西,但我是一个 PS 新手,正在努力寻找正确的语法。

$ConnectionString = "Data Source=<myServer>; Database=<myDatabase>;Trusted_Connection=True;";
. "E:\Out-DataTable.ps1"

$csvDataTable = Import-CSV -Path "<sourceCsvFile>" | Out-DataTable
$bulkCopy = new-object ("Data.SqlClient.SqlBulkCopy") $ConnectionString
$bulkCopy.DestinationTableName = "<myDestinationTable>"
$bulkCopy.WriteToServer($csvDataTable)

【问题讨论】:

  • CSV 中的日期是否有特定格式?
  • 请考虑对您认为有帮助的任何答案进行投票,或者如果它解决了您的问题,请接受答案(通过复选框)。
  • 我的日期/时间字段的格式:2020 年 4 月 17 日 20:32。谢谢回复。我会玩它并报告。

标签: powershell csv


【解决方案1】:

关于您的主要问题:“我有没有办法使用这种方法,但将字段设置为正确的格式,以便它们可以进入正确定义的 sql 表?”是的。 p>

可以在创建 DataTable 之前将值转换为所需的类型(下面的示例代码)。就个人而言,我可能会尝试将 CSV 文件替换为对类型信息进行编码的内容,例如 JSON 或 parquet。


根据 OP 的问题更新:

我必须拼出该循环中的每个字段,还是只拼出需要从字符串更改为其他内容的字段? 您必须将每个字段映射到[PSCustomObject],但您只需要转换包含非字符串数据的字段。

对于有空格的列标题,在 PS 中引用它们时是否用括号括起来(如 [Created on Date]) 我调整了答案以说明一种处理带有空格的列标题的方法。请参见“e e”列。

我看到您的示例将 0.2 类型数字转换为“double”。 我的十进制数据将是货币,并将与其他数据一起使用,以便我将其设置为十进制。您是否期望这会正常流动,或者是否有不同的正确语法可以转换为十进制? 根据下面的参考,.NET 的十进制类型应该映射到 SQL Server 的十进制类型。请参阅示例数据中的属性 f。 https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql-server-data-type-mappings



假设您在 CSV 中有以下数据

a,b,c,d,e e,f
string1,1,0.1,2020-05-02 14:29:06.780,e,1.99
string2,2,0.2,2020-05-03 01:01:02.000,e,3.59


您可以使用类似于下面foreach 循环中的方法将值转换为所需的类型。显然,您必须对其进行自定义以适合您的实际数据。

param(
    $datasource = 'localhost',
    $db = 'test',
    $destTable = 'table',
    $csvPath = "$PSScriptRoot\data.csv"
)

$ConnectionString = "Data Source=$datasource; Database=$db;Trusted_Connection=True;";
. "$PSScriptRoot\Out-DataTable.ps1"

$rawData = Import-CSV -Path $csvPath
$typedData = @()
foreach ($row in $rawData) {
    $tempObj = [PSCustomObject] @{
        a = $row.a
        b = [int] $row.b
        c = [double] $row.c
        d = [datetime] $row.d
        e_e = $row."e e"
        f = [decimal] $row.f
    }
    $typedData += $tempObj
}

$csvDataTable = $typedData | Out-DataTable
$bulkCopy = new-object ("Data.SqlClient.SqlBulkCopy") $ConnectionString
$bulkCopy.DestinationTableName = $destTable
$bulkCopy.WriteToServer($csvDataTable)

Out-DataTable.ps1 的内容(供未来读者使用)

####################### 
function Get-Type { 
    param($type) 

    $types = @( 
        'System.Boolean', 
        'System.Byte[]', 
        'System.Byte', 
        'System.Char', 
        'System.Datetime', 
        'System.Decimal', 
        'System.Double', 
        'System.Guid', 
        'System.Int16', 
        'System.Int32', 
        'System.Int64', 
        'System.Single', 
        'System.UInt16', 
        'System.UInt32', 
        'System.UInt64') 

    if ( $types -contains $type ) { 
        Write-Output "$type" 
    } 
    else { 
        Write-Output 'System.String' 

    } 
} #Get-Type 

####################### 
<# 
.SYNOPSIS 
Creates a DataTable for an object 
.DESCRIPTION 
Creates a DataTable based on an objects properties. 
.INPUTS 
Object 
    Any object can be piped to Out-DataTable 
.OUTPUTS 
   System.Data.DataTable 
.EXAMPLE 
$dt = Get-psdrive| Out-DataTable 
This example creates a DataTable from the properties of Get-psdrive and assigns output to $dt variable 
.NOTES 
Adapted from script by Marc van Orsouw see link 
Version History 
v1.0  - Chad Miller - Initial Release 
v1.1  - Chad Miller - Fixed Issue with Properties 
v1.2  - Chad Miller - Added setting column datatype by property as suggested by emp0 
v1.3  - Chad Miller - Corrected issue with setting datatype on empty properties 
v1.4  - Chad Miller - Corrected issue with DBNull 
v1.5  - Chad Miller - Updated example 
v1.6  - Chad Miller - Added column datatype logic with default to string 
v1.7 - Chad Miller - Fixed issue with IsArray 
.LINK 
http://thepowershellguy.com/blogs/posh/archive/2007/01/21/powershell-gui-scripblock-monitor-script.aspx 
#> 
function Out-DataTable { 
    [CmdletBinding()] 
    param([Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] [PSObject[]]$InputObject) 

    Begin { 
        $dt = new-object Data.datatable   
        $First = $true  
    } 
    Process { 
        foreach ($object in $InputObject) { 
            $DR = $DT.NewRow()   
            foreach ($property in $object.PsObject.get_properties()) {   
                if ($first) {   
                    $Col = new-object Data.DataColumn   
                    $Col.ColumnName = $property.Name.ToString()   
                    if ($property.value) { 
                        if ($property.value -isnot [System.DBNull]) { 
                            $Col.DataType = [System.Type]::GetType("$(Get-Type $property.TypeNameOfValue)") 
                        } 
                    } 
                    $DT.Columns.Add($Col) 
                }   
                if ($property.Gettype().IsArray) { 
                    $DR.Item($property.Name) = $property.value | ConvertTo-XML -AS String -NoTypeInformation -Depth 1 
                }   
                else { 
                    $DR.Item($property.Name) = $property.value 
                } 
            }   
            $DT.Rows.Add($DR)   
            $First = $false 
        } 
    }  

    End { 
        Write-Output @(, ($dt)) 
    } 

} #Out-DataTable

【讨论】:

  • 请注意,使用 += 迭代构建数组效率低下,并且随着数据集的增加而变得昂贵。
  • Derek,我是否必须拼出该循环中的每个字段,或者只是需要从字符串更改为其他内容的字段(默认情况下,它们使用 Import-Csv 作为字符串开始)。对于有空格的列标题,在 PS 中引用它们时,我是否将它们括在括号中(例如 [Created on Date])。最后,我看到您的示例将 0.2 类型数字转换为“double”。我的十进制数据将是货币,并将与其他数据一起使用,以便我将其设置为十进制。您是否期望这会正常流动,或者是否有不同的正确语法可以转换为十进制?谢谢!
  • @TimMiller,我更新了答案以回应您最近评论中的问题。
  • 谢谢。对于许多行,我的几个日期时间字段将为空白,这会导致问题。 (无法将值“”转换为类型“System.DateTime”。错误:“字符串未被识别为有效的日期时间。”)收到了几个类似的响应。对于许多十进制字段也是如此,但我没有收到任何与转换为十进制有关的错误。在其中几个之后,我看到了很多,我认为这只是第一个问题的结果(Out-DataTable : Cannot bind argument to parameter 'InputObject' because it is null.)。
  • @TimMiller,您可以这样做来处理这种情况:d = if ($null -eq $row.d -or $row.d -eq '') { Get-Date -Year 0001 -Month 01 -Day 01 } else { [datetime] $row.d } 您可能希望更新传递给Get-Date 的参数以处理缺失值。 Powershell 5.1及以下没有合适的三元运算,不过这个基本一样。
猜你喜欢
  • 2017-10-19
  • 2018-01-26
  • 2016-07-28
  • 2019-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-28
  • 2021-05-12
相关资源
最近更新 更多