Rails: Count specific records then do calculation - ruby-on-rails

I am attempting to do a rails record count then do a calculation from those records to give me a final number:
Example
100 Records = A
30 Records = B
Total Records = C
A+(-B)=C
I am not even going to show you what I tried... in retrospect I am very new to rails and it made no logical sense!
UPDATE:
To further expand:
When implementing this I realized that there might be some slight difference from what it solves above.
I have a MVC called "POST" It was some records within the table one specifically is called "VOTE" the vote integer will consist of 1 or (-1). Each post will have a VOTE column that represent a value of 1 or (-1). I am trying to create an analytic metric that consists of the following:
TOTAL = (Total posts with Value 1) + (Total posts with Value -1)
Example
1234 = 2000 + (-776)
Thank you in advance!

In general,
C = A.count + B.count
If you want C to be the union of A and B, then do
C = A & B
C.count # Number of elements in C
For your specific case:
yes_votes = POST.where('VOTE = ?', 1)
no_votes = POST.where('VOTE = ?', -1)
total = yes_votes.count - no_votes.count

Related

Rails match 2 arrays

I have 2 models Donation and BankDeposit
Donation has a column sender_id
Bank Deposit has a column donated_by_id
So let say I have:
#donations = Donation.all
#bank_deposits = BankDeposit.all
and these 2 arrays return this values for mentioned columns:
#donations.sender_id = [1,3,6,8,9]
#bank_deposits.donated_by_id = [2,3,7,8]
In this example, assuming ids of donations are 1 to 5, result I want to achieve is selecting rows with id 2 and 4 (as they contain 3 and 8 present on bank_deposits.donated_by_id).
How should I implement the code to achieve this?
If you ONLY need the donations you can change the code and try:
#donations = Donation.where(sender_id: BankDeposit.pluck(:donated_by_id))
If you want to keep both and use all the records just filter using select:
bank_deposit_donated_by_ids = #bank_deposits.map(&:donated_by_id)
matched_donations = #donations.select do |elem|
bank_deposit_donated_by_ids.include?(elem.sender_id)
end
Didn't test the syntax/API, but that's the idea.
Try below code
#donations.sender_id & #bank_deposits.donated_by_id
This will give you output of common element id from the both array after that you can filter it in any sense you like.
Hope it will work for you.

Computing ActiveRecord nil attributes

In my Rails app I have something like this in one of the models
def self.calc
columns_to_sum = "sum(price_before + price_after) as price"
where('product.created_at >= ?', 1.month.ago.beginning_of_day).select(columns_to_sum)
end
For some of the rows we have price_before and or price_after as nil. This is not ideal as I want to add both columns and call it price. How do I achieve this without hitting the database too many times?
You can ensure the NULL values to be calculated as 0 by using COALESCE which will return the first non NULL value:
columns_to_sum = "sum(COALESCE(price_before, 0) + COALESCE(price_after, 0)) as price"
This would however calculate the sum prices of all products.
On the other hand, you might not have to do this if all you want to do is have an easy way to calculate the price of one product. Then you could add a method to the Product model
def.price
price_before.to_i + price_after.to_i
end
This has the advantage of being able to reflect changes to the price (via price_before or price_after) without having to go through the db again as price_before and price_after will be fetched by default.
But if you want to e.g. select records from the db based on the price you need to place that functionality in the DB.
For that I'd modulize your scopes and join them again later:
def self.with_price
columns_to_sum = "(COALESCE(price_before, 0) + COALESCE(price_after, 0)) as price"
select(column_names, columns_to_sum)
end
This will return all records with an additional price reader method.
And a scope independent from the one before:
def self.one_month_ago
where('product.created_at >= ?', 1.month.ago.beginning_of_day)
end
Which could then be used like this:
Product.with_price.one_month_ago
This allows you to continue modifying the scope before hitting the DB, e.g. to get all Products where the price is higher than x
Product.with_price.one_month_ago.where('price > 5')
If you are trying to get the sum of price_before and price_after for each individual record (as opposed to a single sum for the entire query result), you want to do it like this:
columns_to_sum = "(coalesce(price_before, 0) + coalesce(price_after, 0)) as price"
I suspect that's what you're after, since you have no group in your query. If you are after a single sum, then the answer by #ulferts is correct.

Selecting greatest date range count in a rails array

