【问题标题】:Powershell - Extract data from a file depending on a 2nd file dataPowershell - 根据第二个文件数据从文件中提取数据
【发布时间】:2019-12-03 07:18:21
【问题描述】:

我正在尝试从一个文件中提取一些数据,并与另一个文件中的数据进行比较。我需要在 Powershell 中发生这种情况。我真的在试图哄骗一些类似 SQL 的工作,尽管我已经摸不着头脑,阅读了这个主题,但我无法让任何事情发生。

基本上,我需要将文件 1 中的 4 个条件匹配到文件 2 上的 4 个条件。如果匹配,我需要确保文件 2 上的 2 个其他条件满足某些值。

如果条件匹配并且满足值,我希望将这些事务放在一个文件中,否则在另一个文件中。

从文件 1 到文件 2 的匹配条件:

  • 公司编号:$_.File1Field1$_.File2Field4

  • 交易号:$_.File1Field8$_.File2Field1的后4位

  • 交易值:$_.File1Field11$_.File2Field10

  • 交易日期:$_.File1Field7$_.File2Field7

如果这 4 个条件匹配,我需要文件 2 上的 2 个额外字段,而不是,例如,$_.File2Field2 的“ABC”或“DEF”和$_.File2Field3 的“1234”。

然后我需要一个文件中的一种类型的所有事务,而另一个文件中的其他事务。 我是否对 powershell 要求太多(我当然对自己要求太多了!!!!大声笑)?!?

