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.
Related
Below is the query to get the numbers of agents created in each month in last three months.
agents_per_month = Agents.where("created_at > ? AND created_at < ?", Date.today.at_beginning_of_month - 2.months, Date.today).group("date_trunc('month', created_at)").count
The result output is as follows:
{2020-07-01 00:00:00 UTC=>75, 2020-08-01 00:00:00 UTC=>31}
The issue with the above result is that since the last third month had no agents created so it doesn't show any value. But I want it to show result in the order as below:
{2020-06-01 00:00:00 UTC=>0, 2020-07-01 00:00:00 UTC=>75, 2020-08-01 00:00:00 UTC=>31}
So if a month doesn't have any value it should show 0 rather than no value shows up for the particular month.
Please help me resolve this issue.
You can join to a generate_series table using PostgreSQL:
class Agent < ApplicationRecord
def self.totals_in_last_three_months
joins("
RIGHT JOIN generate_series(
date_trunc('month', statement_timestamp() - interval'2 months'),
date_trunc('month', statement_timestamp()),
interval'1 month'
) as months(month)
ON date_trunc('month', agents.created_at) = months.month")
.group('months.month')
.count('agents.*')
end
end
date_start = Time.parse('11/08/2015').beginning_of_day
date_end = Time.parse('11/08/2015').end_of_day
created_at_day_tz = "date(created_at AT TIME ZONE \'UTC\'
AT TIME ZONE \'#{Time.zone.tzinfo.identifier}\')"
users = User.where("users.created_at BETWEEN ? AND ?", date_start, date_end)
Grouping by created_at as created_at_day (date only, new name for the groupped attribute)
grouped_with_timezone_day = users.group(created_at_day_tz).
order(created_at_day_tz).
select("#{created_at_day_tz} as created_at_day, count(*) as count")
# grouped_with_timezone_day.map {|u| [u.created_at_day, u.count] }
# => [[Tue, 11 Aug 2015, 186]]
Grouping by created_at as created_at (date only, same name for the groupped attribute)
grouped_with_timezone = users.group(created_at_day_tz).
order(created_at_day_tz).
select("#{created_at_day_tz} as created_at, count(*) as count")
# grouped_with_timezone.map {|u| [u.created_at, u.count] }
# => [[Mon, 10 Aug 2015 21:00:00 BRT -03:00, 186]]
Why the results differ if the records are the same? Why one result comes with timezone, as DateTime, and the other comes as Date only?
Is activerecord 'casting' to DateTime with Timezone because created_at is defined that way (btw, this makes the dates incorrect in this case)?
The timestamp isn't incorrect - that is, it's 2015-08-11 at midnight UTC - it's just displaying in your local time.
Rails has a bit of special behavior for created_at and updated_at:
The timestamps macro adds two columns, created_at and updated_at. These special columns are automatically managed by Active Record if they exist.
It always treats created_at coming back from a query as a timestamp. Your query returns just the date 2015-08-11, which is interpreted as midnight. When printed, the timestamp is displayed in your locale's timezone (which I presume must be -03:00), leading to 3 hours before midnight on the 11th.
When you name the result created_at_day, you avoid Rails converting it to a timestamp and get just the date you expect.
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]
]
I know that Ruby on rails stores all times in UTC (for created_at and updated_at fields) and when you fetch an active-record object from database, and ask RoR for it's date, it will convert it your configured (in environment.rb) Timezone and show you.
But my case is different. I am building a custom query. And I am adding a where clause manually to it. Where clause is such that: select * where created_at > [user entered date].
Now the problem that's arising is that the user entered date is in UTC - 7 and created_at is in UTC. So I can't really make it work. I could hardcode it like select * where created_at > [user-entered-date] 07:00:00 - but this created problem because of daylight savings, and also doesn't seem like a good solution.
This is not the only problem, the second problem is that when I print out the record.created_at, I am getting UTC date (perhaps because I build a custom query?), which also I don't want to manually (hardcode) convert to local time.
Here's my code for the query:
cond = EZ::Where::Condition.new
if !start_date.empty?
start_date = params[:filter][:start_date].to_date.to_s(:db)
cond.append "(registrations.created_at) >= '#{start_date} 07:00:00'" #Here!
end
if !end_date.empty?
end_date = params[:filter][:end_date].to_date
end_date = end_date + 1.day;
end_date = end_date.to_s(:db)
cond.append "(registrations.created_at) <= '#{end_date} 07:00:00'" #Here!
end
registrations = Registration.all(
:joins => [:event],
:select => 'registrations.id, registrations.first_name, registrations.last_name, registrations.company_name,
(registrations.created_at) AS reg_date, events.name AS evt_name, sum(fees) AS fees, code, events.id AS event_id',
:group => 'registrations.id',
:order => 'evt_name, events.id',
:conditions=> cond.to_sql
)
unless registrations.empty?
registrations.each_with_index do |registration, i|
sheet[ i, 3 ] = (DateTime.strptime(registration.reg_date, "%Y-%m-%d %H:%M:%S") - 7.hours).to_date #Here!
end
end
Try to use TimeWithZone and TimeZone
tz = ActiveSupport::TimeZone.new("Mountain Time (US & Canada)")
...
start_date = tz.local_to_utc(params[:filter][:start_date].to_time).to_s(:db)
...
sheet[ i, 3 ] = registration.reg_date.in_time_zone("Mountain Time (US & Canada)").to_date
I need to retrieve all rows from a table where the created_at timestamp is during a certain hour ... say 04:00 and 05:00. Anyone know how to do this?
RecordNameHere.find_by_sql("SELECT * FROM `table_name_here` WHERE HOUR(created_at) = HOUR('4:01:00')")
The MySQL documentation is awesome: http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_hour
For multiple hour range (eg: records between in 4:00 to 6:00)
User.all(:conditions => "HOUR(created_at) BETWEEN ? AND ?", 4, 5)
For single hour use the following syntax:
User.all(:conditions => "HOUR(created_at) = ?", 4)
Note 1
The HOUR method returns the hour in 24 hour format. Provide the hour value accordingly.