I have a database with a bunch of deviceapi entries, that have a start_date and end_date (datetime in the schema) . Typically these entries no more than 20 seconds long (end_date - start_date). I have the following setup:
data = Deviceapi.all.where("start_date > ?", DateTime.now - 2.weeks)
I need to get the hour within data that had the highest number of Deviceapi entries. To make it a bit clearer, this was my latest try on it (code is approximated, don't mind typos):
runningtotal = 0
(2.weeks / 1.hour).to_i.times do |interval|
current = data.select{ |d| d.start_time > (start_date + (1.hour * (interval - 1))) }.select{ |d| d.end_time < (start_date + (1.hour * interval)) }.count
if current > runningtotal
runningtotal = current
end
The problem: this code works just fine. So did about a dozen other incarnations of it, using .where, .select, SQL queries, etc. But it is too slow. Waaaaay too slow. Because it has to loop through every hour within 2 weeks. Then this method might need to be called itself dozens of times.
There has to be a faster way to do this, maybe a sort? I'm stumped, and I've been searching for hours with no luck. Any ideas?
To get adequate performance, you'll want to do everything in a single query, which will mean avoiding ActiveRecord functionality and doing a raw query (e.g. via ActiveRecord::Base.connection.execute).
I have no way to test it, since I have neither your data nor schema, but I think something along these lines will do what you are looking for:
select y.starting_hour, max(y.num_entries) as max_entries
from
(
select x.starting_hour, count(*) as num_entries
from
(
select date_trunc('hour', start_time) starting_hour
from deviceapi as d
) as x
group by x.starting_hour
) as y
where y.num_entries = max(y.num_entries);
The logic of this is as follows, from the inner-most query out:
"Bucket" each starting time to the hour
From the resulting table of buckets, get the total number of entries in each bucket
Get the maximum number of entries from that table, and then use that number to match back to get the starting_hour itself.
If there happen to be more than one bucket with the same number of entries, you could determine a consistent way to pick one -- say the min(starting_hour) or similar (since that would stay the same even as data gets added, assuming you are not deleting items).
If you wanted to limit the initial time slice -- I see 2 weeks referenced in your post -- you could do that in the inner-most query with a where clause bracketing the date range.

Payout Algorithm Ruby

Im building a rails app that has users and scores. I want the top half of the users to get paid out. I have a separate tiebreaker input stored for each user if they get happen to tie for last place (last paid out place). For example, I need help, if their are 8 users and 4th and 5th tie in points. Then it calls my tiebreaker.
This is what I have tried:
First I am counting the users and determening the top half of the players:
theUsersCount = ParticipatingUser.where(game_id: game_id).size
numofWinners = theUsersCount / 2
Then I am taking the users and their scores and pushing it to an array then only showing the top half of the users that won.
userscores.push("#{user.username}" => playerScore})
userscores[0..numofWinners].sort_by { |y| y[:score] }
But I am unsure of how to take execute the tiebreaker if their is a tie for last place.
To get the users count you should use count rather than size - size fetches all the rows, then counts them, while count counts the rows in the DB, and returns the number:
user_count = ParticipatingUser.where(game_id: game_id).count
(actually - the above is wrong - here is an explanation - you should use size which smartly chooses between length and count - thanks #nzifnab)
Now, find the score of the user in the user_count/2 place
minimal_score = ParticipatingUser.order(:score, :desc).pluck(:score).take(user_count/2).last
And take all the users with this score or more:
winning_users = ParticipatingUser.where('score >= ?', minimal_score).order(:score, :desc)
now check if there are more users than expected:
if winning_users.size > user_count/2
then break your ties:
tie_breaker(winning_users[user_count/2-1..-1])
All together:
user_count = ParticipatingUser.where(game_id: game_id).size
minimal_score = ParticipatingUser.order(:score, :desc).pluck(:score).take(user_count/2).last
winning_users = ParticipatingUser.where('score >= ?', minimal_score).order(:score, :desc)
if winning_users.size > user_count/2
losers = tie_breaker(winning_users[user_count/2-1..-1])
winning_users -= losers
end
winning_users

ruby on rails average per day

Referencing this post:
ruby on rails average per day
I'm getting the average per day, which works fine. Until now that the month switched over...
I have this code
score = (7.days.ago.to_date.to_s(:db)..Date.today.tomorrow.to_s(:db)).map{|dt| [dt, ave_score[dt] || 0]}
Which works, but now that the month switches over, it comes back with dates that don't exist, like 2009-08-32 to 2009-08-99 and 2009-09-00. All in the array. How can I delete dates that don't actually exist.
Try waiting to make the call to to_s:
score = (7.days.ago.to_date..Date.today.tomorrow).map{ |dt| d = dt.to_s(:db); [d, ave_score[d] || 0] }
I figured it out, I'm not sure if this is the best way of doing it so if anybody else would like to add something go for it:
arr_count = 0
length = score.length
while arr_count < length
begin
score[arr_count][0].to_date
arr_count = arr_count + 1
rescue
#score.delete_at(arr_count)
length = length - 1
end
end

Resources