PowerShell - 将大型固定宽度文件与大型SQL表进行比较

时间:2016-07-14 22:21:45

标签: sql-server powershell datatable

我是一名软件专家,但是在我第二周的PowerShell知识中。

我们有一组12个固定宽度的格式文件,其中包含人员列表(记录可能是重复的)。这些文件大约为800MB,总计行数约为1400万。查看第一个文件,它包含1,201,940行。

此外,我们有一个应该包含所有数据(不同记录)的SQL表。我的任务是使用PowerShell通过将源文件中的一些选择字段与SQL表进行比较,然后将任何丢失的记录写入CSV日志来确保数据完全加载。

让我们假设我感兴趣的字段是ID,FirstName,LastName,并且对于所有情况我限制我的对象/查询只考虑那些字段。

比较数据的PowerShell中最理想的方法是什么?您是将数据捆绑到SQL,使其完成工作,然后检索结果,还是将所有数据捆绑到PowerShell并在那里进行处理?

我想到了以下想法,但没有测试过它们:

  1. 创建SQL表变量(@fileInfo)。从文件(DataTable)创建$dtFile。使用$dtFile,对于每X行数,请加载@fileInfo。在LEFT JOIN和SQL表之间执行@fileInfo,并将结果推送到DataTable($dtResults)。将$dtResults写入日志。清空@fileInfo的内容以准备下一次循环迭代。这似乎是我最好的主意。
  2. 从文件(DataTable)创建$dtFile。使用$dtFile,对于每X行,构造一个SQL select语句,该语句具有可怕的WHERE子句,用于限制数据库返回的行。推送到另一个DataTable($dtSQL)。比较两者并记录$dtFile中未出现在$dtSQL中的任何条目。看起来很粗糙,但很有效。
  3. 将文件中的所有1.2米记录加载到DataTable。批量将它们插入SQL临时表,LEFT JOIN对SQL表,检索结果并将结果写入日志。我想我会因为通过网络推送大量数据而陷入困境
  4. 将SQL表中的所有记录加载到DataTable,将文件中的所有记录加载到第二个DataTable,比较PowerShell中的结果并将结果写入日志。我假设我的内存不足......?
  5. 我会为每个解决方案创建脚本并自己做一个测试,但是我处于紧张状态并且没有奢侈品。是不是总是那种情况?

    修改:我发布了一个适用于我的解决方案

2 个答案:

答案 0 :(得分:1)

我会在数据库引擎上完全卸载比较:

  1. 使用Import-CsvToSql(或bcp)等内容将数据批量加载到SQL中的新表fileTable
  2. fileTableoriginalTable using UNION ALL(见下文)
  3. 进行比较
  4. 将结果(即差异)记录到文件中。
  5. 根据底层存储,您可能希望将原始表复制到数据库,您可以在从数据库导入数据集之前将恢复模型切换为SIMPLE或BULK_LOGGED

    基于UNION ALL的比较程序看起来像:

    SELECT MIN(TableName) as TableName, ID, FirstName, LastName
    FROM
    (
      SELECT 'Database' as TableName, originalTable.ID, originalTable.FirstName, originalTable.LastName
      FROM originalTable
      UNION ALL
      SELECT 'Files' as TableName, fileTable.ID, fileTable.FirstName, fileTable.LastName
      FROM fileTable
    ) tmp
    GROUP BY ID, FirstName, LastName
    HAVING COUNT(*) = 1
    ORDER BY ID
    

答案 1 :(得分:0)

很抱歉所有人都没有及时回复,但我有一个解决方案!最可能的改进空间,但解决方案相当快。我没有权限访问我的数据库直接运行PowerShell,所以我最后使用了SQL导入和导出向导。

流程摘要:

  1. 创建数据点的CSV文件,该文件将作为PowerShell中的对象数组使用。
  2. 找到感兴趣的文件并将它们存储到字符串数组中(可能没有必要,但似乎更快)
  3. 遍历字符串数组中的每个文件。对于每个文件,打开.NET StreamReader,对于每一行,根据对象数据点数组解析文件,以创建写入单个合并分隔输出文件的子字符串。我推荐管道字符(看起来像这样: | ,通常高于 Enter ),因为它通常不在数据中找到,而流氓逗号或制表符可能绊倒你。
  4. 脚本完成后,使用SQL中的导入向导从输出文件
  5. 创建表

    <强>详细

    1. 创建一个CSV文件,列出A列中的数据点,B列中的起始位置以及C列中的宽度。它看起来像这样: data point CSV file
    2. 使用对象数组在脚本中导入数据点。

      $dataPoints = Import-Csv "c:\temp\datapoints.csv"
      $objDataCols = @()
      foreach($objCol in $dataPoints){
          objColumn = New-Object psobject
          $objColumn | Add-Member -Type NoteProperty -Name Name -Value $objCol.Name
          $objColumn | Add-Member -Type NoteProperty -Name Position -Value ([int] $objCol.Position)
          $objColumn | Add-Member -Type NoteProperty -Name ColumnLength -Value ([int] $objCol.ColumnLength)
          $objSourceCols += $objColumn
      }
      
    3. 查找文件并将名称组合到一个数组(可选)。我使用正则表达式来过滤我的文件。

      $files = @()
      Get-ChildItem -Path $sourceFilePath | Where-Object { $_.FullName -match $regExpression } | ForEach-Object{
          $files += $_.FullName
      }
      
    4. 循环遍历每个文件并将其解析为输出文件。在生产代码中,您需要try / catch块,但我在示例中将它们遗漏了。

      $writer = New-Object System.IO.StreamWriter "c:\temp\outputFile.txt"
      ForEach($sourceFileName in $files){
          $reader = [System.IO.File]::OpenText($sourceFileName)
          while($reader.Peek() -gt -1){
              $line = $reader.ReadLine()
      
              # Write each data point in the line, pipe delimited
              for($i = 0; $j -le ($objDataCols).Length; $i++){
                  # Write to a pipe-delimited file
                  $writer.Write("{0}|", $line.Substring($objDataCols[$i].Position, $objDataCols[$i].ColumnLength))
              }
      
              # Write a new line, along with any additional reference columns not defined in the source file, such as adding in the source file name and line number
              $writer.WriteLine($sourceFileName)
          }    
          $reader.Close()         
          $reader.Dispose()
      }
      $writer.Close()
      $writer.Dispose()
      
    5. 将竖线分隔的输出文件导入SQL。

相关问题