F# 使用Nessos.Stream减少获取查询输出并将其写入“.CSV”的时间`

F# 使用Nessos.Stream减少获取查询输出并将其写入“.CSV”的时间`,f#,lazy-evaluation,F#,Lazy Evaluation,在过去,我编写的查询只是获取整个查询,将结果存储在内存中,然后将whote序列提供给.CSV类型提供程序。查询示例: let results = query { for row in db.ThisRow do select row } |> Seq.toList 据我所知,Seq.toList部分强制查询运行,同时将查询本身的输出保持为Seq将是惰性的。如果查询结果的数量很小,这不是什么大问题。但是,如果结果的

在过去,我编写的查询只是获取整个查询,将结果存储在内存中,然后将whote序列提供给
.CSV
类型提供程序。查询示例:

let results = 
    query {
        for row in db.ThisRow do 
        select row
        }
        |> Seq.toList
据我所知,
Seq.toList
部分强制查询运行,同时将查询本身的输出保持为
Seq
将是
惰性的。
如果查询结果的数量很小,这不是什么大问题。但是,如果结果的数量很大(例如>1Mil行),我最终会得到一个
System.OutOfMemoryException
。因此,一位朋友在《探索使用Nessos.Stream》库》中提出了建议

我的目标是从查询中提取一行,对该行执行一些操作,然后将该行写入一个
.CSV
,并对每一行重复执行该操作,所有行最终都在同一个
.CSV
文件中

所以,我试着

open Nessos.Stream

type StringInt = {
    String: string option
    Int: int
    }

type StringIntCsvType = CsvProvider<Sample = "item_number, num", 
                                    Schema = "item_number (string option), num (int)", 
                                    HasHeaders = true>
let buildRowFromObject (obj: StringInt) = StringIntCsvType.Row(obj.String, 
                                                               obj.Int)

let results = 
    query {
        for row in db.ThisRow do 
        select row
        }
        |> Stream.ofSeq
        |> Stream.groupBy (fun row -> row.ITEMNUMBER)
        |> Stream.map (fun (itemString, seq) -> (itemString, (seq |> Seq.length)))
        |> Stream.map (fun (str, num) -> {String = Some str; 
                                          Int = num})
        |> Stream.map buildRowFromObject
        |> Stream.toSeq

let ThisCsv= new StringIntCsvType(results)
let ThisCsvLoc = "pathToFileLocation"
let ThisCsv.Save(ThisCsvLoc)
|>Stream.map buildRowFromObject
行下,但是
x
错误为

Type Constraint Mismatch.  The type
    CsvProvider<...>.Row
is not compatible with type
    Collections.Generic.IEnumerable<CsvProvider<...>.Row>
类型约束不匹配。类型
CsvProvider.Row
与类型不兼容
Collections.Generic.IEnumerable
即使没有给我这个错误,我认为添加这一行会为每一行创建一个新的
.CSV
,这显然不是我想要的


如何编写查询,对查询的每个不同部分进行操作,并将查询的每一行写入相同的
.CSV
文件?我和上面的代码接近了吗

您不需要中间StringInt记录类型。您已经拥有buildRowFromObject的CSV序列,您可以直接将其写入文件。虽然我也是一个超级粉丝,但我不确定它是否能为您带来这么多价值(我可能错了,也许您正在进行一些更复杂的map/reduce操作)。在本例中,在探索其他解决方案之前,我会先选择简单的解决方案

首先,确保FSI设置为64位,并且在exe中编译为64位。其次,正如@krontogiannis所说,您的操作似乎是在计算某个组中的项目。我认为您不想实际遍历每个组,所以为什么不在DB端进行计数呢。您将返回一个元组序列(我希望如此),其中包含一些ID和countNumber。您可以直接将其提供给CSVTypeprovider。因此:

type StringIntCsvType = CsvProvider<Sample = "item_number, num", 
                                    Schema = "item_number (string), num (int)", 
                                    HasHeaders = true>

let buildRowFromObject (row:string * int) = StringIntCsvType.Row(row)

let qry = query {
            for row in tbl1 do
            groupBy row.ItemNumber into g
            select (g.Key,g.Count())
            }

let csvout = qry |> Seq.map  buildRowFromObject
(new StringIntCsvType(csvout)).Save(@"C:\tmp\test.csv")
手册中还有一条关于处理大量行的注释,不确定它与您的相关性如何,但您可以在实例化CsvProvider时设置
CacheRows=false

编辑

有些人认为这可能不相关,但这里是:

#load @"..\..\FSLAB\packages\FsLab\FsLab.fsx"

#r "System.Data.dll"
#r "FSharp.Data.TypeProviders.dll"
#r "System.Data.Linq.dll"
#r @"..\packages\Streams.0.4.1\lib\net45\Streams.dll"
#r @"..\packages\FileHelpers.3.1.5\lib\net45\FileHelpers.dll"

open System
open System.Diagnostics
open System.IO
open System.Collections
open System.Collections.Generic
open System.IO.Compression

open System.Data
open System.Data.Linq
open System.Linq
open Microsoft.FSharp.Data.TypeProviders
open FSharp.Linq
open FSharp.Data
open Nessos.Streams
open FileHelpers

[<Literal>]
let connectionString2 = @"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=C:\Users\...\Documents\test.mdf;Connection Timeout = 60"

