Elixir/Phoenix sum of the column - phoenix-framework

I'm trying to get the sum of the particular column.
I have a schema of orders, with the field total, that stores the total price.
Now I'm trying to created a query that will sum total value of all the orders, however not sure if I'm doing it right.
Here is what i have so far:
def create(conn, %{"statistic" => %{"date_from" => %{"day" => day_from, "month" => month_from, "year" => year_from}}}) do
date_from = Ecto.DateTime.cast!({{year_from, month_from, day_from}, {0, 0, 0, 0}})
revenue = Repo.all(from p in Order, where: p.inserted_at >= ^date_from, select: sum(p.total))
render(conn, "result.html", revenue: revenue)
end
And just calling it like <%= #revenue %> in the html.eex.
As of right now, it doesn't return errors, just renders random symbol on the page, instead of the total revenue.
I think my query is wrong, but couldn't find good information about how to make it work properly. Any help appreciated, thanks!

Your query returns just 1 value, and Repo.all wraps it in a list. When you print a list using <%= ... %>, it treats integers inside the list as Unicode codepoints, and you get the character with that codepoint as output on the page. The fix is to use Repo.one instead, which will return the value directly, which in this case is an integer.
revenue = Repo.one(from p in Order, where: p.inserted_at >= ^date_from, select: sum(p.total))

#Dogbert's answer is correct. It is worth noting that if you are using Ecto 2.0 (currently in release candidate) then you can use Repo.aggregate/4:
revenue = Repo.aggregate(from p in Order, where: p.inserted_at >= ^date_from, :sum, :total)

Related

Combining multiple array/hash selects

I have the following code:
sum = array_of_hashes.select{ |key| (date_range).include? Date.parse(key[:created])}.map { |h| h[:amount] }.sum
size = array_of_hashes.select{ |key| (date_range).include? Date.parse(key[:created])}.size
total = sum / size
sum selects all hashes with a date that is inside a date range and the adds up all the values of the :amount key.
size counts the number of hashes that are in the date range.
total divides the sum by the size.
How can I combine those so it's not 3 separate items?
I think it's as simple as:
selected = array_of_hashes.select { ... }
avarage = selected.map { ... }.sum / selected.size
Note: using include? with ranges of dates is pretty inefficient since it needs to traverse the whole dates range, I suggest to use cover? instead.
There is really no nice way of doing this more compact. One alternative could be the following:
average = (selected = array_of_hashes.select { ... }.map { ... }).sum/selected.size.to_f

CSS Selector for Table Row with X number of Cells

I'm trying to scrape some content off of a website and I am having trouble selecting the correct elements.
I'm using Nokogiri, and, as I know CSS best, I am trying to use it to select the data I want.
There is a big table with rows I do not want, but these can change; They are not always row 4, 5, 6, 10, 14 for example.
The only way I can tell if it's a row I want is if the row has TD tags in it.
What is the right CSS selector to do this?
# Search for nodes by css
doc.css('#mainContent p table tr').each do |td|
throw td
end
EDIT:
I'm trying to scrape boxrec.com/schedule.php. I want the rows for each match, but, it's a very large table with numerous rows which aren't the match. The first couple rows of each date section aren't needed, including every other line which has "bout subject to change....", and also spacing rows between days.
SOLUTION:
doc.xpath("//table[#align='center'][not(#id) and not(#class)]/tr").each do |trow|
#Try get the date
if trow.css('.show_left b').length == 1
match_date = trow.css('.show_left b').first.content
end
if trow.css('td a').length == 2 and trow.css('* > td').length > 10
first_boxer_td = trow.css('td:nth-child(5)').first
second_boxer_td = trow.css('td:nth-child(5)').first
match = {
:round => trow.css('td:nth-child(3)').first.content.to_i,
:weight => trow.css('td:nth-child(4)').first.content.to_s,
:first_boxer_name => first_boxer_td.css('a').first.content.to_s,
:first_boxer_link => first_boxer_td.css('a').first.attribute('href').to_s,
:second_boxer_name => second_boxer_td.css('a').first.content.to_s,
:second_boxer_link => second_boxer_td.css('a').first.attribute('href').to_s,
:date => Time.parse(match_date)
}
#:Weight => trow.css('td:nth-child(4)').to_s
#:BoxerA => trow.css('td:nth-child(5)').to_s
#:BoxerB => trow.css('td:nth-child(9)').to_s
myscrape.push(match)
end
end
You won't be able to tell how many td elements a tr contains, but you can tell if it is empty or not:
doc.css('#mainContent p table tr:not(:empty)').each do |td|
throw td
end
You can do something like this:
tr rows with a 4th td
doc.xpath('//tr/td[4]/..')
another way with css:
doc.css('tr').select{|tr| tr.css('td').length >= 4}

