Какой оператор обеспечивает более быстрый вывод -match -contains или Where-Object для больших файлов CSV - PullRequest
0 голосов
/ 18 октября 2019

Я пытаюсь построить логику, в которой мне нужно запросить 4 больших файла CSV против 1 файла CSV. В частности, поиск объекта AD для 4 доменов и сохранение их в переменной для сравнения атрибутов.

Я попытался импортировать все файлы в разных переменных и использовал ниже 3 разных кодов для получения желаемого результата. Но для его завершения требуется больше времени, чем ожидалось.

Импорт CSV:

$AllMainFile = Import-csv c:\AllData.csv
#Input file contains below
EmployeeNumber,Name,Domain
Z001,ABC,Test.com
Z002,DEF,Test.com
Z003,GHI,Test1.com
Z001,ABC,Test2.com


$AAA = Import-csv c:\AAA.csv
#Input file contains below
EmployeeNumber,Name,Domain
Z001,ABC,Test.com
Z002,DEF,Test.com
Z003,GHI,Test1.com
Z001,ABC,Test2.com
Z004,JKL,Test.com

$BBB = Import-Csv C:\BBB.csv
$CCC = Import-Csv C:\CCC.csv
$DDD = Import-Csv c:\DDD.csv

Пример кода 1:

foreach ($x in $AllMainFile) {
    $AAAoutput += $AAA | ? {$_.employeeNumber -eq $x.employeeNumber}
    $BBBoutput += $BBB | ? {$_.employeeNumber -eq $x.employeeNumber}
    $CCCoutput += $CCC | ? {$_.employeeNumber -eq $x.employeeNumber}
    $DDDoutput += $DDD | ? {$_.employeeNumber -eq $x.employeeNumber}

    if ($DDDoutput.Count -le 1 -and $AAAoutput.Count -le 1 -and $BBBoutput.Count -le 1 -and $CCCoutput.Count -le 1) {
        #### My Other script execution code here
    } else {
        #### My Other script execution code here
    }
}

Пример кода 2 (просто замена на -match вместо Where-Object):

foreach ($x in $AllMainFile) {
    $AAAoutput += $AAA -match $x.EmployeeNumber
    $BBBoutput += $BBB -match $x.EmployeeNumber
    $CCCoutput += $CCC -match $x.EmployeeNumber
    $DDDoutput += $AllMainFile -match $x.EmployeeNumber

    if ($DDDoutput.Count -le 1 -and $AAAoutput.Count -le 1 -and $BBBoutput.Count -le 1 -and $CCCoutput.Count -le 1) {
        #### My Other script execution code here
    } else {
        #### My Other script execution code here
    }
}

Пример кода 3 (просто замена на оператор -contains):

foreach ($x in $AllMainFile) {
    foreach ($c in $AAA){ if ($AllMainFile.employeeNumber -contains $c.employeeNumber) {$AAAoutput += $c}}
    foreach ($c in $BBB){ if ($AllMainFile.employeeNumber -contains $c.employeeNumber) {$BBBoutput += $c}}
    foreach ($c in $CCC){ if ($AllMainFile.employeeNumber -contains $c.employeeNumber) {$CCCoutput += $c}}
    foreach ($c in $DDD){ if ($AllMainFile.employeeNumber -contains $c.employeeNumber) {$DDDoutput += $c}}

    if ($DDDoutput.Count -le 1 -and $AAAoutput.Count -le 1 -and $BBBoutput.Count -le 1 -and $CCCoutput.Count -le 1) {
        #### My Other script execution code here
    } else {
        #### My Other script execution code here
    }
}

Я ожидаю, что скрипт будет выполнен быстро и быстронасколько это возможно, сравнивая и просматривая все 4 файла CSV с 1 входным файлом. Каждый файл содержит более 1000 тыс. Объектов / строк с 5 столбцами.

1 Ответ

1 голос
/ 20 октября 2019

Производительность

Прежде чем ответить на вопрос, я хотел бы немного рассказать об измерении производительности командлетов PowerShell. Собственный PowerShell очень хорош в потоковых объектах и, следовательно, может сэкономить много памяти, если потоковая передача выполняется правильно ( не назначать поток переменной или использовать скобки ). PowerShell также может вызывать практически все существующие .Net методы (например, Add()) и такие технологии, как LINQ .

