在powershell中快速比较两个大的csv(行和列)
Posted
技术标签:
【中文标题】在powershell中快速比较两个大的csv(行和列)【英文标题】:Fast compare two large csv(boths rows and columns) in powershell 【发布时间】:2022-01-20 10:10:56 【问题描述】:我有两个大的 CSV 文件要比较。 Bosth csvs 基本上是相隔 1 天来自同一系统的数据。行数约为 12k,列数约为 30。
目的是识别主键(#ID)更改了哪些列数据。
我的想法是遍历 CSV 以识别哪些行已更改并将这些行转储到单独的 csv 中。完成后,我再次遍历更改行,并确定列中的确切更改。
NewCSV = Import-Csv -Path ".\Data_A.csv"
OldCSV = Import-Csv -Path ".\Data_B.csv"
foreach ($LineNew in $NewCSV)
ForEach ($LineOld in $OldCSV)
If($LineNew -eq $LineOld)
Write-Host $LineNew, " Match"
else
Write-Host $LineNew, " Not Match"
但是一旦运行循环,就需要永远运行 12k 行。我希望必须有一种更有效的方法来比较大文件 powershell。更快的东西。
【问题讨论】:
您只关心$NewCsv
上的“发生了什么变化” 与$OldCsv
或并排比较?另外,ID
在两个 CSV 上是否都有唯一值?
我想知道特定 ID 的更改列的旧值和新值
Compare-Object (Get-content Data_A.csv) (Get-Content Data_B.csv) 怎么样?
为什么需要永远:通过将 oldcsv 中的每一行与 newcsv 的每一行进行比较,进行 12k*12k 次比较,因此大约有 1.44 亿次操作。
这是一个安静的经典问题。使用这个Join-Object script
/Join-Object Module
(另见:In Powershell, what's the best way to join two tables into one?):Import-Csv .\Data_A.csv |Join (Import-Csv .\Data_B.csv) -on ID -Name 'A.', 'B.'
【参考方案1】:
好吧,您可以尝试一下,我并不是说对于 vonPryz 已经指出的内容会很快,但它应该为您提供一个很好的并排视角来比较从 OldCsv 到新CSV。
注意:在两个 CSV 上具有相同值的单元格将被忽略。
$NewCSV = Import-Csv -Path ".\Data_A.csv"
$OldCSV = Import-Csv -Path ".\Data_B.csv" | Group-Object ID -AsHashTable -AsString
$properties = $newCsv[0].PSObject.Properties.Name
$result = foreach($line in $NewCSV)
if($ref = $OldCSV[$line.ID])
foreach($prop in $properties)
if($line.$prop -ne $ref.$prop)
[pscustomobject]@
ID = $line.ID
Property = $prop
OldValue = $ref.$prop
NewValue = $line.$prop
continue
Write-Warning "ID $($line.ID) could not be found on Old Csv!!"
【讨论】:
这个解决方案比我自己尝试的解决方案快得多。但是有一个问题,它随机读取一些值作为System.Object[]
,因此有时会显示数据不匹配。
@misguided 没有查看实际的 CSV 我不知道这怎么可能
我可以看到它返回 Object[]
的唯一可能方法是,如果 OldCsv 或 NewCsv 在 ID 列上没有唯一值,您已经提到这些值是唯一的,如果不是这种情况那么它应该已经被澄清了。
太棒了...这就是原因。我回去对数据进行了更深入的挖掘,并看到了一些重复项,正如您所说,这些重复项导致了问题。我现在已经删除了它们,它工作正常。比较 2 个 csv 文件,每个文件有 12k 行,不到一分钟。
@misguided 很高兴知道,很高兴它起作用了 :)【参考方案2】:
作为vonPryz hints in the comments,您编写了一个具有二次时间复杂度的算法(O(n²)
采用 Big-O 表示法) - 每次输入大小翻倍,执行的计算次数就会增加 4 -折叠。
为避免这种情况,我建议使用哈希表或其他字典类型来保存每个数据集,并使用输入中的主键作为字典键。通过这种方式,您可以获得相应记录的恒定时间查找,并且算法的时间复杂度变得接近线性 (O(2n + k)
):
$NewCSV = @
Import-Csv -Path ".\Data_A.csv" |ForEach-Object
$NewCSV[$_.ID] = $_
$OldCSV = @
Import-Csv -Path ".\Data_B.csv" |ForEach-Object
$OldCSV[$_.ID] = $_
现在我们可以通过 ID 有效地解析每一行,我们可以通过一个独立的循环检查整个数据集:
foreach($entry in $NewCSV.GetEnumerator())
if(-not $OldCSV.ContainsKey($entry.Key))
# $entry.Value is a new row, not seen in the old data set
$newRow = $entry.Value
$oldRow = $OldCSV[$entry.Key]
# do the individual comparison of the rows here
像上面一样执行另一个循环,但用$NewCSV
代替$OldCSV
来查找/检测删除。
【讨论】:
您不能在空值表达式上调用方法。在 C:\Data\FIle.PS1:39 char:19 + foreach($entry in $NewCSV.GetEnumerator()) + ~~~~~~~~~~~~~~~~~~~~~ ~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNull @misguided 您是否在我的答案中运行了第二个代码块而没有运行第一个代码块? :) 第一行,$NewCSV = @
,为$NewCSV
分配了一个哈希表,所以如果你按顺序运行它们,就不会出现该错误
我确实做到了。但仍然得到错误。数据集在某些列中有一些空值(不是主键)。这可能是个问题吗?
不,Import-Csv
会吐出空字符串而不是空值。此外,无论 CSV 是否为空,$NewCSV
变量仍然存在。
这是做什么的? ForEach-Object $NewCSV[$_.ID] = $_ ???正常导入 csv 加载数据正常。当我使用此代码对其进行管道传输时,该数组为空白。我假设 ID 是“主键”列名?以上是关于在powershell中快速比较两个大的csv(行和列)的主要内容,如果未能解决你的问题,请参考以下文章