Trouble eliminating separate query for each child item

I have a scenario where I have a Candidate and a Candidate has many Votes. I want to order the candidates in order of highest votes to lowest. This query does that:
Candidate.joins(:votes).
select(['candidates.*', 'SUM(votes.score) as total_score']).group('candidates.id, candidates.candidate_id, candidates.user_id, candidates.status, candidates.card_id, candidates.created_at, candidates.updated_at').
order('candidates.status desc, total_score desc, candidates.created_at asc').where("candidates.riding_id = ? and candidates.status != ?", 124, CandidateStatus::Eligible ).
having('SUM(votes.score) >= 0')
My problem is that I use this to render it:
render :json => #candidates.to_json(:include => [:votes])
This then causes a query for each candidate to get their votes. I should be able to do this in one query but no matter how I change it, it still grabs each candidates votes separately.
By the time #to_json is called, the votes have to be fetched again, hence each additional query. Using eager-loading, however, it's possible to do this without the additional queries:
Candidate.all(:include => :votes).to_json(:include => [:votes])
Thus, it can be done with #candidates including the eager-loading, such as:
#candidates = Candidate.all(:include => :votes)
#candidates.to_json(:include => [:votes])

MongoDB + Ruby: updating records in an iteration

Using MongoDB and the Ruby driver, I'm trying to calculate the rankings for players in my app, so I'm sorting by (in this case) pushups, and then adding a rank field and value per object.
pushups = coll.find.sort(["pushups", -1] )
pushups.each_with_index do |r, idx|
r[:pushups_rank] = idx + 1
coll.update( {:id => r }, r, :upsert => true)
coll.save(r)
end
This approach does work, but is this the best way to iterate over objects and update each one? Is there a better way to calculate a player's rank?
Another approach would be to do the entire update on the server by executing a javascript function:
update_rank = "function(){
var rank=0;
db.players.find().sort({pushups:-1}).forEach(function(p){
rank +=1;
p.rank = rank;
db.players.save(p);
});
}"
cn.eval( update_rank )
(Code assumes you have a "players" collection in mongo, and a ruby variable cn that holds a conection to your database)

increment value in a hash

I have a bunch of posts which have category tags in them.
I am trying to find out how many times each category has been used.
I'm using rails with mongodb, BUT I don't think I need to be getting the occurrence of categories from the db, so the mongo part shouldn't matter.
This is what I have so far
#recent_posts = current_user.recent_posts #returns the 10 most recent posts
#categories_hash = {'tech' => 0, 'world' => 0, 'entertainment' => 0, 'sports' => 0}
#recent_posts do |cat|
cat.categories.each do |addCat|
#categories_hash.increment(addCat) #obviously this is where I'm having problems
end
end
end
the structure of the post is
{"_id" : ObjectId("idnumber"), "created_at" : "Tue Aug 03...", "categories" :["world", "sports"], "message" : "the text of the post", "poster_id" : ObjectId("idOfUserPoster"), "voters" : []}
I'm open to suggestions on how else to get the count of categories, but I will want to get the count of voters eventually, so it seems to me the best way is to increment the categories_hash, and then add the voters.length, but one thing at a time, i'm just trying to figure out how to increment values in the hash.
If you aren't familiar with map/reduce and you don't care about scaling up, this is not as elegant as map/reduce, but should be sufficient for small sites:
#categories_hash = Hash.new(0)
current_user.recent_posts.each do |post|
post.categories.each do |category|
#categories_hash[category] += 1
end
end
If you're using mongodb, an elegant way to aggregate tag usage would be, to use a map/reduce operation. Mongodb supports map/reduce operations using JavaScript code. Map/reduce runs on the db server(s), i.e. your application does not have to retrieve and analyze every document (which wouldn't scale well for large collections).
As an example, here are the map and reduce functions I use in my blog on the articles collection to aggregate the usage of tags (which is used to build the tag cloud in the sidebar). Documents in the articles collection have a key named 'tags' which holds an array of strings (the tags)
The map function simply emits 1 on every used tag to count it:
function () {
if (this.tags) {
this.tags.forEach(function (tag) {
emit(tag, 1);
});
}
}
The reduce function sums up the counts:
function (key, values) {
var total = 0;
values.forEach(function (v) {
total += v;
});
return total;
}
As a result, the database returns a hash that has a key for every tag and its usage count as a value. E.g.:
{ 'rails' => 5, 'ruby' => 12, 'linux' => 3 }

Resources