Обычный способ измерения производительностиКоманда:

(Measure-Command {<myCommand>}).TotalMilliseconds

Если вы используете это в собственных потоковых командлетах PowerShell, они, кажется, не очень хорошо работают по сравнению с операторами и командами dotnet. Часто делается вывод, что, например, LINQ превосходит собственные команды PowerShell более чем в сто раз. Причина этого в том, что LINQ реагирует и использует отложенное (ленивое) выполнение: оно говорит, что оно выполнило свою работу, но на самом деле делает это в тот момент, когда вам нужен какой-либо результат (кроме того, он кэширует множество результатов, что проще всего). исключить из теста путем запуска нового сеанса), когда Native PowerShell довольно проактивен: он передает любой разрешенный элемент немедленно обратно в конвейер, и любой следующий командлет (например, Export-Csv) может затем завершить элемент и освободить его из памяти.
Другими словами, если у вас медленный ввод (см .: Защита собственной PowerShell ) или у вас есть большой объем данных для обработки (например, больше, чем доступно физической памяти), это может быть лучше и прощеиспользовать подход Native PowerShell.
В любом случае, если вы сравниваете какие-либо результаты, вам следует протестировать их на практике и тестировать их полностью, а не только на данных, которые уже доступны в памяти.

Создание списка

Я согласен, что использование метода Add() в списке намного быстреепри использовании +=, который объединяет новый элемент с текущим массивом, а затем переназначает его обратно в массив.
Но, опять же, оба подхода останавливают конвейер, поскольку они собирают все данные в памяти, где выможет быть, лучше промежуточно выпустить результат на диск.

HashTables

Вероятно, вы найдете наибольшее улучшение производительности при использовании хеш-таблицы, так как они оптимизированы для двоичного поиска .
Поскольку требуется сравнениедве коллекции друг к другу, вы не можете передавать оба потока, но, как было объяснено, было бы лучше и проще всего использовать 1 хеш-таблицу для одной стороны и сравнивать ее с каждым элементом в потоке на другой стороне, а также потому, что вы хотите сравнитьAllData для каждой из других таблиц лучше всего индексировать эту таблицу в память (в форме хеш-таблицы).

Вот как я бы это сделал:

$Main = @{}
ForEach ($Item in $All) {
    $Main[$Item.EmployeeNumber] = @{MainName = $Item.Name; MainDomain = $Item.Domain}
}

ForEach ($Name in 'AAA', 'BBB', 'CCC', 'DDD') {
    Import-Csv "C:\$Name.csv" | Where-Object {$Main.ContainsKey($_.EmployeeNumber)} | ForEach-Object {
        [PSCustomObject](@{EmployeeNumber = $_.EmployeeNumber; Name = $_.Name; Domain = $_.Domain} + $Main[$_.EmployeeNumber])
    } | Export-Csv "C:\Output$Name.csv"
}

Приложение

На основании комментария (и дубликатов в списках) выясняется, что на самом деле запрашивается объединение для всех ключей, а не только для EmployeeNumber,Для этого вам нужно объединить соответствующие ключи (разделенные разделителем, который , а не используется в данных) и использовать его в качестве ключа для хэш-таблицы.
Не в вопросе, а из комментарияПоявляется также, что ожидается полное соединение. Для части с правым соединением это можно сделать, вернув нужный объект в случае, если в основной таблице не найдено совпадений ($Main.ContainsKey($Key)). Для части с левым соединением это сложнее, так как вам нужно будет отследить ($ InnerMain), какие элементы в main уже сопоставлены, и вернуть оставшиеся элементы в конце:

$Main = @{}
$Separator = "`t"                       # Chose a separator that isn't used in any value
ForEach ($Item in $All) {
    $Key = $Item.EmployeeNumber, $Item.Name, $Item.Domain -Join $Separator
    $Main[$Key] = @{MainEmployeeNumber = $Item.EmployeeNumber; MainName = $Item.Name; MainDomain = $Item.Domain}    # What output is expected?
}

