【问题标题】:Compare two CSV file using PowerShell and return matching values faster使用 PowerShell 比较两个 CSV 文件并更快地返回匹配值
【发布时间】:2018-03-29 15:42:47
【问题描述】:

我使用此代码匹配两个 CSV 文件并获取我需要的列 在这段代码中,我比较了MatriculenameFirstname 的数据,当我得到匹配时,我可以检索到“IGG”列

但是很慢……(18行20分钟)

有人可以帮助我吗?

这是我的代码:

foreach ($item in $fileContentIMM) 
{
    try
    {
        $Matricule = $item.'Matricule'
        $name = $item.'Nom'
        $firstname = $item.'Prenom'

        # find first matching row in $$fileContentMagic using wildcard
        $objMatch = $fileContentMagic | where { $_.'Matricule' -eq $Matricule -and $_.'NOM' -eq $name -and $_.'PRENOM' -eq $firstname}


        ##### check if any match found 
        if ($objMatch -eq $null)
        {
            $item  | ForEach-Object {
                $filechecktrue += [pscustomobject]@{
                    'MATRICULE' = $item.'Matricule'
                    'IGG' = 'noSet'
                    'NAME'  = $item.'Nom'
                    'FIRSTNAME' = $item.'Prenom'
                    'SERVICE' = $item.'Service'
                    'Immeuble'= $item.'Immeuble' 
                    'Niveau' = $item.'Niveau'
                    'Loc.' = $item.'Loc.'
                    'PDT' = $item.'PDT'
                    'Occ.' = $item.'Occ.'
                    'Site' = $item.'Site'
                }
            }
        }
        else
        {
            $item  | ForEach-Object {
                $filechecktrue += [pscustomobject]@{
                    'MATRICULE' = $item.'Matricule'
                    'IGG' = ($objMatch.'IGG' -join '/')
                    'NAME'  = $item.'Nom'
                    'FIRSTNAME' = $item.'Prenom'
                    'SERVICE' = $item.'Service'
                    'Immeuble'= $item.'Immeuble' 
                    'Niveau' = $item.'Niveau'
                    'Loc.' = $item.'Loc.'
                    'PDT' = $item.'PDT'
                    'Occ.' = $item.'Occ.'
                    'Site' = $item.'Site'
                }
            }

        }
    }
    catch
    {
        "ERROR: Problem reading line - skipping :" | Out-File $LogFile -Append -Force
        $item.nom + $item.prenom + $item.service| Out-File $LogFile -Append -Force
    }
}

【问题讨论】:

  • 20分钟找出18行有多少?你看过Compare-Object吗?
  • 你确定这是慢的部分吗?两个 csv 文件有多大?您是否测量或使用过 ex。 Write-Host "import done"确定不是读取慢的文件?
  • 文件 contentIMM 包含 18 行和 filecontentMagic 45000
  • 也许我要去看看谢谢

标签: performance powershell csv


【解决方案1】:

我会读取您用于查找的文件,然后为此创建一个 HashTable。 HashTables 对于查找非常有效。

假设您在FileContentMagic 中没有任何重复项,请尝试这样的操作:

# Use any character here which is guaranteed not to be present in the Matricule, Nom,
# or Prenom fields
$Delimiter = '|'

# Read the FileContent Magic into a HashTable for fast lookups
# The key is Matricule|Nom|Prenom
# The value is IGG joined with a forward slash
$FileContentMagic = @{}
Import-Csv -Path $FileContentMagicFileName | ForEach-Object {
    # Here we build our lookup key. The Trim() is just in case there's any leading or trailing
    # whitespace You can leave it out if you know you don't need it
    $Key = $_.Matricule.Trim(), $_.Nom.Trim(), $_.Prenom.Trim() -join $Delimiter

    # Since we only need the IGG value joined with a /, we'll just keep that
    $Value = $_.IGG -join '/'
    $FileContentMagic.Add($Key, $Value)
}

$FileContentIMM = Import-Csv -Path $FileContentIMMFileName

$FileCheckTrue = foreach ($item in $FileContentIMM) {
    $Key = $_.Matricule.Trim(), $_.Nom.Trim(), $_.Prenom.Trim() -join $Delimiter

    [PSCustomObject]@{
        'MATRICULE' = $item.'Matricule'
        'IGG'       = if ($FileContentMagic.ContainsKey($Key)) { $FileContentMagic[$Key] } else { 'noSet' }
        'NAME'      = $item.'Nom'
        'FIRSTNAME' = $item.'Prenom'
        'SERVICE'   = $item.'Service'
        'Immeuble'  = $item.'Immeuble' 
        'Niveau'    = $item.'Niveau'
        'Loc.'      = $item.'Loc.'
        'PDT'       = $item.'PDT'
        'Occ.'      = $item.'Occ.'
        'Site'      = $item.'Site'
    }
}