type dbSchema = SqlDataConnection<connectionString2,StoredProcedures = true>


type StringIntCsvType = CsvProvider<Sample = "item_number, num", 
                                    Schema = "item_number (string), num (int)", 
                                    HasHeaders = true,CacheRows=false>



let getDbx() = 
    let dbx = dbSchema.GetDataContext()
    dbx.DataContext.ObjectTrackingEnabled <- false  // could impact memory consumption but so far has little effect on time
    dbx.DataContext.CommandTimeout <- 90
    dbx

let dbx = getDbx()

let tbl1 = dbx.MyTable
\load@.\..\FSLAB\packages\FSLAB\FSLAB.fsx
#r“System.Data.dll”
#r“FSharp.Data.TypeProviders.dll”
#r“System.Data.Linq.dll”
#r@..\packages\Streams.0.4.1\lib\net45\Streams.dll”
#r@“.\packages\FileHelpers.3.1.5\lib\net45\FileHelpers.dll”
开放系统
开放系统诊断
开放系统
开放系统。集合
open System.Collections.Generic
开放系统IO压缩
开放系统.数据
开放系统.Data.Linq
开放系统
打开Microsoft.FSharp.Data.TypeProviders
打开FSharp.Linq
打开FSharp.Data
打开尼索斯河
打开文件助手
[]
让connectionString2=@“数据源=(LocalDB)\MSSQLLocalDB;AttachDbFilename=C:\Users\…\Documents\test.mdf;连接超时=60”
类型dbSchema=SqlDataConnection
类型StringIntCsvType=CsvProvider
让getDbx()
设dbx=dbSchema.GetDataContext()

dbx.DataContext.ObjectTrackingEnabled,而不是将整个表带到客户机中,为什么不在
查询中使用
分组依据
?删除
groupBy |>map
调用,并将查询更改为类似
query{for row in db.ThisRow do groupBy row.ItemNumber in g select(g.Key,g.Count())}
当然,我可以这样做,它会加快查询速度,但这对问题的其余部分没有帮助。发生错误的原因是,某些内容需要一个行数组,但您正在传递一行。将其包装在数组或序列中。但通常情况下,您可能需要重新思考方法并将问题分开。如果你能在db端做一些最好的处理,那就是他们的目的。然后将其输出到CSV,您有许多不同的方法,csvprovider、FileHelper,只是普通字符串,您应该能够惰性地处理它。我们能知道涉及的规模吗?这像是一个TB级的数据库,从中提取一个100GB的表吗?你从哪里得到这些错误?我昨天和我的老板谈了Nessos.Streams
,考虑到
Seq
已经很懒了,似乎这里没有必要使用
.Streams
。此外,简化代码以消除管道中的记录类型和分组,可以提供更清晰的代码。我现在遇到了一个新的错误。当我运行最后一行(实际上是在服务器上运行查询)时,我得到一个错误
System.invalidooperationexception:无法将节点“New”格式化为SQL执行。
@Steven好吧,seq是关于迭代器的,如果你把很多东西连在一起,即使它很懒,也会让你陷入困境。我可以假设无论csv部分如何,都会发生此错误。也就是说,如果您执行
qry |>Seq.toList
来实现它,您会得到相同的错误吗?关于设置的更多详细信息,如数据库类型、版本、提供程序类型等,也会有所帮助。上面我使用的是stock SqlDataProvider来访问SQLServer,如果您使用其他东西,例如它可能没有groupby。您是对的:使用
groupby
qry |>Seq.toList
时会发生错误。数据库是SQL Server 11.0.2218,我使用的类型提供程序是Fsharp.Data.TypeProviders.SqlDataConnection
groupBy
确实存在于该提供程序中,因为我已将该函数用于其他应用程序
let writeFile csvout (path:string) =
    use csvtype = new  StringIntCsvType(csvout)
    csvtype.Save(path)
writeFile csvout @"C:\tmp\test2.csv"
#load @"..\..\FSLAB\packages\FsLab\FsLab.fsx"

#r "System.Data.dll"
#r "FSharp.Data.TypeProviders.dll"
#r "System.Data.Linq.dll"
#r @"..\packages\Streams.0.4.1\lib\net45\Streams.dll"
#r @"..\packages\FileHelpers.3.1.5\lib\net45\FileHelpers.dll"

open System
open System.Diagnostics
open System.IO
open System.Collections
open System.Collections.Generic
open System.IO.Compression

open System.Data
open System.Data.Linq
open System.Linq
open Microsoft.FSharp.Data.TypeProviders
open FSharp.Linq
open FSharp.Data
open Nessos.Streams
open FileHelpers

[<Literal>]
let connectionString2 = @"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=C:\Users\...\Documents\test.mdf;Connection Timeout = 60"

type dbSchema = SqlDataConnection<connectionString2,StoredProcedures = true>


type StringIntCsvType = CsvProvider<Sample = "item_number, num", 
                                    Schema = "item_number (string), num (int)", 
                                    HasHeaders = true,CacheRows=false>



let getDbx() = 
    let dbx = dbSchema.GetDataContext()
    dbx.DataContext.ObjectTrackingEnabled <- false  // could impact memory consumption but so far has little effect on time
    dbx.DataContext.CommandTimeout <- 90
    dbx

let dbx = getDbx()

let tbl1 = dbx.MyTable