在Clojure中将大文件写入磁盘

4

我有一个SQL查询,返回了大量的行。我想将查询结果保存到磁盘上的CSV文件中。然而,由于有太多的行,所以在SQL查询聚合所有行之前就会用完内存。

它看起来像这样,并且在查询部分失败:

(-> query format-transformer csv-writer)
1个回答

5

我在 #clojure irc 频道得到了一些帮助,现在我已经理解了。你需要两个库:

(ns myproj.example
  (:require [[clojure.java.jdbc :as sql]
             [clojure.data.csv :as csv]]))

诀窍在于处理每行返回的数据,然后丢弃它。Java JDBC库提供了查询函数,可以使用:row-fn:result-set-fn选项来实现此功能。

(defn sql->csv  [title]
  (with-open [w (clojure.java.io/writer (str title ".csv") :append true)]
   (sql/query ds (second query)
             :row-fn (fn [row]
                       (csv/write-csv w  [(mapv str (vals row))]))
             :result-set-fn dorun)))

有趣的部分是 :row-fn:result-set-fn
每次从查询返回一行时,都会调度 :row-fn。每行都是一个形如 {:column1 "data" :column2 "data2"} 的映射。我们使用 [(mapv str (:vals row)]) 将映射转换为 write-csv 可以使用的东西(嵌套向量)。然后 write-csv 将其附加到您提供的文件中。 :result-set-fn 对于不使堆栈溢出至关重要。通过将其设置为 dorun,您告诉它在处理时丢弃头部。
这个特定的实现没有提供列标题,这留给读者自己练习(我的太过粗糙,无法发布在公共论坛上)。

我认为你的:row-fn现在为每一行打开和关闭文件。更糟糕的是,它必须扫描整个文件以便写入每一行。虽然我没有测试过,但我认为如果你将对with-open的调用移到sql/query之外,这可能会更有效率。我还认为你实际上想设置:result-set-fn而不是:row-set-fn,根据最新的文档。我猜测默认的:result-set-fndoall函数会起到同样的作用,或者你正在使用旧版本。 - schaueho
@schaueho:好观点,谢谢。我已经测试了更改,并且在with-open内部速度更快。我尝试将write-csv函数放入:result-set-fn中,但最终返回的nil与行数相同。 - detran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接