Sum of table attributes when value is string - ruby-on-rails

I've never came across this before. I'm working with a table attribute whos value is a string, not float/int.
Model.first.amount => "58.00"
I need to sum up all amount. What I'm used to, with the amount being a float, would be:
Model.all.sum(&:amount) => # total value
Took a wild guess with:
Model.all.sum(&:amount.to_i) # undefined method `to_i' for :amount:Symbol
Is there a clean way to sum up the amount? Or convert the database to float?

Processing database with Ruby is memory inefficient.
First shot:
Model
.pluck(:amount) # will fire sql
.sum(&:to_f) # convert to float, operating on resulting Array, not AR and sum
But the most effective way to process database data is SQL of course:
Model.sum("CAST(COALESCE(amount, '0') AS DECIMAL)")
coalesce will replace null values with '0'
sum all values casted to DECIMAL.

In pure Ruby you can use method inject.
Model.all.inject(0) { |sum, object| sum += object.amount.to_i }

I dont have commenting permissions but this should work for ruby:
Model.all.map(&:to_f).reduce(&:+)

Related

Ruby on Rails query returns extra column

I am working with Ruby 2.0.0 and Rails 4.0.9, on Oracle 11g database.
I query the database to get pairs of values [date, score] to draw a chart.
Unfortunately, my query returns triplets such as [date, score, something], and the chart fails.
Here is the query:
#business_process_history = DmMeasure.where("period_id between ? and ? and ODQ_object_id = ?",
first_period_id, current_period_id, "BP-#{#business_process.id}").
select("period_day, score").order("period_id")
Here is the result in the console:
DmMeasure Load (1.2ms) SELECT period_day, score FROM "DM_MEASURES" WHERE (period_id between 1684 and 1694 and ODQ_object_id = 'BP-147') ORDER BY period_id
=> #<ActiveRecord::Relation [#<DmMeasure period_day: "20140811", score: #<BigDecimal:54fabf0,'0.997E2',18(45)>>,
#<DmMeasure period_day: "20140812", score: #<BigDecimal:54fa7e0,'0.997E2',18(45)>>, ...]
Trying to format the result also returns triplets:
#business_process_history.map { |bp| [bp.period_day, bp.score] }
=> [["20140811", #<BigDecimal:54fabf0,'0.997E2',18(45)>],
["20140812", #<BigDecimal:54fa7e0,'0.997E2',18(45)>], ...]
Where does this come from?
How can I avoid this behaviour?
Thanks for your help,
Best regards,
Fred
what triplets? From what I can see, you have two attributes per item: 'period_day' (a string representing a date) and 'score' (a BigDecimal representation of a single number).
The ruby BigDecimal is just one way of representing a number.
eg if you play around with them in the rails console:
> BigDecimal.new(1234)
=> #<BigDecimal:f40c6d0,'0.1234E4',9(36)>
The first part, as you can see, a bit like scientific notation, it contains the significant digits and precision.
To figure out what the 18(45) is, I had to dig into the original c-code for the BigDecimal#inspect method.
Here's the commenting for that method:
/* Returns debugging information about the value as a string of comma-separated
* values in angle brackets with a leading #:
*
* BigDecimal.new("1234.5678").inspect ->
* "#<BigDecimal:b7ea1130,'0.12345678E4',8(12)>"
*
* The first part is the address, the second is the value as a string, and
* the final part ss(mm) is the current number of significant digits and the
* maximum number of significant digits, respectively.
*/
The answer being: the current number of significant digits and the maximum number of significant digits, respectively

Rails 4 where,order,group,count include zero's - postgresql

Here is my query:
User.where("created_at >= ? AND created_at <=?", date1,date2).order('DATE(created_at) DESC').group("DATE(created_at)").count
and I get output as:
{Thu, 15 May 2014=>1}
But I want to get output as 0 for the rest of the days. For ex
{Thu, 15 May 2014=>1,Fri, 15 May 2014=>0}
What I want to get is Users created in a date range, ordered and grouped by created_at and number of such Users for each day. When no users are there for a particular day it should return 0, which the current query doesn't return.
I agree with Himesh
Try this:
User.where("created_at >= ? AND created_at <=?", date1,date2).order('DATE(created_at) DESC').group("DATE(created_at)").count
Which will give:
{Thu, 15 May 2014=>1}
Get the date range as hash initialized to 0:
hash1 = Hash[(date1..date2).collect { |v| [v, 0] }]
Now merge the two hashes:
hash = hash1.merge(hash)
Do remember to merge hash with hash1 and not the vice-versa, because we want to overwrite the value of keys from hash to hash1
Although it doesn’t seem to be well known, Ruby hashes have a “default value” feature that will help you do what you want in a better way. Another way of expressing what you’re trying to do is this:
Create a hash that gives 0 for any key within my data range unless I have stored some other value under that key.
You can accomplish exactly this using a block to establish the default value of the hash:
#data = Hash.new do |h, key|
if (date1..date2).include?(key)
0
else
nil
end
end
Or, more succinctly:
#data = Hash.new {|h, key| (date1..date2).include?(key) ? 0 : nil }
Then load your data from the database and merge it into this hash:
#data.merge! User.where(…).group(…).count
Note: Your order() is irrelevant because you are grouping. It will probably get optimized away, but you can simply leave it off and get the same result.
Now if you do #data[foo]:
if foo is not comparable to a date you will get nil
if foo is outside the date range you will get nil
if foo is within the date range but no data was returned by your query you will get 0
if foo is a date with data in the database you will get the correct count
This method is preferable to pre-loading the hash because it will work efficiently (in time and space) even for very large date ranges. And arguably it is more straightforward too.
I created a gem exactly for this - check out Groupdate.
Perhaps, this is a bit difficult as if any user is not created on 16th May then there would be no record of 16th May as created_at in the DB and group by query result wont contain any value for 16th May.
Probably you will have to handle this in ruby. Or what you can do is check if a particular date is present as key in the hash_result, and if it is not present then user count is 0 by default.
Hope this helps :)

Doing calculation in ruby on rails Model.sum()

I have a model called Data and some columns called timestamp, value1 and value2. I would like to use it with highstock chart.
Before the chart is printed I would like some calculations on it:
Summarize the result of value1 devided by value2 (value1/value2) by each day or month or year and put it in an array like [[timestamp_day, value1/value2], [...], ...].
I'm able to do the "timestamp-grouping". But I'm hanging on summarize the value1/value2.
Is there a way to do it like .sum(value1/value2)? Or is there any way to define a virtual column that does the calculation?
Thanks & best regards, Andreas
If you're trying to grab the calculated values right from the database you can pass an expression into the active record sum function.
Data.group(:timestamp).sum("value1 / value2")
#array = #collection.collect { |c| [v.timestamp, (v.value1+v.value2 /3)] }
#collection is the collection/array of all your data
#array will be of this format after collect is executed: [[timestamp_day, value1/value2], [...], ...]
The one thing that isn't clear is what the denominator is. I use "3" here but could be anything you want. You could even call up another method to get it if it's a complex operation.

How do I count items in an array that have a specific attribute value?

In my application, I have an array named #apps which is loaded by ActiveRecord with a record containing the app's name, environment, etc.
I am currently using #apps.count to get the number of apps in the array, but I am having trouble counting the number of applications in the array where the environment = 0.
I tried #apps.count(0) but that didn't work since there are multiple fields for each record.
I also tried something like #apps.count{ |environment| environment = 0} but nothing happened.
Any suggestions?
Just use select to narrow down to what you want:
#apps.select {|a| a.environment == 0}.count
However, if this is based on ActiveRecord, you'd be better off just making your initial query limit it unless of course you need all of the records and are just filtering them in different ways for different purposes.
I'll assume your model is call App since you are putting them in #apps:
App.where(environment: 0).count
You have the variable wrong. Also, you have assignment instead of comparison.
#apps.count{|app| app.environment == 0}
or
#apps.count{|app| app.environment.zero?}
I would use reduce OR each_with_object here:
reduce docs:
#apps.reduce(Hash.new(0)) do |counts, app|
counts[app.environment] += 1
counts
end
each_with_object docs:
#apps.each_with_object(Hash.new(0)) do |app, counts|
counts[app.environment] += 1
end
If you are able to query, use sql
App.group(:environment).count will return a hash with keys as environment and values as the count.

activerecord sum returns a string?

This seems very strange to me, an active record sum returns a string, not a number
basket_items.sum("price")
This seems to make it work, but i thought i may have missed something, as this seems like very strange behaviour.
basket_items.sum("price").to_i
According to the (rails 2.3.9) API:
The value is returned with the same data type of the column, 0 if there’s no row
Could your price column be a string or text?
https://github.com/rails/rails/pull/7439
There was a reason it returned a string - calling to_d on a Fixnum in Ruby 1.8 would give a NoMethodError. This is no longer the case in Ruby 1.9 so it's probably okay to change.
ActiveRecord sum:
Difference:
1) basket_items.sum("price")
It will also sum non integer also and it will return non integer type.
2) basket_items.sum("price").to_i
This above will convert into integer.
# File activerecord/lib/active_record/relation/calculations.rb, line 92
def sum(*args)
if block_given?
self.to_a.sum(*args) {|*block_args| yield(*block_args)}
else
calculate(:sum, *args)
end
end
Calculates the sum of values on a given column. The value is returned with the same data type of the column, 0 if there’s no row.
http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-sum
Github:
https://github.com/rails/rails/blob/f8f4ac91203506c94d547ee0ef530bd60faf97ed/activerecord/lib/active_record/relation/calculations.rb#L92
Also see, Advanced sum() usage in Rails.

Resources