Sum of array in rails - ruby-on-rails

I have a array suppose
[#<Data id: 1, date: "2016-01-06", value: "1">,
#<Data id: 2, date: "2015-12-31", value: "3">,
#<Data id: 3, date: "2016-01-06", value: "6">...]
and so on..
I want to sum the values having same date
i.e here first and third record are of same date so the result array will give
#<Data id: 1, date: "2016-01-06", value: "1">,
#<Data id: 3, date: "2016-01-06", value: "7">,

Hey you can use try this way if you have already fetch an array from database
arr.group_by{|a| a.date.to_date}.map{|k,v| {k => v.map(&:value).sum()}}
If you are are not fetch array/active record from database you can directly use database query as
If your database stores only date then you can use
Model.group("date").sum(:value)
If your database stores date with time here i have use DATE_FORMAT function for skipping Time part of date
Model.group("DATE_FORMAT(date, '%Y%m%d')").sum(:value)

You can use sql groupping on the model:
Data.where(date:(42.days.ago..Date.today)).group(:date).sum(:value)
This will return a hash of {date => sum}
On an array:
Hash[your_array.group_by(&:date).map{|k,v| [k, v.sum(&:value)]}]

sum = Hash.new(0)
array.each do |data|
<p>sum[data[:date]] += data[:value]</p>
end
# => {:id => 1, "Wed, 04 May 2011" => 300, "Tue, 03 May 2011" => 450...}
# => If you then want this in the same array format you started with:
new_array = sum.collect{ |key, value| {:date => key, :value => value} }
# => [{:id => 1,:date => "Wed, 04 May 2011", :value => 300}, {....}]

Related

Rails update massive data

I have one model call DataIndicator, it contains daily data,
And It has the following column.
:id => :integer,
:date => :datetime,
:dau => :integer,
:login_count => :integer
It had many data, but now I need to change some of it.
How do I massive update its value by date?
EX:
The original
{ "id" => 1, "date" => 2017-01-01 00:00:00 UTC, "dau" => 5 , "login_count" => 150 },
{ "id" => 2, "date" => 2017-01-02 00:00:00 UTC, "dau" => 5 , "login_count" => 140 },
{ "id" => 3, "date" => 2017-01-03 00:00:00 UTC, "dau" => 5 , "login_count" => 300 }
Now I have a hash value, which would be referred to modify the original data.
Like this
update_date = {
"2017-01-01" => {
"dau" => 5,
"login_count" => 5,
},
"2017-01-02" => {},
"2017-01-03" => {
"dau" => 5,
},
...
}
As you can see, the update_date will not contain all attributes, it may only have one or even zero new data.
What is the best way to update this value?
I can only think about the bad one.
Like this
update_date.each do |k, v|
data_by_date = DataIndicator.where(date: DateTime.parse(k)).first
next if data_by_date.nil?
data_by_date.update(v)
end
I think I misread your question.
Wouldn't it be easier if you just picked all DataIndicator records and then checked if the hash contained data for it?
DataIndicator.all.each do |di|
date = di.date.strftime("%Y-%m-%d")
to_update = update_date[date]
next if to_update.blank?
to_update.each do |field,value|
di.send("#{field}=".to_sym, value)
end
di.save!
end
This works for me.
For Rails 4+
DataIndicator.where(["date(date) = ?"], "2017-01-01").update_all(dau: 5, login_count: 5)

Merge like array values to new grouped array?

I need to combine like array values as follows:
From:
arr = ['abc', 'abc', 'eff', 'eff', 'foo', 'bar', 'bar', 'bar']
To:
merged_like_arr = [ ['abc', 'abc'], ['eff', 'eff'], ['foo'], ['bar', 'bar', 'bar']]
Basically have an ActiveRecord object which returns a collection of records in order.
aff=Registration.order('affiliate DESC')
aff.map{|code| code. affiliate }
# produces the following:
# arr = ['abc', 'abc', 'eff', 'eff', 'foo', 'bar', 'bar', 'bar']
I need to be alter the data form arr to merged_like_arr (as shown above) so that I can do:
merged_like_arr.sort_by{|arr|-arr.size}.first(10)
#=> [ ["bar", "bar", "bar"], ["eff", "eff"], ["abc", "abc"] ... ]
The purpose is to find the top 10 affiliates in the system by looking up how many times their affiliate id was used in the registration table.
Alternative implementations are also welcome. Thank you!
The purpose is to find the top 10 affiliates in the system by looking up how many times their affiliate id was used in the registration table.
You can use count with group for that kind of query:
If count is used with group, it returns a Hash whose keys represent the aggregated column, and the values are the respective amounts:
Person.group(:city).count
# => { 'Rome' => 5, 'Paris' => 3 }
In your case:
Registration.group(:affiliate).count
#=> { 'abc' => 2, 'eff' => 2, 'foo' => 1, 'bar' => 3 }
This should work:
arr = ['abc', 'abc', 'eff', 'eff', 'foo', 'bar', 'bar', 'bar']
arr = arr.group_by { |e| e }.values
# => [["abc", "abc"], ["eff", "eff"], ["foo"], ["bar", "bar", "bar"]]

Why ActiveRecord not receiving these getting aggregate functions

Doing a query with aggregate functions directly on ActiveRecord with Postgres seems to be working ok.
ActiveRecord::Base.connection.execute("
SELECT created_at::date as date,
sum(item1_count) as sum_item1,
sum(item2_count) as sum_item2,
sum(item3) as sum_item3 from items
GROUP by
created_at::date ORDER BY date desc").to_a
And returns something like this which is ok.
[
{
"date" => "2014-01-23",
"sum_item1" => "3239",
"sum_item2" => "90",
"sum_item3" => "0.00000"
},
{
"date" => "2014-01-22",
"sum_item1" => "1981",
"sum_item2" => "19",
"sum_item3" => "0.00000"
}
]
The problem is when trying to do the same using scopes, for instance.
class Item < ActiveRecord::Base
scope :myscope, -> {
select("created_at::date as date, sum(item1_count) as sum_item1,
sum(item2_count) as sum_item2,
sum(item3) as sum_item3")
.group("created_at::date")
.order("date desc") }
end
The result here is different. When running user.items.myscope.to_a I get the following result missing the aggregate values and adding an id field that should not be there.
[
#<Item:0x00000103cc3d38> {
:id => nil,
:date => Thu, 23 Jan 2014
},
#<Item:0x00000103cc39a0> {
:id => nil,
:date => Wed, 22 Jan 2014
}
]
How it would be possible to pass the aggregate functions to the scope?

Converting String to Datetime Rails

I'm using LazyHighCharts and trying to convert json data to display only the last 24hrs, I'm having some troubles converting the date and time ("2014-06-16 16:00:00") to milliseconds.
data structure
{"status": "ok", "data": [{"2014-06-16 16:00:00": 24.2},{"2014-06-17 12:00:00": 30.2},{"2014-06-18 17:00:00": 42.9}]} etc
Controller
#data = JSON.parse(open(#temperature.url).read)
dates = []
temps = []
#data['data'].each do |data|
dates << data.keys
temps << data.values
end
datetime = dates.each do |d| DateTime.parse(d).to_i end
#graph = LazyHighCharts::HighChart.new('graph') do |f|
f.chart(:height => '400')
f.yAxis [:title => {:text => "Temperature", :margin => 20, style: { color: '#333'}}]
f.series(:pointInterval => 1.hour, :pointStart => 30.day.ago, :type => 'area', :name => '24hrs', :data => [[datetime, temps]])
f.options[:xAxis] = { :minTickInterval => 24 * 3600 * 1000, :type => "datetime", :dateTimeLabelFormats => { day: "%b %e"}, :title => { :text => nil }, :labels => { :enabled => true } }
end
You need to covert sstring to dateTime as the first,
Use this code:
DateTime.parse("2011-05-19 10:30:14").strftime('%Q')
Or this code:
"2014-06-16 16:00:00".to_datetime.strftime('%Q')
So you can convert array of strings of dates as the following:
dates.map!{|d| d.to_datetime.strftime('%Q')}
Helper links: link-1, link-2
In Rails you can convert a properly formatted string to milliseconds with:
"2014-06-16 16:00:00".to_datetime.strftime('%Q')
You can use Activesupport String#in_time_zone
in_time_zone(zone = ::Time.zone)Link Converts String to a TimeWithZone
in the current zone if Time.zone or Time.zone_default is set,
otherwise converts String to a Time via String#to_time

How would I save multiple records at once in Rails?

How would I save this array in one call with Rails?
tax_rates = [{
:income_from => 0
:income_to => 18200
:start => "01-07-2013"
:finish => "30-06-2014"
:rate => nil
:premium => nil
},{
:income_from => 18201
:income_to => 37000
:start => "01-07-2013"
:finish => "30-06-2014"
:rate => 0.19
:premium => nil
},{
:income_from => 18201
:income_to => 37000
:start => "01-07-2013"
:finish => "30-06-2014"
:rate => 0.19
:premium => nil
}]
Can I just call Rails.create(tax_rates)?
Also, is there a way to remove duplicate symbols so they look neater?
Your example is almost correct.
Use ActiveRecord::Persistence#create, which can accept an array of hashes as a parameter.
tax_rates = [
{
income_from: 0,
income_to: 18200,
start: "01-07-2013",
finish: "30-06-2014",
rate: nil,
premium: nil,
},
{
income_from: 18201,
income_to: 37000,
start: "01-07-2013",
finish: "30-06-2014",
rate: 0.19,
premium: nil,
},
# ...
]
TaxRate.create(tax_rates) # Or `create!` to raise if validations fail
A nice solution is to use the active record import gem. I recommend it over now built-in Rails bulk insert because it's more flexible in the options in case of constraint violation.
TaxRate.import(
[:income_from, :income_to, :start, :finish, :rate, :premium],
tax_rates
)
Its definitely better than my old answer which would trigger a db commit per entry in the array :)
Old answer:
tax_rates.map {|tax_rate| TaxRate.new(tax_rate).save }
This way you'll retrieve an Array with true or false to know which did succeed and which didn't.
If you want all of them to be saved .or, non of them to be saved even if one fails, you can use 'ActiveRecord::Base.transaction'
e.g.
ActiveRecord::Base.transaction do
tax_rate.each do |tax_rt|
TaxRate.new(tax_rt).save
end
end
I am not sure about rails < 4.2 but I have tried it in rails 4.2 you can simply do this
TaxRate.create(tax_rt)
Here is an example like yours:
a = []
a << B.new(:name => "c")
a << B.new(:name => "s")
a << B.new(:name => "e")
a << B.new(:name => "t")
The array is saved all at once with:
a.each(&:save)
This will call B#save on each item in the array.
use a gem 'fast_inserter': https://github.com/joinhandshake/fast_inserter
it generates a single sql query of thousand records.
movie_data = [1, 'Climates (Iklimler)', 'Clay Pauwel', 'Drama'],
[2, 'Tinpis Run', 'Andros Glazer', 'Comedy'],
[3, 'Naked City, The', 'Bethena Chatband', 'Mystery'],
[4, 'Small Time Crooks', 'Naomi Plom', 'Crime'],
[5, 'Shadowboxer', 'Georgeanne Widdicombe', 'Thriller']
params = {
table: 'movies',
static_columns: {
created_at: '0000-00-00 00:00:00',
updated_at: '0000-00-00 00:00:00',
},
options: {
timestamps: false,
unique: true,
check_for_existing: true
},
group_size: 100,
variable_columns: %w(id title director description),
values: movie_data
}
inserter = FastInserter::Base.new(params)
inserter.fast_insert

Resources