Ruby on Rails query for a given set of dates - ruby-on-rails

I am trying to write a query for my Rails API that will return the total number of hours of job applications that are accepted per month for a given set of dates.
Table: Job, JobApplication
Job has many JobApplication
JobApplication has a field status "SUCCESS" if matched
Start Date: Jul 16 2015
Is it possible to have it return {Jul 16: 100, Aug 16: 200, .... } ?
I am quite confused. Any help would be appreciated.
This query would give the number of successful Job Applications within a date range.
JobApplication.where(status: "SUCCESS").("created_at < ? ", Jul 16).("created_at < ? ", Aug 15)
JobApplication.job.duration would give the number of hours for that job.
I am not sure how to put them together and then loop through the dates.
My expect results is something like this:
{Jul 16: 100, Aug 16: 200, .... }

Hey you can try this way:
JobApplication.joins(:job).where(status: "SUCCESS").where(job_applications => {:created_at => [state_date..end_date]}).group(:created_at).sum("jobs.duration")
It will Return you result like
{Date => total_duration, :date => total_duration}
If You want you can convert date to specific format like DATE_FORMAT(created_at, "%d-%m-%y") so just put group(DATE_FORMAT(created_at, "%m-%y")). It will remove your time part from timestamp in mysql and returns data daywise

Something like
data = JobApplication.where(status: 'SUCCESS').group(:start_date).select('start_date, count(*), sum(duration) as sum')
should do the trick, if i understood your question right. This will return JobApplication instances with sum set
Perhaps it is easier to run a pure SQL query:
SELECT start_date, count(*), sum(duration) FROM job_applications WHERE status LIKE 'SUCCESS' GROUP BY start_date

Related

Convert Ruby Hash to ranks with no repeats

[{: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]] }

ruby how to group by given date

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.

Finding sum of purchases

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]
]

Order query by day / month / year in rails

Let me explain want I want to do, I've a table Clicks where all clicks for products are saved. And I want to make a graph of how many times something is clicked by customers.
This graph needs a day, month and year view.
What I need for the graph to work is a hash or an object.
My idea is that I'm getting something like:
data( 2012 => [ 01 => 100, 02 => 300, 03 => 50 etc.], 2011 => [ 01 => 100, 02 => 300, 03 => 50 etc.])
How can I retrieve all the data from a postgresql database in such an order? I would like to get the created_at field and extract the date from there. In a later process, all items from a particular month must be counted.
I'm struggling with this problem quite a bit, could someone push me in the right direction?
The snippet you posted is for MySQL - to do the same in PostgreSQL:
#clicks = Click.select("count(*) as c, date_part('year', created_at) as y, date_part('month', created_at) as m").
group("y", "m").
where({:user_id => current_user.id})
This will give you a hash in the format [year, month] => count. To get it in the format you wanted, you'll need to tweak the results a bit:
results = #clicks.inject({}) do |m, kv|
year, month = kv.first
clicks = kv.last
m[year] ||= {}
m[year][month] = clicks
m
end
Or a bit more concise:
results = #clicks.each_with_object({}) do |((year, month), clicks), m|
(m[year] ||= {})[month] = clicks
end
If you are using Postgree(or Heroku whit PG) can make this way:
by Month:
Consumer.all.order("extract(month from created_at) DESC")
by Day
Consumer.all.order("extract(day from created_at) DESC")
by Year
Consumer.all.order("extract(year from created_at) DESC")

find_by_sql in Rails, accessing the resulting array

