【问题标题】:Data being inserted row-by-row rather than in batches数据被逐行插入,而不是批量插入
【发布时间】:2019-11-05 01:24:34
【问题描述】:

我正在尝试从大型 json 文档中批量处理行,并将它们插入 SQL Server。

以下代码有效,但一次将 1 行插入 SQL Server 表中。我认为这实际上是每 1000 行。

add-type -path "C:\Program Files\WindowsPowerShell\Modules\newtonsoft.json\1.0.2.201\libs\newtonsoft.json.dll"
install-module sqlserver -AllowClobber

class EliteCoords {
    [double] $x
    [double] $y
    [double] $z
}

class EliteSystem {
    [int]         $id
    [long]        $id64
    [string]      $name
    [EliteCoords] $coords
    [string]      $date 
}

$dt = New-Object system.data.datatable

$dt.Columns.Add("ID",[int])
$dt.Columns.Add("ID64",[long])
$dt.Columns.Add("Name",[string])
$dt.Columns.Add("Coordsx",[decimal])
$dt.Columns.Add("Coordsy",[decimal])
$dt.Columns.Add("Coordsz",[decimal])
$dt.Columns.Add("DiscoveryDate",[string])

$stream = (Get-Item c:\code\systemsWithCoordinates.json).OpenText()
$reader = [Newtonsoft.Json.JsonTextReader]::new($stream)
while ($reader.Read()) {
    $cnt = 0
    $dt.Clear()
    while ($cnt -le 1000) {
        $cnt = $cnt + 1

        if ($reader.TokenType -eq 'StartObject') {

            $row = [Newtonsoft.Json.JsonSerializer]::CreateDefault().Deserialize($reader, [EliteSystem])

            $dr = $dt.NewRow()

            $dr["ID"]            = $row.id
            $dr["ID64"]          = $row.id64
            $dr["Name"]          = $row.name
            $dr["Coordsx"]       = $row.coords.x
            $dr["Coordsy"]       = $row.coords.y
            $dr["Coordsz"]       = $row.coords.z
            $dr["DiscoveryDate"] = $row.date

            $dt.Rows.Add($dr)       
        }
    }

    write-sqltabledata -ServerInstance ELITEDANGEROUS -Database EDSM -Schema Staging -Table SystemsWithCoordinates -InputData $dt
}

$stream.Close()

我知道问题是因为 if 块被跳过,除了内部 while 循环的第一次迭代之外,因为令牌类型正在从 StartObject 更改为 StartArray。

我尝试在其中添加一个额外的阅读器循环,但当然,它会读取整个文件。

我也尝试只读取数组而不是对象,但当然,由于嵌套的 json,这会失败。

我应该如何构建循环,以便批量处理 1000 行?

【问题讨论】:

  • 无法复制。我尝试了一个基本示例,其中$dt[system.data.datatable] 类型。然后我添加了列来匹配你的。然后我用while循环循环添加虚拟数据。最后我跑了最后的write-sqltabledata。所有 1000 行都同时快速运行。我使用 sqlserver 模块版本 21.1.18121 进行了测试。
  • 如果您使用 SQL Server 2016 或更高版本,您可以简单地将 JSON 本身插入数据库,并使用 TSQL 函数对其进行处理。 docs.microsoft.com/en-us/sql/relational-databases/json/…
  • 我无法插入整个文档,原因与粉碎时需要批处理行的原因相同。文件是休。这个文件是 5GB,但我有另一个是 90GB。它需要流式传输和批处理,否则根本不实用
  • @AdminOfThings - 我在问题中添加了更多细节,包括目标表的架构。它似乎“忽略”了嵌套的 while
  • 您能否验证$row = [Newtonsoft.Json.JsonSerializer]::CreateDefault().Deserialize($reader, [EliteSystem]) 行每次$cnt 增加时都会产生不同的输出,或者目标是拥有1000 个相同数据的副本?

标签: json powershell json.net


【解决方案1】:

您的问题是每次调用 $reader.Read() 时都要刷新和清除表——即每一行。

相反,您需要累积行直到达到 1000,然后刷新:

$stream = (Get-Item c:\code\systemsWithCoordinates.json).OpenText()
$reader = [Newtonsoft.Json.JsonTextReader]::new($stream)
try {
    $serializer = [Newtonsoft.Json.JsonSerializer]::CreateDefault()
    while ($reader.Read()) {
        # If the reader is positioned at the start of an object then accumulate a row.
        if ($reader.TokenType -eq 'StartObject') {                
            $row = serializer.Deserialize($reader, [EliteSystem])

            $dr = $dt.NewRow()

            $dr["ID"]            = $row.id
            $dr["ID64"]          = $row.id64
            $dr["Name"]          = $row.name
            $dr["Coordsx"]       = $row.coords.x
            $dr["Coordsy"]       = $row.coords.y
            $dr["Coordsz"]       = $row.coords.z
            $dr["DiscoveryDate"] = $row.date

            $dt.Rows.Add($dr)       
        }

        # If we have accumulated 1000 rows, flush them.
        if ($dt.Rows.Count -ge 1000) {
            write-sqltabledata -ServerInstance ELITEDANGEROUS -Database EDSM -Schema Staging -Table SystemsWithCoordinates -InputData $dt
            $dt.Clear()
        }
    }

    # Flush any remaining rows.
    if ($dt.Rows.Count -ge 0) {
        write-sqltabledata -ServerInstance ELITEDANGEROUS -Database EDSM -Schema Staging -Table SystemsWithCoordinates -InputData $dt
        $dt.Clear()
    }
}
finally {
    $reader.Close()
    $stream.Close()
}

注意事项:

  • 如果发生异常,您可能应该在 finally 块中处理 StreamReaderJsonTextReader。有关执行此操作的漂亮方法,请参阅 How to implement using statement in powershell?

  • 仅分配一次序列化程序应该会稍微提高性能,而且不会产生任何成本。

  • 如果没有 JSON 文件的示例,我们无法说明用于每一行的 EliteSystem 数据模型是否存在其他问题。例如,如果 JSON 文件实际上是一个锯齿状二维数组,这可能不起作用。

【讨论】:

    【解决方案2】:

    答案是用 break 代替内部循环。然后添加一个外部循环,该循环一直持续到流结束标记命中。

    add-type -path "C:\Program Files\WindowsPowerShell\Modules\newtonsoft.json\1.0.2.201\libs\newtonsoft.json.dll"
    #install-module sqlserver -AllowClobber
    
    class EliteCoords {
        [double] $x
        [double] $y
        [double] $z
    }
    
    class EliteSystem {
        [int]         $id
        [long]        $id64
        [string]      $name
        [EliteCoords] $coords
        [string]      $date 
    }
    
    $dt = New-Object system.data.datatable
    
    $dt.Columns.Add("ID",[int])
    $dt.Columns.Add("ID64",[long])
    $dt.Columns.Add("Name",[string])
    $dt.Columns.Add("Coordsx",[decimal])
    $dt.Columns.Add("Coordsy",[decimal])
    $dt.Columns.Add("Coordsz",[decimal])
    $dt.Columns.Add("DiscoveryDate",[string])
    
    $stream = (Get-Item c:\code\systemsWithCoordinates.json).OpenText()
    $reader = [Newtonsoft.Json.JsonTextReader]::new($stream)
    while ($stream.EndOfStream -eq $false) {
    $cnt = 0
        $dt.Clear()
        while ($reader.Read()) {
    
            if ($reader.TokenType -eq 'StartObject') {
    
                    $row = [Newtonsoft.Json.JsonSerializer]::CreateDefault().Deserialize($reader, [EliteSystem])
    
                    $dr = $dt.NewRow()
    
                    $dr["ID"]            = $row.id
                    $dr["ID64"]          = $row.id64
                    $dr["Name"]          = $row.name
                    $dr["Coordsx"]       = $row.coords.x
                    $dr["Coordsy"]       = $row.coords.y
                    $dr["Coordsz"]       = $row.coords.z
                    $dr["DiscoveryDate"] = $row.date
    
                    $dt.Rows.Add($dr)       
    
    
            $cnt = $cnt + 1
    
    
            }
            if ($cnt -gt 9999) {break}
        }
        write-sqltabledata -ServerInstance ELITEDANGEROUS -Database EDSM -Schema Staging -Table SystemsWithCoordinates -InputData $dt -Timeout 0
    }
    
    $stream.Close()
    

    【讨论】:

    • JsonTextReader 缓冲其输入,因此stream.EndOfStream 可能在最后一行实际反序列化之前为真。
    猜你喜欢
    • 1970-01-01
    • 2020-02-02
    • 2012-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多