我已经阅读了很多关于 Compare-Object、Where-Object 的内容,但找不到任何可以使用的东西。 :(

这些是我目前的原始文件。

#File 1:
$LoanTransactions = import-Csv \\774512-LRBSPT01\********$\uardata\rt1\BankRec\Test\step1\Test_Store_Loans_Module.txt
#File 2:
$LoanExtract = import-Csv \\774512-LRBSPT01\********$\uardata\rt1\BankRec\Test\loans_extract.csv

我过去做过 where-object,我会预先定义一些条件,然后应用该 a 文件来提取结果,但我无法使条件取决于包含在我的第二个文件。

类似的东西:

$ConditionsToMeet = { $_.File1Field1 -eq $_.File2Field4 -and $_.File1Field8 -eq $_.File2Field1.....}
$ConditionsNotMet = { $_.File1Field1 -ne $_.File2Field4 -or $_.File1Field8 -ne $_.File2Field1.....}

$LoanTransactions | where-object $ConditionsToMeet | Export-Csv \\774512-LRBSPT01\********$\uardata\rt1\BankRec\Test\step1\Extract1.txt

$LoanTransactions | where-object $ConditionsNotMet | Export-Csv \\774512-LRBSPT01\********$\uardata\rt1\BankRec\Test\step1\Extract2.txt

很明显,我的条件构造不正确,不幸的是,尽管进行了大量阅读,但我还是找不到方法。 它可能非常简单,或者 Powershell 不能完全做到这一点,但我认为这应该可以实现?

【问题讨论】:

  • does this help at all? 它本质上等同于 unix diff 命令,这似乎是您想要完成的。
  • @cyberstems,对不起,我在那个链接中找不到任何可以使用的东西(反正我自己!)。不过那里有一些有趣的东西。

标签: powershell


【解决方案1】:

我将假设公司编号、交易编号、交易日期和交易价值是保证每行值的唯一标识符。换句话说,它们将构成 SQL 表中的一个键。坦率地说,我的假设是事务值实际上不是密钥的一部分,而是两个文件之间的数据一致性检查。如果这些假设中的任何一个不正确,则此解决方案可能不适合您。但是,如果是这种情况,那么您的问题可能是不确定的。也就是说,您的数据文件可能没有足够的信息来实际解决问题。

Compare-Object 在这里无法正常工作。这是一个非常不稳定的命令,在大多数应用程序中表现不佳。

最简单的答案可能是“将您的数据文件加载到 SQL 数据库中并运行查询”。最终,这可能是您的最佳选择,即使您正在考虑使用 SQLite 数据库。

但是,假设您必须使用 PowerShell 执行此操作。最简单的方法是采取死而简单的蛮力方法:

#File 1:
$LoanTransactions = Import-Csv \\774512-LRBSPT01\********$\uardata\rt1\BankRec\Test\step1\Test_Store_Loans_Module.txt
#File 2:
$LoanExtract = Import-Csv \\774512-LRBSPT01\********$\uardata\rt1\BankRec\Test\loans_extract.csv

# Find all transactions to save to Extract1
$Extract1 = foreach ($Tran in $LoanTransactions) {
    foreach ($Extract in $LoanExtract) {
        if (
            ($Tran.File1Field1 -eq $Extract.File2Field4) -and
            ($Tran.File1Field8 -eq $Extract.File2Field1) -and
            ($Tran.File1Field11 -eq $Extract.File2Field10) -and
            ($Tran.File1Field7 -eq $Extract.File2Field7) -and
            ($Extract.File2Field2 -notin ('ABC','DEF')) -and
            ($Extract.File2Field3 -ne '1234')
        ) {
            $Tran
        }
    }
}

# Find all transactions not in Extract1 and save them to Extract2
$Extract2 = foreach ($Tran in $LoanTransactions) {
    $FoundInExtract1 = $false
    foreach ($Extract in $Extract1) {
        if (
            ($Tran.File1Field1 -eq $Extract.File2Field4) -and
            ($Tran.File1Field8 -eq $Extract.File2Field1) -and
            ($Tran.File1Field11 -eq $Extract.File2Field10) -and
            ($Tran.File1Field7 -eq $Extract.File2Field7)
        ) {
            $FoundInExtract1 = $true
        }
    }
    if (-not $FoundInExtract1) {
        $Tran
    }
}

$Extract1 | Where-Object $ConditionsToMeet | Export-Csv \\774512-LRBSPT01\********$\uardata\rt1\BankRec\Test\step1\Extract1.txt -NoTypeInformation

$Extract2 | Where-Object $ConditionsNotMet | Export-Csv \\774512-LRBSPT01\********$\uardata\rt1\BankRec\Test\step1\Extract2.txt -NoTypeInformation

通过使用哈希表作为索引来加快处理速度,您可能会更聪明。基本上,我们要做的是将$LoadExtract 加载到嵌套的哈希表中。哈希表使 非常 快速键查找。构建表应该相对较快。使用主键中有 6 个字段的本地数据库表,处理 175,000 条记录大约需要 12 秒,但您的里程可能会有所不同。这里有很多变数。

一旦构建完成,脚本就可以很容易地在 File2 中针对 File1 中的值进行查找。代码会有点难看,但它的性能应该和 PowerShell 差不多。

接下来要记住的是Import-Csv 将所有内容都导入为纯文本。这意味着重要的格式包括尾随空格。此外,哈希表是数据类型感知的。 $hash[1234]$hash['1234'] 不同。如果数据类型和数据值不匹配完全,您将无法正确进行查找,因此您需要非常确定您用于键的字段完全相同。您唯一不必担心的是区分大小写。其他一切都很重要,因为没有隐式数据转换。

请注意,在下面的代码中,我不小心交换了交易日期和交易价值。这实际上并不会影响它的功能,但它会使它变得更加混乱。我责怪真正糟糕的字段名称。

首先,我们将从文件 2 中设置哈希表:

#File 1:
$LoanTransactions = Import-Csv \\774512-LRBSPT01\********$\uardata\rt1\BankRec\Test\step1\Test_Store_Loans_Module.txt
#File 2:
$LoanExtract = Import-Csv \\774512-LRBSPT01\********$\uardata\rt1\BankRec\Test\loans_extract.csv

#Hash table to be used for Loan Extract lookups
$LoanExtractTable = @{}

#Build the loan extract table nested hash table
$LoanExtract | ForEach-Object {
    # Company Number
    if (!$LoanExtractTable.ContainsKey($_.File2Field4)) {
        $LoanExtractTable[$_.File2Field4] = @{}
    }
    # Transaction Number
    if (!$LoanExtractTable[$_.File2Field4].ContainsKey($_.File2Field1)) {
        $LoanExtractTable[$_.File2Field4][$_.File2Field1] = @{}
    }
    # Transaction Date
    if (!$LoanExtractTable[$_.File2Field4][$_.File2Field1].ContainsKey($_.File2Field7)) {
        $LoanExtractTable[$_.File2Field4][$_.File2Field1][$_.File2Field7] = @{}
    }
    # Transaction Value
    # This is the last key, so we save our data here.
    if (!$LoanExtractTable[$_.File2Field4][$_.File2Field1][$_.File2Field7].ContainsKey($_.File2Field10)) {
        $LoanExtractTable[$_.File2Field4][$_.File2Field1][$_.File2Field7][$_.File2Field10] = $_
    }
}

所以,如果我们有一个公司编号57,交易编号为123456,交易日期为2019-11-30,交易价值为123.45,您可以这样查找:

$LoanExtractTable['57']['123456']['2019-11-30']['123.45']

再次注意,这里的一切都是明确的字符串。格式对日期和货币价值很重要。然而,在下面的代码中,我们需要返回ContainsKey() 并再次嵌套 if 语句,因为我们需要查找记录的存在。如果感觉我们正在构建 SQL 样式索引并进行连接,那是因为我们基本上是。

现在,我们可以遍历交易文件了。

foreach ($LoanTransaction in $LoanTransactions) {
    $ValidExtract = $false
    # Company Number
    if ($LoanExtractTable.ContainsKey($LoanTransaction.File1Field1)) {
        # Transaction Number
        if ($LoanExtractTable[$LoanTransaction.File1Field1].ContainsKey($LoanTransaction.File1Field8)) {
            # Transaction Date
            if ($LoanExtractTable[$LoanTransaction.File1Field1][$LoanTransaction.File1Field8].ContainsKey($LoanTransaction.File1Field7)) {
                # Transaction Value
                if ($LoanExtractTable[$LoanTransaction.File1Field1][$LoanTransaction.File1Field8][$LoanTransaction.File1Field7].ContainsKey($LoanTransaction.File1Field11)) {
                    # It's in the LoanExtract file! Now we can do our additional data checks
                    if (
                        $LoanExtractTable[$LoanTransaction.File1Field1][$LoanTransaction.File1Field8][$LoanTransaction.File1Field7][$LoanTransaction.File1Field11].File2Field2 -notin @('ABC','DEF')
                        -and $LoanExtractTable[$LoanTransaction.File1Field1][$LoanTransaction.File1Field8][$LoanTransaction.File1Field7][$LoanTransaction.File1Field11].File2Field3 -ne '1234')
                    ) {
                        # It's valid to export to Extract1.txt
                        $ValidExtract = $true
                        $LoanTransaction | Export-Csv \\774512-LRBSPT01\********$\uardata\rt1\BankRec\Test\step1\Extract1.txt -Append -NoTypeInformation 
                    }
                }
            }
        }
    }

    if ($ValidExtract -eq $false) {
        $LoanTransaction | Export-Csv \\774512-LRBSPT01\********$\uardata\rt1\BankRec\Test\step1\Extract2.txt -Append -NoTypeInformation 
    }
}