ForEach ($Name in 'AAA', 'BBB', 'CCC', 'DDD') {
    $InnerMain = @($False) * $Main.Count
    $Index = 0
    Import-Csv "C:\$Name.csv" | ForEach-Object {
        $Key = $_.EmployeeNumber, $_.Name, $_.Domain -Join $Separator
        If ($Main.ContainsKey($Key)) {
            $InnerMain[$Index] = $True
            [PSCustomObject](@{EmployeeNumber = $_.EmployeeNumber; Name = $_.Name; Domain = $_.Domain} + $Main[$Key])
        } Else {
            [PSCustomObject](@{EmployeeNumber = $_.EmployeeNumber; Name = $_.Name; Domain = $_.Domain; MainEmployeeNumber = $Null; MainName = $Null; MainDomain = $Null})
        }
        $Index++
    } | Export-Csv "C:\Output$Name.csv"
    $Index = 0
    ForEach ($Item in $All) {
        If (!$InnerMain[$Index]) {
            $Key = $Item.EmployeeNumber, $Item.Name, $Item.Domain -Join $Separator
            [PSCustomObject](@{EmployeeNumber = $Null; Name = $Null; Domain = $Null} + $Main[$Key])
        }
        $Index++
    } | Export-Csv "C:\Output$Name.csv"
}

Join-Объект

Просто к вашему сведению, я внес несколько изменений в Join-Object командлет (использование и установка очень просты, см .: В Powershell, как лучше объединить две таблицы в одну? ). Я сделал несколько небольших улучшений, в том числе более простую смену нескольких соединений, которые могут пригодиться для запроса, как этот. Хотя у меня до сих пор нет полного понимания того, что именно вы ищете (и у меня есть небольшие вопросы, такие как: как могут домены отличаться в столбце домена, если это выдержка из одного конкретного домена?).
Я беру общее описание" В частности, поиск объекта AD для 4 доменов и сохранение их в переменной для сравнения атрибутов " в качестве ведущего. Здесь я предполагаю, что $AllMainFile на самом деле является просто промежуточной таблицей, существующей вне конкатенации всех соответствующих таблиц (и не обязательно, но просто сбивающей с толку, поскольку она может содержать типы дубликатов employeenumbers из того же домена иemployeenumbers с других доменов). Если это правильно, вы можете просто опустить эту таблицу, используя командлет Join-Object:

$AAA = ConvertFrom-Csv @'
EmployeeNumber,Name,Domain
Z001,ABC,Domain1
Z002,DEF,Domain2
Z003,GHI,Domain3
'@

$BBB = ConvertFrom-Csv @'
EmployeeNumber,Name,Domain
Z001,ABC,Domain1
Z002,JKL,Domain2
Z004,MNO,Domain4
'@

$CCC = ConvertFrom-Csv @'
EmployeeNumber,Name,Domain
Z005,PQR,Domain2
Z001,ABC,Domain1
Z001,STU,Domain2
'@

$DDD = ConvertFrom-Csv @'
EmployeeNumber,Name,Domain
Z005,VWX,Domain4
Z006,XYZ,Domain1
Z001,ABC,Domain3
'@

$AAA | FullJoin $BBB -On EmployeeNumber -Discern AAA |
    FullJoin $CCC -On EmployeeNumber -Discern BBB |
    FullJoin $DDD -On EmployeeNumber -Discern CCC,DDD | Format-Table

Результат:

EmployeeNumber AAAName AAADomain BBBName BBBDomain CCCName CCCDomain DDDName DDDDomain
-------------- ------- --------- ------- --------- ------- --------- ------- ---------
Z001           ABC     Domain1   ABC     Domain1   ABC     Domain1   ABC     Domain3
Z001           ABC     Domain1   ABC     Domain1   STU     Domain2   ABC     Domain3
Z002           DEF     Domain2   JKL     Domain2
Z003           GHI     Domain3
Z004                             MNO     Domain4
Z005                                               PQR     Domain2   VWX     Domain4
Z006                                                                 XYZ     Domain1
...