【问题标题】:Compare Two CSVs, match the columns on 2 or more Columns, export specific columns from both csvs with powershell比较两个 CSV,匹配 2 个或更多列上的列,使用 powershell 从两个 csv 导出特定列
【发布时间】:2018-09-07 22:22:22
【问题描述】:

我有 2 个 CSV

left.csv

Ref_ID,First_Name,Last_Name,DOB
321364060,User1,Micah,11/01/1969
946497594,User2,Acker,05/28/1960
887327716,User3,Aco,06/26/1950
588496260,User4,John,05/23/1960
565465465,User5,Jack,07/08/2020

正确的.csv

First_Name,Last_Name,DOB,City,Document_Type,Filename
User1,Micah,11/01/1969,Parker,Transcript,T4IJZSYO.pdf
User2,Acker,05/28/1960,,Transcript,R4IKTRYN.pdf
User3,Aco,06/26/1950,,Transcript,R4IKTHMK.pdf
User4,John,05/23/1960,,Letter,R4IKTHSL.pdf

最终结果:

组合.csv

Ref_ID,First_Name,Last_Name,DOB,Document_Type,Filename
321364060,User1,Micah,11/01/1969,Parker,Transcript,T4IJZSYO.pdf
946497594,User2,Acker,05/28/1960,Transcript,R4IKTRYN.pdf
887327716,User3,Aco,06/26/1950,Transcript,R4IKTHMK.pdf
588496260,User4,John,05/23/1960,Letter,R4IKTHSL.pdf

我需要在 First_Name,Last_Name,DOB 上匹配它们,然后返回 Ref_ID、first_name、last_name、DOB 从 left.csv 和 Document_Type,Filename 从 right.csv

使用 Compare-Object: 仅返回来自其中一个 csv 的列,而不是来自两者的列。

使用join-object:这是我很大的希望,但这只能让我匹配一个列,我需要匹配多个列(不知道如何做多个)

我不知道从哪里开始,欢迎提出建议。

