MAP CSV userdata для разделения CSV
Я унаследовал немного беспорядка. У меня есть несколько файлов CSV с разными пользовательскими данными. Мне нужно найти способ собрать всю информацию в один файл, и я не хочу тратить на это часы. Проблема в том, что не все пользователи одинаковы, и они не в том же порядке. Есть ли простой способ перенести поля из второго файла в другой, где имя пользователя соответствует одному в первом файле? Я уверен, что я не описываю это правильно, просто начинаю.
Например: файл 1
username,first,last,phone number
john.do,John,Doe,8888675309
jack.jo,Jack,Johnson,5378984687
harry.po,Harry,Potter,9876543219
Файл 2
username,first,last,email
john.do,John,Doe,john.squidwork@yahoo.com
sandy.mi,Sandy,Michaels,sandy.mi@hotelcalifornia.com
jack.jo,Jack,Johnson,bubbletoes@jackjohnson.net
harry.po,Harry,Potter,iluvmuggles@diagonalley.com
3 ответа
Примите это как хотите, это должно объединить несколько файлов CSV. Обратите внимание, что это может быть не быстро, но это должно быть тщательно.
$CSVList = 'C:\Path\To\Users1.csv','C:\Path\To\Users2.csv','C:\Path\To\Users3.csv','C:\Path\To\Users4.csv','C:\Path\To\Users5.csv'
$PrimaryTable = @{}
Import-CSV $CSVList[0] | %{$PrimaryTable.Add($_.UserID,$_)}
$PrimaryKeys = $PrimaryTable.Values[0] | Get-Member -MemberType Properties | Select -ExpandProperty Name
ForEach($CSVFile in ($CSVList|Select -Skip 1)){
$Users = Import-CSV $CSVFile
$Keys = $Users[0] | Get-Member -MemberType Properties | Select -ExpandProperty Name
$KeysToAdd = @{}
$Keys|?{$_ -notin $PrimaryKeys}|%{$KeysToAdd.Add($_,"")}
$PrimaryTable.Values|%{$_|Add-Member -NotePropertyMembers $KeysToAdd}
ForEach($User in $Users){
If(!($User.UserID -in $PrimaryTable.Keys)){
$PrimaryKeys | ?{$_ -notin $Keys} | %{add-member -InputObject $User -NotePropertyName $_ -NotePropertyValue ""}
$PrimaryTable.Add($User.UserID,$User)
}Else{
$Keys | ?{[string]::IsNullOrWhiteSpace($PrimaryTable.($User.UserID).$_)} | %{$PrimaryTable.($User.UserID).$_ = $User.$_}
}
}
$PrimaryKeys = $PrimaryTable.Values[0] | Get-Member -MemberType Properties | Select -ExpandProperty Name
}
$PrimaryTable.Values|Export-CSV C:\Path\To\AllUserData.csv -NoTypeInformation
Это делает хеш-таблицу индексируемой по идентификатору пользователя. Он заполняет его данными из первого файла CSV. Затем для каждого дополнительного он проверяет различия в свойствах того, что находится в первом CSV и текущем, добавляет недостающие свойства ко всем элементам в основной хеш-таблице, затем идет запись за записью, и если пользователь не находится в main hashtable добавляет их, и если они есть, то заполняет любые пробелы, которые могут для их свойств.
Изменить: Хорошо, так что у вас возникли проблемы с -notin
оператор. Наиболее вероятная причина этого - более старая версия PowerShell. Мое первое предложение - обновить PowerShell до версии v3 или v4, но я знаю, что это не всегда вариант, поэтому, чтобы сделать его немного более совместимым с предыдущими версиями, я внес некоторые изменения в сценарий, который должен заставить его работать на вас... Я надеюсь. Я протестировал приведенный выше скрипт (с обновленными путями в строке 1 и закомментировал последнюю строку, потому что мне не хотелось засорять мой жесткий диск еще большим количеством файлов) с тремя CSV-файлами, у каждого из которых есть поле UserID, и каждый было от 2 до 4 записей, и это работало точно так, как я ожидал. Во всяком случае, отредактированный скрипт:
$CSVList = 'C:\Path\To\Users1.csv','C:\Path\To\Users2.csv','C:\Path\To\Users3.csv','C:\Path\To\Users4.csv','C:\Path\To\Users5.csv'
$PrimaryTable = @{}
Import-CSV $CSVList[0] | %{$PrimaryTable.Add($_.UserID,$_)}
$PrimaryKeys = $PrimaryTable.Values[0] | Get-Member -MemberType Properties | Select -ExpandProperty Name
ForEach($CSVFile in ($CSVList|Select -Skip 1)){
$Users = Import-CSV $CSVFile
$Keys = $Users[0] | Get-Member -MemberType Properties | Select -ExpandProperty Name
$KeysToAdd = @{}
$Keys|?{$PrimaryKeys -notcontains $_}|%{$KeysToAdd.Add($_,"")}
$PrimaryTable.Values|%{$_|Add-Member -NotePropertyMembers $KeysToAdd}
ForEach($User in $Users){
If(!($User.UserID -in $PrimaryTable.Keys)){
$PrimaryKeys | ?{$Keys -notcontains $_} | %{add-member -InputObject $User -NotePropertyName $_ -NotePropertyValue ""}
$PrimaryTable.Add($User.UserID,$User)
}Else{
$Keys | ?{[string]::IsNullOrWhiteSpace($PrimaryTable.($User.UserID).$_)} | %{$PrimaryTable.($User.UserID).$_ = $User.$_}
}
}
$PrimaryKeys = $PrimaryTable.Values[0] | Get-Member -MemberType Properties | Select -ExpandProperty Name
}
$PrimaryTable.Values|Export-CSV C:\Path\To\AllUserData.csv -NoTypeInformation
Это должно делать то, что вы хотите, и должно работать в старых версиях PowerShell. Дайте мне знать, если у вас есть ошибки с этим. Опять же, я рекомендую обновить PowerShell, если вы используете v2. В конечном итоге вы будете счастливее, чем работать вокруг.
Вот функция, которую вы можете использовать для группировки данных по некоторой клавише. Если в некоторой группе будет несколько разных значений для какого-либо свойства, то результирующий объект будет иметь массив со всеми значениями для этого свойства:
function Group-Data {
param(
[object[]]$Property
)
$AllProperties=[ordered]@{}
@(
$input|Group-Object $Property|ForEach-Object {
$_.Group|ForEach-Object {$Properties=@{}} {
$_.PSObject.Properties|Where-Object Value|ForEach-Object {
if($Properties[$_.Name]){
if($Properties[$_.Name]-notcontains$_.Value){
$Properties[$_.Name]=@($Properties[$_.Name];$_.Value)
}
}else{
$Properties[$_.Name]=$_.Value
$AllProperties[$_.Name]=$null
}
}
} {[PSCustomObject]$Properties}
}
)|Select-Object @($AllProperties.Keys)
}
Вот функция, которая объединяет массивы в свойствах. Вы должны использовать это, потому что Export-Csv
неправильно обрабатывает массивы в свойствах.
filter Join-Array {
param(
[string]$Separator=', '
)
$_.PSObject.Properties|Where-Object Value -is Array|ForEach-Object {
$_.Value=$_.Value-join$Separator
}
$_
}
И вы можете использовать это так:
Import-Csv File1.csv,File2.csv,File3.csv|Group-Data username|Join-Array|Export-Csv Result.csv
Управление данными может быть грязным, особенно когда вы наследуете беспорядок, который происходит в большинстве случаев.
Одним из лучших инструментов для управления данными является система управления базами данных, или СУБД. Это может быть, однако, излишним в вашем случае. Вам может потребоваться выполнить эту операцию только один раз, пока у вас не появятся все беспорядочные унаследованные данные в одном аккуратном CSV-файле, который вы сможете поддерживать в курсе. В этом случае кривая обучения для полноценной СУБД может не стоить этого.
Есть три реляционных оператора, которые дают реляционным базам данных большую часть своих возможностей для обработки данных во время поиска. Эти операторы ограничены (ранее назывались select), project и join. Если вы можете имитировать этих трех операторов в PS, вы сможете очистить свои данные в PS без вызова СУБД.
PS уже есть хороший оператор, который делает то, что делает ограничение. Это где-объект.
PS уже есть хороший оператор, который делает то, что делает проект. Это групповой объект.
Относительное соединение - это то, где это становится грязным. Насколько я знаю, в PS нет join-объекта. Однако Бэкон Битс предоставил ссылку на статью блога Join-Object, и это, по-видимому, именно то, что нужно, если вы хотите создать функцию join-object самостоятельно. Спасибо, Бэкон. Некоторые из статей блога являются мотивационными, в них объясняется, почему разложение (разбиение) таблиц иногда полезно, а затем мотивируется объект соединения для использования, когда вам нужны все данные в одном месте. Если вы SQL жокей, вы уже знаете это. Но научиться делать это в PS - это здорово.