这里肯定有优化和改进的空间。我不知道您的数据是什么样的,但我不得不认为上面字段的顺序非常糟糕。我认为交易日期、公司编号、交易编号、交易价值会更好。

此外,您可以轻松地将 File2Field2File2Field3 的测试移动到您正在构建哈希表的位置,但我不能确定您正在做的所有事情。

写出数据的方式也不是非常理想,但它可能比迭代两次或使用+= 的数组连接要好。至少这样可以使用磁盘缓冲区。

上面的代码简单,但它与我想的一样简单,无需让 PowerShell 为 File 1 的每一行迭代 File2(即,做一个笛卡尔积)如果两个文件都很大,可能会非常慢。

【讨论】:

  • 不幸的是,我今天没有时间尝试使用您的示例。我希望明天有一些。你的例子#1很棒。我不可能写出来,但我理解你是如何让它工作的,而且似乎正是我所需要的。您的示例 2 似乎也不错,但由于某种原因,我更喜欢 #1。我将需要同时实现两者,看看我是否可以获得相同的确切输出。好刺激!!!!太感谢了。 :)
  • 哦,我认为没有描述性的字段名称会更简单......大声笑在真实文件中,字段具有实际名称。 :D
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-09
  • 1970-01-01
  • 1970-01-01
  • 2020-10-09
  • 2018-12-10
相关资源
最近更新 更多