I'm trying to iterate over each week in the calendar year and run a query.
range = Date.new(2020,3,16)..Date.new(2020,3,22)
u = User.where(created_at: range).count
But I'd like to do this for EACH week in another range (say since the beginning of this year).
Ruby's Date has a cweek function that gives you the week number but there doesn't seem to be a way to easily get from the week number to the date range.
Anyway, not sure how helpful cweek will be as I need week to run Sunday -> Saturday.
Thoughts?
I'm assuming this is Postgres and the model name is User based on your previous question.
If this blog is to to believed you can shift a date one day to get sun-sat day week.
User.group("(date_trunc('week', created_at::date + 1)::date - 1)")
.count
If you want the to select the actual week number while you are at it you can select raw data from the database instead of using ActiveRecord::Calculations#count which is pretty limited.
class User
# #return [ActiveRecord::Result]
# the raw query results with the columns count, year, week
def self.count_by_biblical_week
connection.select_all(
select(
"count(*) as count",
"date_part('year', created_at)::integer as year",
"(date_part('week', created_at::date + 1) - 1)::integer as week"
).group(:week, :year)
)
end
end
Usage:
results = User.where(created_at: Date.new(2020,3,16)..Date.new(2020,3,22))
.count_by_biblical_week
results.each do |row|
puts [row[:year], row[:week], row[:count]].join(' | ')
end
Adding the year to the group avoids ambiguity if the results span multiple years.
Related
Using Rails 4, I have a set of data where I only want the data points where the date is on the last day of the month, any month, but only where the date is the last day of the month.
Below I set up the #data variable all my statistics data limiting it to Statistics where the date is in the last year. I would also like to limit the data to only contain Statistics that have a date of the last day of the month.
EDIT
- Note that the start date is a variable and can be changed by a parameter
startdate = Date.1.year.ago
#data = Statistic.where(date: startdate..Date.today).all
Is there a simple way to add another .where clause and use end_of_month for this?
Write this scope in your Statistics model:
scope :last_date_stats, ->(dates) { where(date: dates) }
Populate a dates array in your StatisticsController and then use the scoped query to get specific data:
dates = (0..11).to_a.map { |n| n.months.ago.end_of_month }
#last_date_statistics = Statistics.last_date_stats(dates)
I have a Rails model DailyAssignment with a date column, and would like to find the first date after today which does not have a DailyAssignment associated with it.
For instance, if I have an instance today, no instance tomorrow, and an instance the day after tomorrow, this method should return tomorrow.
If I were to do this in Ruby, it would be something like:
(Date.today..1.year.since.to_date).find do |date|
DailyAssignment.where(date: date).empty?
end
This is medium okay since it will terminate the iteration once it finds a record, but has two issues:
Iterating through a collection in Ruby is slow.
Barring some sort of while construct, I need to specify an 'end' date.
Is there a nice, efficient way to do this in PostgreSQL?
If you can, you should use a custom query to search through your database (these kind of searches are a lot faster within the DB).
If you search for a date within a time range, you can use the
generate_series(timestamp, timestamp, interval) function:
select s
from generate_series(?, ? + interval '1 year'), interval '1 day') s
left join daily_assignment on s = "date"
where "date" is null
limit 1
If you have no real upper bound, you can use a self-join to get the next free date:
select coalesce(
(select c."date" + interval '1 day'
from daily_assignment c
left join daily_assignment n on n."date" = c."date" + interval '1 day'
where c."date" > ? - interval '1 day'
and n."date" is null
order by c."date"
limit 1),
? + interval '1 day'
)
? marks mean the parameter of today (you may need casts, depending on your input); you could use now() instead, if you prefer.
P.S.: please, do not use date as a column name, it is a reserved word in SQL, and tells nothing about the column itself. Instead, you can use names like created_at, updated_at, happens_at, etc. or even at_date.
What I propose is to do 1 select query between dates, then loop your results and compare them with your selected results.
# select all dailyassignments
results = DailyAssignment.where("date >= from_date AND date <= to_date")
not_found_dates = []
(Date.today..1.year.since).find do |date|
found_assignment = results.detect {|instance| instance.date == date }
not_found_dates << date if found_assignment.nil?
end
You can try it this way:
def first_date_without_assignment
assignments = DailyAssignment.select('date').where('date > ?', Date.today)
return Date.tomorrow if assignments.empty?
assignment_dates = assignments.map(&:date)
date_range = (Date.tomorrow..(assignment_dates.last.advance(days: 1)).to_a
(date_range - assignment_dates).first
end
I didn't test it so I could mistype something, but it could work. I also find this, it should work on postgres http://www.postgresql.org/message-id/4F96EC90.6070600#encs.concordia.ca but it could be quite hard to write in rails or at least bad looking.
I have the an Order model in the following
If today is 5/3
and I want to sum the previous 3 months data of order, how to do it ?
I mean I want to show the 2/1 ~ 4/30 excluding the orders in May.
If today is 2014/4/20, and I want to show the sum of previous 3 weeks data. 2014/2/1~2/15
How to do it in Rubic way ?
You want something along the lines:
date = DateTime.now.utc
Order.where('created_at >= ? and created_at <= ?', date.beginning_of_month, date.utc.end_of_month).sum('price')
Where price is the column you want to sum.
You can reuse the logic of #Santosh in order to get the dates you want =)
start_date = 3.months.ago.beginning_of_month
end_date = 1.month.ago.end_of_month
You can write your query based on these dates. Same logic can be applied for weeks also.
I'm trying to create an array that has the total of all sales a company does for each day of the week. Right now I have something similar to this
#sales = Sale.all
#sales_total = #sales.map{|sale|sale.total.to_i}
Which returns an array of each total for every single sale.
Before turning it into an array, how can I group Sale by day, and adding total together?
I'm a fan of groupdate gem.
You ca do something like:
#sales_total = Sale.group_by_day(:created_at).order("day asc").sum(:total)
Assuming total is the column you want to sum up.
EDIT - To add a interval of time condition you can use where, for example:
#sales_total = Sale.where('created_at between ? and ?', Date.today, 1.week.ago).group_by_day(:created_at).order("day asc").sum(:total)
In my application I have a variety of date sequences, such as Weekly, Monthly and Annually. Given an arbitrary date in the past, I need to calculate the next future date in the sequence.
At the moment I'm using a sub-optimal loop. Here's a simplified example (in Ruby/Rails):
def calculate_next_date(from_date)
next_date = from_date
while next_date < Date.today
next_date += 1.week # (or 1.month)
end
next_date
end
Rather than perform a loop (which, although simple, is inefficient especially when given a date in the distant past) I'd like to do this with date arithmetic by calculating the number of weeks (or months, years) between the two dates, calculating the remainder and using these values to generate the next date.
Is this the right approach, or am I missing a particularly clever 'Ruby' way of solving this? Or should I just stick with my loop for the simplicity of it all?
Because you tagged this question as ruby-on-rails, I suppose you are using Rails.
ActiveSupport introduces the calculation module which provides an helpful #advance method.
date = Date.today
date.advance(:weeks => 1)
date.advance(:days => 7)
# => next week
I have used the recurrence gem in the past for this purpose. There are a few other gems that model recurring events listed here.
If you are using a Time object, you can use Time.to_a to break the time into an array (with fields representing the hour, day, month, etc), adjust the appropriate field, and pass the array back to Time.local or Time.utc to build a new Time object.
If you are using the Date class, date +/- n will give you a date n days later/earlier, and date >>/<< n will give you a date n months later/earlier.
You can use the more generic Date.step instead of your loop. For example,
from_date.step(Date.today, interval) {|d|
# Each iteration of this block will be passed a value for 'd'
# that is 'interval' days after the previous 'd'.
}
where interval is a length of time in days.
If all you are doing is calculating elapsed time, then there is probably a better approach to this. If your date is stored as a Date object, doing date - Date.today will give you the number of days between that date and now. To calculate months, years, etc, you can use something like this:
# Parameter 'old_date' must be a Date object
def months_since(old_date)
(Date.today.month + (12 * Date.today.year)) - (old_date.month + (12 * old_date.year))
end
def years_since(old_date)
Date.today.year - old_date.year
end
def days_since(old_date)
Date.today - old_date
end