此外,任何时候您使用+= 连接一个数组,都会导致显着的性能损失。避免使用它是值得的,因为每个赋值都会创建一个新数组,用新项复制整个数组,然后丢弃旧数组。效率很低。

如果$FileContentMagic 包含重复键,则应更改 HashTable 的加载方式:

$FileContentMagic = @{}
Import-Csv -Path $FileContentMagicFileName | ForEach-Object {
    $Key = $_.Matricule.Trim(), $_.Nom.Trim(), $_.Prenom.Trim() -join $Delimiter
    if (!$FileContentMagic.ContainsKey($Key)) {
        $Value = $_.IGG -join '/'
        $FileContentMagic.Add($Key, $Value)
    }
    else {
        $FileContentMagic[$Key] += '/' + ($_.IGG -join '/')
    }
}

【讨论】:

  • 谢谢我试试这个,我把时间除以 3!但是如果我需要比 'IGG' 更多的值,我该怎么做呢?
  • 更改为 $Value = $_ 并更新其余部分以从哈希表返回的对象访问 igg 属性。这是一个简单的更改,您应该能够自己修复。我不建议运行你不理解的代码。
【解决方案2】:

我会简化这一点,但更改不会对处理时间造成太大影响。我所做的唯一优化是将 $filechecktrue 更改为更节省内存的 List。

不确定这是否真的是您脚本的慢速部分。这需要$fileContentMagic 是一个非常大的数组。

$filechecktrue = New-Object System.Collections.ArrayList

foreach ($item in $fileContentIMM) 
{
    try
    {
        $Matricule = $item.'Matricule'
        $name = $item.'Nom'
        $firstname = $item.'Prenom'

        # find first matching row in $fileContentMagic using wildcard
        $objMatch = $fileContentMagic | Where-Object { $_.'Matricule' -eq $Matricule -and $_.'NOM' -eq $name -and $_.'PRENOM' -eq $firstname}

        #Create results object with common properties
        $o += [pscustomobject]@{
            'MATRICULE' = $item.'Matricule'
            'IGG' = 'noSet'
            'NAME'  = $item.'Nom'
            'FIRSTNAME' = $item.'Prenom'
            'SERVICE' = $item.'Service'
            'Immeuble'= $item.'Immeuble' 
            'Niveau' = $item.'Niveau'
            'Loc.' = $item.'Loc.'
            'PDT' = $item.'PDT'
            'Occ.' = $item.'Occ.'
            'Site' = $item.'Site'
        }

        ##### check if any match found 
        if ($objMatch)
        {
            #if not null, set IGG value. No need for foreach as $item is already a "foreach-value".
            $o.IGG = ($objMatch.'IGG' -join '/')
        }

        #Add result to arraylist
        $filechecktrue.Add($o)
    }
    catch
    {
        "ERROR: Problem reading line - skipping :" | Out-File $LogFile -Append -Force
        $item.nom + $item.prenom + $item.service| Out-File $LogFile -Append -Force
    }
}

【讨论】:

  • 是的,这并没有影响处理时间,谢谢;)
【解决方案3】:

您的第一个 foreach 在每次迭代时都会返回一个 $item-object,因此再次在代码块内的 $item 上使用 foreach 是无稽之谈(两次)。

试试这个(删除冗余):

foreach ($item in $fileContentIMM) {
    try {
        # find first matching row in $fileContentMagic using wildcard
        $objMatch = $fileContentMagic | where { $_.'Matricule' eq $item.'Matricule'
                                           -and $_.'NOM' -eq $item.'Nom'
                                           -and $_.'PRENOM' -eq $item.'Prenom'}


        ##### check if any match found 
        if ($objMatch -eq $null) {
            $IGG = 'noSet'
        } else {
            $IGG = ($objMatch.'IGG' -join '/')
        }
        $filechecktrue += [pscustomobject]@{
            'MATRICULE' = $item.'Matricule'
            'IGG' = $IGG
            'NAME'  = $item.'Nom'
            'FIRSTNAME' = $item.'Prenom'
            'SERVICE' = $item.'Service'
            'Immeuble'= $item.'Immeuble' 
            'Niveau' = $item.'Niveau'
            'Loc.' = $item.'Loc.'
            'PDT' = $item.'PDT'
            'Occ.' = $item.'Occ.'
            'Site' = $item.'Site'

    } catch {
        "ERROR: Problem reading line - skipping :" | Out-File $LogFile -Append -Force
        $item.nom + $item.prenom + $item.service| Out-File $LogFile -Append -Force
    }
}

【讨论】:

  • 是的,我只节省了几秒钟,但你是对的,谢谢:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-02-21
  • 1970-01-01
  • 2021-08-07
  • 1970-01-01
  • 2023-03-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多