I'm trying to run a query in a very quick and dirty way in Rails, without putting the rest of the model in place. I know this is bad practice but I just need a quick result in a tight timeframe until I've got the whole solution in place.
I've got items that have a shipping price, based on weight. The weight is stored in the item, the price is stored in the table shipping_zone_prices, and all I currently do is look for the price relating to the first row where the weight is heavier than the item for sale:
class Item < ActiveRecord::Base
def shipping_price
item_id = self.id
shipping_price = ShippingZonePrice.find_by_sql(
"SELECT z.price as price
FROM shipping_zone_prices z, items i
WHERE i.id = '#{item_id}'
AND z.weight_g > d.weight
ORDER BY z.weight_g asc limit 1")
end
end
This sort of works. The SQL does the job, but when plugged into the app as follows:
<%= #item.shipping_price %> Shipping
I get the following displayed:
[#<ShippingZonePrice price: 12>] Shipping
In this example, '12' is the price that is being pulled from the db, and is correct. #item.shipping_price.class returns 'Array'. Trying to access the array using [0] (or any other integer) returns a blank.
Is there another way to access this, or am I missing something fundamental?
Since you are defining an instance method, I think it should return the price if it exists or nil
Try something like this:
def shipping_price
ShippingZonePrice.find_by_sql(
"SELECT z.price as price
FROM shipping_zone_prices z, items i
WHERE i.id = '#{self.id}'
AND z.weight_g > d.weight
ORDER BY z.weight_g asc limit 1").first.try(:price)
end
Then this should work for you:
#item.shipping_price
The first.try(:price) part is needed because find_by_sql may return an empty array. If you tried to do something like first.price on an empty array, you would get an exception along the lines of NoMethodError: undefined method 'price' for nil:NilClass.
This is because find_by_sql returns a model, not data. If you want to do a direct fetch of the data in question, use something like this:
ShippingZonePrice.connection.select_value(query)
There are a number of direct-access utility methods available through connection that can fetch single values, a singular array, rows of arrays, or rows of hashes. Look at the documentation for ActiveRecord::ConnectionAdapters::DatabaseStatements.
As when writing an SQL directly, you should be very careful to not create SQL injection bugs. This is why it is usually best to encapsulate this method somewhere safe. Example:
class ShippingZonePrice < ActiveRecord::Base
def self.price_for_item(item)
self.connection.select_value(
self.sanitize_sql(
%Q[
SELECT z.price as price
FROM shipping_zone_prices z, items i
WHERE i.id=?
AND z.weight_g > d.weight
ORDER BY z.weight_g asc limit 1
],
item.id
)
)
end
end
#item.shipping_price.first.price
or
#item.shipping_price[0].price
Thanks Atastor for pointing that out!
When you use AS price in find_by_sql, price becomes a property of the result.
If not for you saying that you tried and failed accessing [0] i'ld say you want to put
#item.shipping_price.first.price # I guess BSeven just forgot the .first. in his solution
into the view...strange
So, I had a hacky solution for this, but it works great.
Create a table that has the same output as your function and reference it, then just call a function that does a find_by_sql to populate the model.
Create a dummy table:
CREATE TABLE report.compliance_year (
id BIGSERIAL,
year TIMESTAMP,
compliance NUMERIC(20,2),
fund_id INT);
Then, create a model that uses the empty table:
class Visualization::ComplianceByYear < ActiveRecord::Base
self.table_name = 'report.compliance_year'
def compliance_by_year(fund_id)
Visualization::ComplianceByYear.find_by_sql(["
SELECT year, compliance, fund_id
FROM report.usp_compliance_year(ARRAY[?])", fund_id])
end
end
In your controller, you can populate it:
def visualizations
#compliancebyyear = Visualization::ComplianceByYear.new()
#compliancefunds = #compliancebyyear.compliance_by_year(current_group.id)
binding.pry
end
Then, you can see it populate with what you need:
[1] pry(#<Thing::ThingCustomController>)> #compliancefunds
[
[0] #<Visualization::ComplianceByYear:0x00000008f78458> {
:year => Mon, 31 Dec 2012 19:00:00 EST -05:00,
:compliance => 0.93,
:fund_id => 1
},
[1] #<Visualization::ComplianceByYear:0x0000000a616a70> {
:year => Tue, 31 Dec 2013 19:00:00 EST -05:00,
:compliance => 0.93,
:fund_id => 4129
},
[2] #<Visualization::ComplianceByYear:0x0000000a6162c8> {
:year => Wed, 31 Dec 2014 19:00:00 EST -05:00,
:compliance => 0.93,
:fund_id => 4129
}
]

Resources