【问题讨论】:

    标签: powershell csv


    【解决方案1】:
    $left = Import-Csv C:\left.csv
    $right = Import-Csv C:\right.csv
    
    Compare-Object -ReferenceObject $left -DifferenceObject $right -Property First_Name,Last_Name,DOB -IncludeEqual -ExcludeDifferent | 
        ForEach-Object {
            $iItem = $_
            $ileft = $left.Where({$_.First_Name -eq $iItem.First_Name -and $_.Last_Name -eq $iItem.Last_Name -and$_.DOB -eq $iItem.DOB})
            $iright = $right.Where({$_.First_Name -eq $iItem.First_Name -and $_.Last_Name -eq $iItem.Last_Name -and$_.DOB -eq $iItem.DOB})
            [pscustomobject]@{
                Ref_ID=$ileft.Ref_ID
                first_name=$ileft.first_name
                last_name=$ileft.last_name
                DOB=$ileft.DOB
                Document_Type=$iright.Document_Type
                Filename=$iright.Filename
            }
        } | Export-Csv C:\Combined.csv -NoTypeInformation
    

    【讨论】:

    • 做得很好,只是做了几件事。使用-Passthru 将匹配的对象沿管道传递到ForEach 循环。然后在循环中再次使用Compare-Object 以另一种方式获取右侧对象,并将缺少的属性添加到您传递的左侧对象中。然后Select * -Exclude SideIndicator,然后再导出为 CSV。大量缩短以适应,但这有效代码的下一条评论
    • Compare $left $right -Prop First_Name,Last_Name,DOB -Incl -Excl -PassThru|%{$iRight = Compare $right $_ -Property First_Name,Last_Name,DOB -Incl -Excl -PassThru;$_|Add-Member 'Document_Type' $iRight.Document_Type;$_|Add-Member 'FileName' $iRight.FileName -PassThru}|Select * -Exclude SideIndicator|Export-Csv C:\Combined.csv -NoType
    • @TheMadTechnician 你能把你的代码放在答案中吗?我无法完全弄清楚您通过 cmets 所做的所有更改。
    • @Nas 这看起来很棒,易于阅读,并且非常适合我可能需要的未来更改。明天试一下,如果有效,请将其标记为已接受的答案
    • 给了你正确的答案,这很容易阅读,容易适应,并且它支持多列,即使标题不匹配。很棒的作品
    【解决方案2】:

    已经有一些很好的答案了,还有一个。

    将您的无数对象导入到单个(dis)数组中:

    $left = @"
    Ref_ID,First_Name,Last_Name,DOB
    321364060,User1,Micah,11/01/1969
    946497594,User2,Acker,05/28/1960
    887327716,User3,Aco,06/26/1950
    588496260,User4,John,05/23/1960
    565465465,User5,Jack,07/08/2020
    "@
    
    $right = @"
    First_Name,Last_Name,DOB,City,Document_Type,Filename
    User1,Micah,11/01/1969,Parker,Transcript,T4IJZSYO.pdf
    User2,Acker,05/28/1960,,Transcript,R4IKTRYN.pdf
    User3,Aco,06/26/1950,,Transcript,R4IKTHMK.pdf
    User4,John,05/23/1960,,Letter,R4IKTHSL.pdf
    "@
    
    $disarray = @(
        $left | ConvertFrom-Csv 
        $right | ConvertFrom-Csv
    )
    

    使用Group-Object 将它们组织成具有相同键值的组:

    $keyProps = @('First_Name', 'Last_name', 'DOB')
    $disarray | 
        Group-Object -Property $keyProps | 
        Where-Object Count -gt 1 |
    

    然后合并对象,将任何缺失的属性添加到输出$mergedObject

        ForEach-Object {
            $mergedObject = $_.group[0]
            foreach ($obj in $_.group[1..($_.group.count-1)]) {
                $newProps = ($obj | Get-Member -MemberType NoteProperty).name | 
                    Where-Object {
                        $_ -notin ($mergedobject | Get-Member -MemberType NoteProperty).name
                    } 
                foreach ($propName in $newProps) {
                    $mergedObject | Add-Member -MemberType NoteProperty -Name $propName -Value $obj.$propName -Force
                }
            }
            Write-Output $mergedObject
        }
    

    这与您已有的答案没有太大区别,但消除“左”“右”的区别可能会有所帮助;上面的代码应该处理三个​​或更多的源投入$disarray,合并所有包含相同$keyProps的对象。

    请注意,有一些极端情况需要考虑。例如,如果一个对象对用户具有“City=Chigago”而另一个对象具有“City=New York”,会发生什么情况?

    【讨论】:

    • 有没有办法在不同的行中返回两个值?就像芝加哥的一个和纽约的一个?如果文件 A 有三行代表同一个人和不同的文件名,我最喜欢,但文件 B 只有一次名称。如何返回所有三行?
    【解决方案3】:

    您可以从每个 csv 创建自己的密钥,然后使用此密钥从每个 csv 添加到新的哈希表。

    在调试器(ISE 或 VSCode)中逐步完成,并根据您的需要对其进行定制... 根据数据的完整性,根据需要添加适当的错误检查。 下面的一些语句仅用于调试,因此您可以检查它运行时发生的情况。

    # Ref_ID,First_Name,Last_Name,DOB
    $csv1 = @'
    321364060,User1,Micah,11/01/1969
    946497594,User2,Acker,05/28/1960
    887327716,User3,Aco,06/26/1950
    588496260,User4,John,05/23/1960
    565465465,User5,Jack,07/08/2020
    '@
    
    # First_Name,Last_Name,DOB,City,Document_Type,Filename
    $csv2 = @'
    User1,Micah,11/01/1969,Parker,Transcript,T4IJZSYO.pdf
    User2,Acker,05/28/1960,,Transcript,R4IKTRYN.pdf
    User3,Aco,06/26/1950,,Transcript,R4IKTHMK.pdf
    User4,John,05/23/1960,,Letter,R4IKTHSL.pdf
    '@
    
    # hashtable
    $data = @{}
    
    $c1 = $csv1 -split "`r`n"
    $c1.count
    
    foreach ($item in $c1)
    {
        $fields = $item -split ','
        $key = $fields[1]+$fields[2]+$fields[3]
        $key
    
        # add new hashtable for given key
        $data.Add($key, [ordered]@{})
    
        # add data from c1 to the hashtable
        $data[$key].ID = $fields[0]
        $data[$key].First = $fields[1]
        $data[$key].Last = $fields[2]
        $data[$key].DOB = $fields[3]
    }
    
    $c2 = $csv2 -split "`r`n"
    $c2.count
    
    foreach ($item in $c2)
    {
        $fields = $item -split ','
        $key = $fields[0]+$fields[1]+$fields[2]
        $key
    
        # add data from c2 to the hashtable
        $data[$key].Type = $fields[4]
        $data[$key].FileName = $fields[5]
    }
    
    $data.Count
    
    foreach ($key in $data.Keys)
    {
        '====================='
        $data[$key]
    }
    

    【讨论】:

    • 上面的csv数据是不是仅供参考?意思是我可以改用 $csv1 = import-csv left.csv 吗?
    • 还有什么部分匹配公共列?试图确保我完全掌握这一点。
    【解决方案4】:

    试试这个Join-Object
    除了基于多列的连接之外,它还有更多功能:

    $Left = ConvertFrom-Csv @"
    Ref_ID,First_Name,Last_Name,DOB
    321364060,User1,Micah,11/01/1969
    946497594,User2,Acker,05/28/1960
    887327716,User3,Aco,06/26/1950
    588496260,User4,John,05/23/1960
    565465465,User5,Jack,07/08/2020
    "@
    
    $Right = ConvertFrom-Csv @"
    First_Name,Last_Name,DOB,City,Document_Type,Filename
    User1,Micah,11/01/1969,Parker,Transcript,T4IJZSYO.pdf
    User2,Acker,05/28/1960,,Transcript,R4IKTRYN.pdf
    User3,Aco,06/26/1950,,Transcript,R4IKTHMK.pdf
    User4,John,05/23/1960,,Letter,R4IKTHSL.pdf
    "@
    
    $Left | Join $Right `
        -On First_Name, Last_Name, DOB `
        -Property Ref_ID, Filename, First_Name, DOB, Last_Name `
        | Format-Table
    
    Last_Name    Ref_ID DOB                    Filename     First_Name
    ---------    ------ ---                    --------     ----------
    Micah     321364060 1969-11-01 12:00:00 AM T4IJZSYO.pdf User1
    Acker     946497594 1960-05-28 12:00:00 AM R4IKTRYN.pdf User2
    Aco       887327716 1950-06-26 12:00:00 AM R4IKTHMK.pdf User3
    John      588496260 1960-05-23 12:00:00 AM R4IKTHSL.pdf User4
    

    【讨论】:

    • 您是绅士和学者......并且是该模块的所有者,非常感谢。
    • 如果列标题名称不匹配怎么办?就像一个是“First”,另一个是“First_Name”。在 sql 我只会做 L.First = R.First_Name
    • 我导入了脚本,我正在尝试执行以下命令: $L = Import-Csv 'c:\Left.csv' $R = import-Csv 'c:\Right.csv' $L |加入 $R 名、姓、出生日期 |选择对象 Ref_ID、First_Name、Last_Name、DOB、Document_Type、文件名,但我没有得到任何结果。我确保标题匹配并且文件格式相同,但仍然没有结果。只需使用一个简单的比较对象,我就会得到几个结果。
    • 第一个问题:对于单列匹配可以使用-Equal参数,例如:$Left | Join $Right First -Equals First_Name。对于名称不匹配的多列匹配,您可以使用表达式:$Left | Join $Right {$Left.First -eq $Right.First_Name -and $Left.Last_Name -eq $Right.Last_Name -and $Left.DOB -eq $Right.DOB}。第二个 (-On 别名 -Using) 参数类似于 SQL 参数,如果您提供表达式,它的作用类似于 SQL Using 子句。 请注意,使用表达式非常昂贵,并且可能会影响性能。
    • 对于第二个问题,我已经更改了答案以反映您特定的表名和标题,不包括您的文件内容。 (如果您能找到差异,我建议您也这样做,并使用ConvertFrom-Csv cmdlet 更改您的问题以反映您的测试数据)
    【解决方案5】:

    添加我找到的答案:

    $left = Import-Csv .\left.csv
    $right = Import-Csv .\right.csv
    
    $right | foreach { 
        $r = $_; 
        $left | where{ $_.First_Name -eq $r.First_Name -and $_.Last_Name -eq $r.Last_Name -and $_.DOB -eq $r.DOB } | 
            select Ref_Id, 
                First_Name, 
                Last_Name, 
                DOB, 
                @{Name="City";Expression={$r.City}}, 
                @{Name="Document_Type";Expression={$r.Document_Type}}, 
                @{Name="FileName";Expression={$r.FileName}}
    } | format-table
    

    【讨论】:

    • 这是一个易于编写和理解的答案,但是对于大型数据集,运行时复杂性会很差。代码为$right 的每个元素迭代了所有$left 元素,导致当左右大小相同时N^2 复杂度,N。
    • @veefu,这是我的想法之一,您认为 powershell 中的大型数据集是什么?几百行还是几千行?你有没有像这样干净简单的东西没有这个问题的建议?我喜欢这个解决方案,因为如果我有多个具有不同文件名的同一用户的记录,并且只有一个具有 ref_id 的记录,它仍然匹配并输出具有正确 ref_id 和不同文件名的每一行。
    • 我无法确定什么是“大”数据集;这取决于硬件。我也不知道开销 powershell 会增加空间或时间要求。要对此务实,请使用Measure-Command 使用示例数据集来衡量脚本的性能。如果性能达不到要求,请将代码替换为提供的其他答案之一;他们肯定都会表现得更好。你喜欢这个答案(ref_idfilename 的东西)当然可以在更有效的答案之一中复制。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-28
    • 1970-01-01
    • 2014-02-21
    • 2021-04-27
    • 1970-01-01
    • 1970-01-01
    • 2013-06-22
    相关资源
    最近更新 更多