F# 使用Nessos.Stream减少获取查询输出并将其写入“.CSV”的时间`
在过去,我编写的查询只是获取整个查询,将结果存储在内存中,然后将whote序列提供给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将是惰性的。如果查询结果的数量很小,这不是什么大问题。但是,如果结果的
.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