在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(行和列)的主要内容,如果未能解决你的问题,请参考以下文章

PowerShell 学习笔记——管道

使用hashmap或hashset比较大的csv文件

使用powershell在一个csv列中查找并替换

在 powershell 中仅提取 csv 文件的前 10 行

如何使用 Python 比较 2 个非常大的矩阵

PowerShell - 从 csv 文件读取数据,比较特定列中的数据并将结果写入新文件