Progression of records number by time - ruby-on-rails

Let's say I have three users created on different dates. Now I want do have a graph of users progression. So I want to get something like:
{
Thu, 02 Nov 2017=>1,
Sat, 04 Feb 2017=>2,
Wed, 21 Mar 2018=>3
}
It's very similar to grouping by created_at::date, but I want to have number of all the records created before this date rather than number of items created exactly on this date.
How can I achieve this using group_by and aggregate functions in Postgresql? I need it for Ruby on Rails project, but I expect simple vanilla SQL, no maps and complex queries.

I am no rails expert and my solution requires loading a big relationship like:
#u = User.all
And then find the smallest creation date :
#start = #u.minimum("created_at")
Then you can calculate the number of days between creation and now:
#days = ((Time.now - #start)/1.day).to_i
Then you can calculate for each day the number of users already created:
#days.each do |day|
puts "day "+day.to_s
puts #u.where("created_at < ?", #start+day.day).count
end
Probably some easier solutions in SQL though. (Also you may take only a subset of users by choosing a range of dates not to exceed the server memory)

Related

Rails hash counting from a specific keys in hash

Hi I am using this code bellow
Companion.where(companion_type: 1).joins(:tasks).where(tasks: {status: 3}).group_by_month(:created_at).size
and i am having a data looks like this
Jan, 2017 => 89
.
.
.
Aug, 2021 => 300
But i need data's above from Jan, 2019
Is there any nice ways to solve it out?
If you are only interested in Jan 2019, then you don't need to pull all the Companion table, and then group them by month. Instead, select only those in the month of interest...
Companion.
where(companion_type: 1).
joins(:tasks).
where(tasks: {status: 3}).
where("MONTH(tasks.created_at) = 1 AND DAY(tasks.created_at) = 19").
count
I assume that it's the created_at of tasks that you want to filter, vs. the created_at of companions, is that right?
Also, it's better to use count than size because that is directly inserted into the sql, as opposed to instantiating all the objects from Jan, 2019 and then applying .size to the result.

Group users by created_at and insert into hash for MorrisJs

