Each User has many Purchases. I want to find the sum for each of the previous 28 days.
t1 = Time.now - (28.days)
t2 = Time.now
#dailysum = Array.new(29)
(0..29).each do |i|
#dailysum[i] = #user.purchases.where(:created_at => (Time.now-(i.day))..(Time.now-((i-1).days))).sum(:amount)
end
This works, but I'm certain there is a much better way of going about this. Any suggestions?
You need to pass a :group option to sum. The value of the :group option is different based on your db. I'll provide a pg and mysql version.
# purchases.rb
def self.recent(num)
where('created_at > ?', num.days.ago
end
# PG version
#user.purchases.recent.sum(:amount, group: "DATE_PART('year', purchases.created_at) || '-' || DATE_PART('month', purchases.created_at) || '-' || DATE_PART('day', purchases.created_at)")
# mysql version
#user.purchases.recent.sum(:amount, group: "DATE(purchases.created_at)")
this will result in a hash where the keys is the date and the values are the sum of the purchases.
This will result in 27 less queries compared to querying for each day for the last 28 days
/!\ SEE MY OTHER (BETTER) ANSWER
This answer will calculate the whole sum of purchases of the last 28 days
Read my other answer for the calculation of the sum of each last 28 days.
I keep this one online for those who can be interested by it.
#user.purchases
.where('created_at > ?', 28.days.ago) # retrieve all purchases within the last 28 days
.sum(:amount) # get the sum of the amount column
If you want to use a scope, as proposed by #andy :
# in purchase.rb
scope :recent, ->(d) { where('created_at > ?', d.days.ago) }
# then in you controller
#user.purchases
.recent(28) # retrieve all purchases within the last 28 days
.sum(:amount) # get the sum of the amount column
Now that I've better understood the question, here is a second try.
It's hard to do this out of context.
user.purchases
.where('created_at > ?', 28.days.ago)
.group_by { |p| p.created_at.to_date }
.collect { |d, ps| [d, ps.sum { |p| p.amount }] }
That should return an array of arrays containing each the date and the amount sum for this date :
[
["Mon, 28 Jan 2013", 137],
["Tue, 29 Jan 2013", 49]
["Wed, 30 Jan 2013", 237]
]
Related
[{:listing_id=>1, :vote_size=>1, :created_at=>Wed, 13 Nov 2019 02:19:45 UTC +00:00},
{:listing_id=>2, :vote_size=>0, :created_at=>Thu, 14 Nov 2019 02:19:45 UTC +00:00},
{:listing_id=>3, :vote_size=>0, :created_at=>Fri, 15 Nov 2019 02:19:45 UTC +00:00}]
I have the following hash of listing IDs, the number of votes and the created_at date for said listing in Ruby (higher vote_size is better), and I'd like to rank them. In other words, I want to get the rank from some sort of function and then update the rank on the listing via listing.update_attribute(:rank, function-call) or something of that sort. Rank 1 is the best. If there are listings with the same amount of votes only one of them should get the rank and the other listing should get a rank below. (Let's say the tiebreaker is the listing created_at date, whoever created the listing first gets the higher rank.)
This is what I have so far and well I'm stuck lol and could really use some help.
namespace :server do
desc "update the listings rank"
task update_listing_rank: :environment do
listings = Listing.all
all_listings_with_votes = total_votes_for_listing listings
all_listings_with_votes.map{ |e|
puts all_listings_with_votes.index(e) + 1
}
end
def total_votes_for_listing listings
listings.map do |listing|
{listing_id: listing.id, vote_size: listing.votes.size, created_at: listing.created_at}
end
end
end
Here's a big hint... Take your array and
arr.sort{|x,y| x[:vote_size] <=> y[:vote_size]}
This will give you a sorted array of vote sizes.
You can try this, that means we sort by combination vote_size and created_at, vote_size descending and created_at ascending in case there are two equal values for vote_size
an_array.sort_by {|a| [-a[:vote_size] , a[:created_at]] }
I have Payment model with data and amount attributes. Now I need to get all the records for the current week on the page. I want to use some kind of pagination, e.g.: On the first page I get all records for the current week, on the second page - records from the previous week etc.
And on every page I need to get total amount for this week and average amount per day.
So, I have two questions:
How to get all the records for particular week on the page?
How to count amount for this week?
Now everything I've done was array with weeks and amounts
def self.count_by_week
raw_result = group_by_week_and_state.count
# {['2014-12-01-2014-12-07', 'foo'] => 100, ['2014-12-01-2014-12-07', 'bar'] => 100, '...' => '...'}
raw_result.each_with_object({}) do |(k, v), result|
result[k[0]] ||= {}
result[k[0]][k[1]] = v
end
end
def self.group_by_week_and_state
group("#{weekday_query(0)} || \'-\' || #{weekday_query(6)}").group('amount')
end
# build sql part for day offset of week (0 => mon, 6 => sun)
def self.weekday_query(offset)
"to_char(cast(date_trunc(\'week\', created_at) as date) + #{offset}, \'YYYY-MM-DD\')"
end
You could use the groupdate gem to accomplish this.
https://github.com/ankane/groupdate
Once you have successfully grouped your records, it's just too simple to get the sum for each groups.
I want to find signup count daily, for the date range say this month. so
starts_at = DateTime.now.beginning_of_month
ends_at = DateTime.now.end_of_month
dates = ((starts_at.to_date)..(ends_at.to_date)).to_a
dates.each_with_index do |date,i|
User.where("created_at >= ? and created_at <= ?", date, date.tomorrow)
end
So nearly 30 queries running, how to avoid running 30 query and do it in single query?
I need something like
group_by(:created_at)
But in group by if there is no data present for particular date it's showing nothing, but I need date and count as 0
I followed this:
How do I group by day instead of date?
def group_by_criteria
created_at.to_date.to_s(:db)
end
User.all.group_by(&:group_by_criteria).map {|k,v| [k, v.length]}.sort
Output
[["2016-02-05", 5], ["2016-02-06", 12], ["2016-02-08", 6]]
There is no data for 2016-02-05 so it should be included with count 0
I can't test it at the moment, but it should be possible to filter your date range and group it with a little help of your dbms like this:
User.select('DATE(created_at)').where("created_at >= ? and created_at <= ?", DateTime.now.beginning_of_month, DateTime.now.end_of_month).group('DATE(created_at)').count
Would this do?
starts_at = DateTime.now.beginning_of_month
ends_at = DateTime.now.end_of_month
User.where(created_at: starts_at..ends_at).group("date(created_at)").count
# => {Tue, 09 Feb 2016=>151, Mon, 08 Feb 2016=>130}
Note that you won't get any results for dates when there has been zero creations, so you might want to do something like this:
Hash[*(starts_at..ends_at).to_a.flat_map{|d| [d, 0]}].merge(
User.where(created_at: starts_at..ends_at).group("date(created_at)").count
)
Not pretty, but what happens there is you first create a hash with all dates in the range having zero values and merging the results from database into that hash.
Having pulled donations from the past two years, I'm trying to derive the sum of those donations per month (a total of 24 sums), storing the keys (each month) and the values (the sum of donations for each month) in an array of hashes.
Here's what I've got so far:
#donations = Gift.between(Date.today - 2.years, Date.today, :field => gift_date)
#donations.each do |donation|
#logic here that puts gift_amount into the right month (of the 24 months)
# and adds it to the previous balance for that month, ending up with 24 keys
# and sum values.
end
How can I do this in Ruby/Rails?
Continuing where #mbratch left off:
donations = Gift.where(:date => (Date.today - 2.years)..Date.today)
Hash[donations.group_by { |d| [d.date.year, d.date.month] }
.map do |year_month, donations|
[year_month, donations.map(&:amount).reduce(:+)]
end]
donation_hash = Hash.new(0)
Gift.where(:gift_date => (Date.today - 2.years)..Date.today).each do |donation|
donation_hash[[donation.gift_date.month, donation.gift_date.year]] += donation.amount
end
This will create a hash with keys of [month, year] and value of total amount donated for that month/year. There are probably a few other suitable ways to create a key which meets the need of your application.
I have a table with a float called 'cost' and timestamp called'created_at'.
I would like it to output an array with the summing the costs for each particular day in the last month.
Something like:
#newarray = [] #creating the new array
month = Date.today.month # Current Month
year = Date.today.year # Current Year
counter = 1 # First Day of month
31.times do #for each day of the month (max 31)
#adding sales figures for that day
#newarray.push(Order.sum(:cost, :conditions => {:created_at => "#{year}-#{month}-#{counter}"}))
counter = counter + 1 #go onto next day
end
However this doesn't work as all the timestamps have a time as well.
Apologies in advance for the poor title, I can't seem to think of a sensible one.
You should be able to use code like the following:
sales_by_day = Order.sum(:cost,
:group => 'DATE(created_at)',
:conditions => ['DATE(created_at) > ?', 31.days.ago])
(0..30).collect { |d| sales_by_day[d.days.ago.to_date.to_s] || 0 }.reverse
This will sum the order cost by day, then create an array with index 0 being the last 24 hours. It will also take only one query, whereas your example would take 31.
To change the cut off date, replace 31.days.ago with an instance of the Time class. Documentation here: http://ruby-doc.org/core/classes/Time.html
Good luck!
This should work:
(Date.today.beginning_of_month..Date.today.end_of_month).map { |d|
Order.sum(:cost, :conditions => ['created_at >= ? AND created_at < ?', d, d+1])
}
Although I think you should try getting it using a single query, instead of making one for each day.