从ActiveRecord原始SQL获取类型化结果

Joe*_*Dyk 26 ruby postgresql activerecord

在续集中,我可以做到:

irb(main):003:0> DB["select false"].get
=> false
Run Code Online (Sandbox Code Playgroud)

返回false布尔值.我希望能够在ActiveRecord中做类似的事情:

irb(main):007:0> ActiveRecord::Base.connection.select_value "select false"
=> "f"
Run Code Online (Sandbox Code Playgroud)

如您所见,它返回字符串"f".有没有办法用ActiveRecord获取一个假布尔值?(类似地,我可能正在调用一个返回timestamptz,数组等的函数 - 我希望返回的值具有正确的类型)

我的用例:我正在调用数据库函数,想要取回一个键入的结果而不是一个字符串.

Ram*_*ord 23

虽然我毫不怀疑BjörnNilsson的答案在他发布时起作用,但对于Postgres 9.4和PG宝石版来说,它是失败的0.18.2.在查看PG gem文档后,我发现以下内容有效:

pg = ActiveRecord::Base.connection
@type_map ||= PG::BasicTypeMapForResults.new(pg.raw_connection)

res = pg.execute("SELECT 'abc'::TEXT AS a, 123::INTEGER AS b, 1.23::FLOAT;")
res.type_map = @type_map
res[0]
# => {"a"=>"abc", "b"=>123, "float8"=>1.23}
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这个解决方案.它还保留列名称.你知道每次运行PG :: BasicTypeMapForResults.new(pg.raw_connection)会有很大的性能损失吗?它似乎获得了DB中所有列的所有类型... (2认同)
  • `pg.raw_connection.type_map_for_results = PG::BasicTypeMapForResults.new(pg.raw_connection)` 作为第二行也有效。在这种情况下,无需为单个结果分配 *type_map*。 (2认同)

Bjö*_*son 14

非常丑陋,但做你要求的:

res = ActiveRecord::Base.connection.
select_all("select 1 as aa, false as aas, 123::varchar, Array[1,2] as xx")

# Breaks unless returned values have unique column names
res.map{|row|row.map{|col,val|res.column_types[col].type_cast val}}

# Don't care about no column names
res.map{|row|
  row.values.map.with_index{|val,idx|
    res.column_types.values[idx].type_cast val
  }
}
Run Code Online (Sandbox Code Playgroud)

得到:

[[1, false, "123", [1, 2]]]
Run Code Online (Sandbox Code Playgroud)

这个怎么运作:

res.column_types
Run Code Online (Sandbox Code Playgroud)

返回列名和Postgresql列类型的哈希

这是一个指向它如何工作的指针:https: //github.com/rails/docrails/blob/fb8ac4f7b8487e4bb5c241dc0ba74da30f21ce9f/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb

  • 您应该使用`type_cast_from_database`而不是`send(:type_cast`,在此处找到它:http://www.rubydoc.info/gems/activerecord/4.2.0/ActiveRecord/ConnectionAdapters/PostgreSQL/OID/Array (5认同)
  • 甜!谢谢.似乎应该有一个`select_rows_typed`方法或AR中的某些东西.如果你关心性能,能够获得原始字符串值是很好的,但是对于大多数情况,你想要获得一个类型化的结果. (2认同)
  • 现在看来#type_cast已成为一种私人方法.我不得不更新到:res.map {| row | row.map {| col,val | res.column_types [col] .send(:type_cast,val)}} (2认同)

una*_*ged 8

没有足够的声誉点来回应,但是Bjorn的回答和相关的回复在Rails 5中被破坏.这有效:

res = ActiveRecord::Base.connection.select_all(sql)
res.to_a.map{|o| o.each{|k, v| o[k] = res.column_types[k].cast v}}
Run Code Online (Sandbox Code Playgroud)


Bru*_*cca 2

在 Rails 6 中,Person.connection.select_all(sql_query).to_a

...将返回一个哈希数组,其值是类型转换的。例子:

[{"id"=>12, "name"=>"John Doe", "vip_client"=>false, "foo"=> nil, "created_at"=>2018-01-24 23:55:58 UTC}]
Run Code Online (Sandbox Code Playgroud)

如果您更喜欢OpenStruct,请使用迈克的建议:

Person.connection.select_all(sql_query).to_a.map {|r| OpenStruct.new(r) }

如果您更喜欢将符号作为键,请map(&:symbolize_keys)在 后调用to_a