【问题标题】:Rails raw query for csv format, to be returned via controllerRails 对 csv 格式的原始查询,通过控制器返回
【发布时间】:2014-02-03 06:23:45
【问题描述】:

我使用活动记录来获取我的故事,然后生成一个 CSV,这是在 rails cast 中完成的标准方式。但是我有很多行,这需要几分钟。我想如果我可以让 posgresql 进行 csv 渲染,那么我可以节省一些时间。

这是我现在拥有的:

query = "COPY stories TO STDOUT WITH CSV HEADER;"
results = ActiveRecord::Base.connection.execute(query);

但是这个查询的结果是空的:

 => #<PG::Result:0x00000006ea0488 @connection=#<PG::Connection:0x00000006c62fb8 @socket_io=nil, @notice_receiver=nil, @notice_processor=nil>> 
2.0.0-p247 :053 > result.count
 => 0 

更好的了解方式:

2.0.0-p247 :059 >   result.to_json
 => "[]" 

我怀疑我的控制器看起来像这样:

format.csv { send_data raw_results }

这适用于普通查询,我只是无法弄清楚将 CSV 结果返回到 rails 的 SQL 语法。

更新

将 CSV 导出从 120000 毫秒降至 290 毫秒

我的模特:

def self.to_csv(story_ids)

    csv  = []
    conn = ActiveRecord::Base.connection.raw_connection
    conn.copy_data("COPY (SELECT * FROM stories WHERE stories.id IN (#{story_ids.join(',')})) TO STDOUT WITH (FORMAT CSV, HEADER TRUE, FORCE_QUOTE *, ESCAPE E'\\\\');") do
      while row = conn.get_copy_data
        csv.push(row)
      end
    end
    csv.join("\r\n")
  end

我的控制器:

send_data Story.to_csv(Story.order(:created_at).pluck(:id))

【问题讨论】:

  • 有什么办法可以直接从 DB 到 send_data 吗?我的意思是,不将其保存到 csv 数组中?
  • @FernandoFabreti 听起来 copy_data 函数会返回需要合并到一个文件中的行。我认为没有某种变量分配的情况下没有任何方法可以组合行。您可能可以从头开始使用字符串并附加到循环中。会对性能差异感兴趣。
  • 我必须将 csv.join("\r\n") 更改为 csv.join("\n") 才能正确生成行。它最初是添加一个额外的换行符。不确定这是否会影响其他非 *nix 机器...
  • @penner 对我来说也很有魅力,感谢您的更新!不过,有两个简单的问题: 1. 当一行由多个涉及关联的复杂 AR 查询生成时,情况如何?然后我们如何生成单个 SQL 查询并在上面的示例中传递它? 2. 虽然它肯定会影响时间方面的性能,但它是否也会影响操作使用的内存?
  • @FernandoFabreti 我最终将答案包装到 Enumerator 中,然后传递给 self.response_body,就像使用的 here 一样。链接的示例不完整,需要lines &lt;&lt; "#{row.length.to_s(16)}\r\n" 才能产生一行以使分块响应起作用。

标签: postgresql csv ruby-on-rails-4 rails-activerecord pg


【解决方案1】:

这个答案建立在@mu-is-too-short 提供的the answer 之上,但没有使用streaming 的临时对象。

headers['X-Accel-Buffering'] = 'no'
headers["Cache-Control"] = 'no-cache'
headers["Transfer-Encoding"] = 'chunked'
headers['Content-Type'] = 'text/csv; charset=utf-8'
headers['Content-Disposition'] = 'inline; filename="data.csv"'
headers.delete('Content-Length')
sql = "SELECT * FROM stories WHERE stories.id IN (#{story_ids.join(',')})"
self.response_body = Enumerator.new do |chunk|
  conn = ActiveRecord::Base.connection.raw_connection
  conn.copy_data("COPY (#{sql.chomp(';')}) TO STDOUT WITH (FORMAT CSV, HEADER TRUE, RCE_QUOTE *, ESCAPE E'\\\\');") do
    while row = conn.get_copy_data
      chunk << "#{row.length.to_s(16)}\r\n"
      chunk << row
      chunk << "\r\n"
    end
    chunk << "0\r\n\r\n"
  end
end

您还可以将gz = Zlib::GzipWriter.new(Stream.new(chunk))gz.write row 与类似的类一起使用

class Stream
  def initialize(block)
    @block = block
  end
  def write(row)
    @block << "#{row.length.to_s(16)}\r\n"
    @block << row
    @block << "\r\n"
  end
end

记住headers['Content-Encoding'] = 'gzip'。另见this gist

【讨论】:

    【解决方案2】:

    AFAIK 您需要在底层 PostgreSQL 数据库连接上使用 copy_data 方法:

    -(对象)copy_data(sql)

    调用序列:

    conn.copy_data( sql ) {|sql_result| ... } -> PG::Result
    

    执行复制过程以将 [sic] 数据传输到服务器或从服务器传输。

    这会通过#exec 发出SQL COPY 命令。对此的响应(如果命令中没有错误)是传递给块的 PG::Result 对象,带有 PGRES_COPY_OUT 或 PGRES_COPY_IN 的状态代码(取决于指定的复制方向)。然后应用程序应使用#put_copy_data#get_copy_data 接收或传输数据行,并在完成后从块中返回。

    还有一个例子:

    conn.copy_data "COPY my_table TO STDOUT CSV" do
      while row=conn.get_copy_data
        p row
      end
    end
    

    ActiveRecord 的原始数据库连接包装器不知道 copy_data 是什么,但您可以使用 raw_connection 来解开它:

    conn = ActiveRecord::Base.connection.raw_connection
    csv  = [ ]
    conn.copy_data('copy stories to stdout with csv header') do
      while row = conn.get_copy_data
        csv.push(row)
      end
    end
    

    这将在csv 中留下一组 CSV 字符串(每个数组条目一个 CSV 行),您可以通过csv.join("\r\n") 获得最终的 CSV 数据。

    【讨论】:

    • 最终不得不使用不同的查询,它可以更好地转义数据。 conn.copy_data("将故事复制到标准输出 (格式 CSV, HEADER TRUE, FORCE_QUOTE *, ESCAPE E'\\\\');").感谢您的帮助!
    猜你喜欢
    • 2012-12-08
    • 1970-01-01
    • 2013-12-02
    • 2018-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多