I want to use MorrisJs-Rails to build up a graphical statistic of user registered within the last 4 weeks.
For that I iterate through each day of the last month and check for users registered at this specific date. Here's the problem though.
admin_helper.rb:
def users_chart_date
(4.weeks.ago.to_date..Date.today).map do |date|
{
created_at: date,
count: User.where("created_at = ? ", date).count
}
end
end
Both dates are stored like so:
puts User.first.created_at # => Tue, 10 Mar 2015 20:08:18 UTC +00:00
puts date # => Thu, 23 Jul 2015
You can see there's quite the difference between those two stored dates thus I can not possibly get an actual count of users registered per day.
Is there a way to "cast" my SQL query somehow? I feel the solution to my problem is easy peasy, but I can't seem to get it.
Thanks for your help,
let me know if you need something.
As you've noticed, the created_at timestamp is of type DateTime (or Time), so you're correct in that its not directly comparable with a Date type. You could compare it with a range of times for each day:
(4.weeks.ago.to_date..Date.today).map do |date|
{
created_at: date,
count: User.where(created_at: date.to_time.all_day).count
}
end
(Note that this will use the times for your computer's timezone.)
However, this method can become very slow especially if the time range becomes longer (e.g., over the entire life of your app), in which case a SQL GROUP BY statement work much faster. Again, you need to deal with a comparison by dates on a time data field, which is database-dependent. With MySQL or PostgreSQL you can:
User.group("DATE(created_at)").count
If you want to restrict the time periods you get data for:
User.where("created_at >= ?", 4.weeks.ago.beginning_of_day).group("DATE(created_at)").count
These queries will return a hash of Date objects to the count of users with created_at times on that day.

Average sales price for each day

I have Rails app with a PostgreSQL DB Sales Table that looks like this:
Sales
price(decimal) date(datetime)
100.20 11/25/2012 11:00:00
30.43 11/26/2012 03:00:00
124.43 11/25/2012 18:00:00
190.34 11/26/2012 22:00:00
The DB has thousands of records like this, spanning 10 years.
I need to find the average sales price for each day and save each as a separate record in another DB table via a rake task.
For further clarity, Sales need to be grouped by day (even though data is in datetime format), and then the price values for that day need to be averaged.
Since I have 10 years of days, the ability to loop through each daily group and perform the averages is essential.
Does anyone have any suggestions as to how to write this?
You'll need to double check the syntax, but something like this should do it all in the database...
Sales.average(:price, :group => "DATE_TRUNC('day', date)")
Note that DATE_TRUNC is PG specific.

Rails 3: Is it possible to access a model's attribute in a query?

Sorry if that question sounds strange, but I'm diving into Rails and I'm still learning the jargon. Basically, I'm trying to create a single-pass query that uses the value of one of the model's attributes in a calculation in the query (assuming that's even possible).
I have a Tournament model that has a start_date attribute that is a DateTime object. I'm trying to create a query that returns all the Tournaments that have a start_date no older than 1 hour + the length of the tournament, or put another way, all tournaments that haven't yet started or have started, but haven't ended longer than an hour ago. My current query, which doesn't work, looks like this...
validTourneys = Tournament.where("start_date > (? - duration_in_mins)", (DateTime.now.utc - 1.hour))
where duration_in_mins is an integer attribute of the Tournament model, but this query doesn't work and it seems to be returning all the Tournaments all the time. I'd like to include duration_in_mins in the (DateTime.now.utc - 1.hour) part of the calculation, but I don't know how to reference it, which is why I included it in the string part of the query, hoping that would work. Am I at least on the right track?
I should mention I'm using SQLite for development and PostgreSQL for production.
Thanks for your wisdom!
The problem is that if you subtract minutes from a DateTime object, you are not subtracting minutes but days.
# This works as expected
dt = DateTime.now # Thu, 28 Apr 2011 09:55:14 +0900
an_hour_ago = dt - 1.hour # Thu, 28 Apr 2011 08:55:14 +0900
# But, this does not...
two_hours_in_minutes = 120
two_hours_ago = dt - two_hours_in_minutes # Wed, 29 Dec 2010 09:55:14 +0900
In the last example 120 days are subtracted instead of minutes. This is probably also happening in your query. You have to convert duration_in_minutes to days and then subtract.
I don't know enough about SQL to answer your question directly (I think this will probably also depend on what database you're using, so you might want to mention that).
Have you considered, though, having start_date and end_date as DateTime columns instead of start_date and duration_in_mins? If this is going to be a common query, that would certainly make it more performant, as well as making your code easier to read and understand.
This query will only work if your database is smart enough to know how to add (what I am assuming) is a DateTime and and integer. And I can't think of a database that will do that correctly the way you have it coded. No database will assume minutes. Some might do ticks, seconds, or days.
This part of the calculation
(? - duration_in_mins)
is going to happen on the database, not in Ruby-land.

Evaluating points in time by months, but without referencing years in Rails

FYI, There is some overlap in the initial description of this question with a question I asked yesterday, but the question is different.
My app has users who have seasonal products. When a user selects a product, we allow him to also select the product's season. We accomplish this by letting him select a start date and an end date for each product.
We're using date_select to generate two sets of drop-downs: one for the start date and one for the end date.
Including years doesn't make sense for our model. So we're using the option: discard_year => true
When you use discard_year => true, Rails sets a year in the database, it just doesn't appear in the views. Rails sets all the years to either 0001 or 0002 in our app. Yes, we could make it 2009 and 2010 or any other pair. But the point is that we want the months and days to function independent of a particular year. If we used 2009 and 2010, then those dates would be wrong next year because we don't expect these records to be updated every year.
My problem is that we need to dynamically evaluate the availability of products based on their relationship to the current month. For example, assume it's March 15. Regardless of the year, I need a method that can tell me that a product available from October to January is not available right now.
If we were using actual years, this would be pretty easy.
For example, in the products model, I can do this:
def is_available?
(season_start.past? && season_end.future?)
end
I can also evaluate a start_date and an end_date against current_date
However, in setup I've described above where we have arbitrary years that only make sense relative to each other, these methods don't work. For example, is_available? would return false for all my products because their end date is in the year 0001 or 0002.
What I need is a method just like the ones I used as examples above, except that they evaluate against current_month instead of current_date, and past? and future months instead of years.
I have no idea how to do this or whether Rails has any built in functionality that could help. I've gone through all the date and time methods/helpers in the API docs, but I'm not seeing anything equivalent to what I'm describing.
Thanks.
Set the year of the current date to 1000 and perform a range compare with the start and end dates.
def is_available?
t = Date.today
d = Date.new(1000, t.month, t.day)
(season_start..season_end).include?(d) or
(season_start..season_end).include?(d+1.year)
end
Second comparison above is to address the following scenario:
Lets say today Jan 15 and the season is from Oct - Feb. In our logic, we set the date to Jan 15 1000. This date will not be within Oct 1 1000 - Mar 1 1001. Hence we do the second comparison where we advance the date by a year to Jan 15 1001, which is within the range.
You might want to consider not using Date objects. What if season_start_month and season_end_month were each an integer 1-12, set with your dropdown? Then when doing your is_available? comparison, you could dynamically create the full date for season_start, doing some math for transitions over December to January. This could use some refactoring, and isn't tested, but should do the trick. Assumes that season_start_month and season_end_month are integers stored in this model:
class Product < ActiveRecord::Base
def is_available?
now = Time.now
season_start < now && now < season_end
end
def season_start
now = Time.now
current_month = now.month
season_start_year = season_start_month < current_month ? now.year : now.year - 1
Time.local(season_start_year, season_start_month)
end
def season_end
now = Time.now
current_month = now.month
season_end_year = season_end_month > current_month ? now.year : now.year + 1
Time.local(season_end_year, season_end_month)
end
end

Resources