PowerShell 5.1合并DataTable后导出CSV内容不全,如何解决?
问题
环境:PowerShell 5.1
我用以下代码将两个DataTable $dt_1 和 $dt_2 合并为新的集合$merged:
$merged = $dt_1 | ForEach-Object { $dt1Row = $_ $dt2Rows = $dt_2 | Where-Object { $_"Facility" -eq $dt1Row"Facility" -and $_"Customer" -eq $dt1Row"Customer" -and $_"Contract" -eq $dt1Row"Contract" } $resultObject = $dt1Row | Select-Object "Contract", "Customer ","Contract" $Count = 1 foreach ($dt2Row in $dt2Rows) { $resultObject | Add-Member -MemberType NoteProperty -Name ("Material - $Count") -Value $dt2Row.Material $resultObject | Add-Member -MemberType NoteProperty -Name ("Material_Number - $Count") -Value $dt2Row.'Material_Number' $Count ++ } $resultObject }
其中$dt_2中每个以Facility、Customer、Contract为键的条目对应多条记录。
在控制台执行$merged | Format-Table -AutoSize时,能显示完整的目标列(包含Material - 1、Material_Number - 1等多组列),但执行$merged | Export-Csv "\Path\To\CSV.csv" -NoType -Delimiter ";" -Encoding UTF8导出CSV后,仅保留到Material - 1和Material_Number - 1列,后续列丢失。
请问该问题的原因是什么?如何实现完整导出?
原因分析
PowerShell的Export-Csv会以集合中第一个对象的属性集合作为CSV的固定表头,后续对象新增的属性会被直接忽略。
你的代码中,如果第一个生成的$resultObject只添加了Material - 1和Material_Number - 1这两组属性,那么后续对象里的Material - 2、Material_Number - 2等属性,在导出时会被丢弃——因为表头已经由第一个对象确定,Export-Csv不会再去识别后续对象的新属性。
而Format-Table能显示所有列,是因为它会遍历整个集合,收集所有对象的所有属性后再统一格式化,不会被第一个对象的属性限制。
解决方法
方法1:预先构建包含所有可能列的对象
先统计$dt_2中每个键对应的最大记录数,然后在创建$resultObject时就预先添加所有可能的Material - N和Material_Number - N属性(初始值设为$null),再填充实际数据。这样所有对象的属性集合一致,Export-Csv就能完整导出所有列:
# 先统计每个键组对应的最大记录数 $maxCount = ($dt_2 | Group-Object Facility, Customer, Contract | Select-Object -ExpandProperty Count) | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum $merged = $dt_1 | ForEach-Object { $dt1Row = $_ $dt2Rows = $dt_2 | Where-Object { $_.Facility -eq $dt1Row.Facility -and $_.Customer -eq $dt1Row.Customer -and $_.Contract -eq $dt1Row.Contract } # 用有序哈希表预先定义所有列,确保顺序一致 $resultProps = [ordered]@{ Contract = $dt1Row.Contract Customer = $dt1Row.Customer } # 循环添加所有可能的Material相关列 for ($i=1; $i -le $maxCount; $i++) { $resultProps["Material - $i"] = $null $resultProps["Material_Number - $i"] = $null } # 创建包含所有列的对象 $resultObject = New-Object -TypeName PSObject -Property $resultProps # 填充实际数据 $Count = 1 foreach ($dt2Row in $dt2Rows) { $resultObject."Material - $Count" = $dt2Row.Material $resultObject."Material_Number - $Count" = $dt2Row.'Material_Number' $Count ++ } $resultObject } # 导出CSV $merged | Export-Csv "\Path\To\CSV.csv" -NoType -Delimiter ";" -Encoding UTF8
方法2:手动指定所有需要导出的列
如果能提前确定最大的记录组数(比如最多有3组Material数据),可以直接在导出时用Select-Object列出所有需要的列,强制Export-Csv包含这些列:
# 假设最多有3组Material列,按需调整数量 $merged | Select-Object Contract, Customer, ` "Material - 1", "Material_Number - 1", ` "Material - 2", "Material_Number - 2", ` "Material - 3", "Material_Number - 3" | Export-Csv "\Path\To\CSV.csv" -NoType -Delimiter ";" -Encoding UTF8
这种方法适合已知最大记录数的场景,灵活性不如方法1。
内容的提问来源于stack exchange,提问作者Peter S

