Getting typed results from ActiveRecord raw SQL

后端 未结 6 2025
Happy的楠姐
Happy的楠姐 2020-12-29 20:51

In Sequel, I can do:

irb(main):003:0> DB[\"select false\"].get
=> false

Which returns a false boolean. I\'d like to be able to do so

相关标签:
6条回答
  • 2020-12-29 21:13

    Pretty ugly but does what you are asking for:

    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
      }
    }
    

    gives:

    [[1, false, "123", [1, 2]]]
    

    How it works:

    res.column_types
    

    returns a hash of columns names and Postgresql column types

    Here is a pointer to how it works: https://github.com/rails/docrails/blob/fb8ac4f7b8487e4bb5c241dc0ba74da30f21ce9f/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb

    0 讨论(0)
  • 2020-12-29 21:16

    I don't know if it is the way, but you can create activerecord model without table with sort of fake column:

    class FunctionValue < ActiveRecord::Base
      def self.columns
        @columns ||= [];
      end
    
      def self.column(name, sql_type = nil, default = nil, null = true)
        columns << ActiveRecord::ConnectionAdapters::Column.new(
          name.to_s,
          default,
          sql_type.to_s,
          null
        )
      end
    
      column :value, :boolean
    end
    

    And then you can run this:

    function_value = FunctionValue.find_by_sql('select false as value').first
    function_value.value
    
    0 讨论(0)
  • 2020-12-29 21:28

    Don't have enough reputation points to respond, but Bjorn's answer and associated replies are broken in Rails 5. This works:

    res = ActiveRecord::Base.connection.select_all(sql)
    res.to_a.map{|o| o.each{|k, v| o[k] = res.column_types[k].cast v}}
    
    0 讨论(0)
  • 2020-12-29 21:29

    While I have no doubt that Björn Nilsson's answer worked when he posted it, it is failing for me with Postgres 9.4 and PG gem version 0.18.2. I have found the following to work after looking through the PG gem documentation:

    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}
    
    0 讨论(0)
  • 2020-12-29 21:30

    In Rails 6, Person.connection.select_all(sql_query).to_a

    ...will return an array of hashes whose values are type-casted. Example:

    [{"id"=>12, "name"=>"John Doe", "vip_client"=>false, "foo"=> nil, "created_at"=>2018-01-24 23:55:58 UTC}]
    

    If you prefer an OpenStruct, use Mike's suggestion:

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

    If you prefer symbols as keys, call map(&:symbolize_keys) after to_a.

    0 讨论(0)
  • 2020-12-29 21:37

    This works for me in rails 5

    results = ActiveRecord::Base.connection.select_all(sql)
    results.rows.map{ |row| Hash[results.columns.zip(row)] }
    

    Gives nice results

    [{"person1"=>563, "person2"=>564, "count"=>1},
     {"person1"=>563, "person2"=>566, "count"=>5},
     {"person1"=>565, "person2"=>566, "count"=>1}]
    
    0 讨论(0)
提交回复
热议问题