2

I have table called sales like this:

id    customer_id    product_id    sales
1        4           190            100
2        4           190            150
3        4           191            200
4        5           192            300
5        6           200            400

What I'd like to do is, get a total on how many of each products have been bought by a customer. Therefor I perform a simple group by:

Sales.all.group(:customer_id, product_id).sum(:sales)

What this gives me is a hash with keys consisting of arrays with the combination of customer_id and product_id:

hash = {
  [4, 190] => 250,
  [4, 191] => 200,
  [5, 192] => 300,
  [6, 200] => 400
}

While this gives me the result, I'm now also looking to get the total sum of sales for each customer. I could of course write another query, but this feels redundant. Isn't there an easy way to do find all the hash array keys that start with [4, *] or something?

1
  • You are right... error in my code. Will fix. Commented Nov 1, 2019 at 13:06

5 Answers 5

2

To get this value for a single value, you could do the following:

hash.select{|x| x[0] == 4}.values.sum 
=> 450

But you could also transform the hash to a grouped one on the customer-ids.

hash.inject(Hash.new(0)) do |result, v| 
  # v will look like [[4, 190], 250]
  customer_id = v[0][0] 
  result[customer_id] += v[1]
  result
end 

which you could also write as a one-liner

hash.inject(Hash.new(0)){|result, v| result[v[0][0]] += v[1]; result} 
=> {4=>450, 5=>700}

Which can be further shortened when using each_with_object instead of inject (thanks to commenter Sebastian Palma)

hash.each_with_object(Hash.new(0)){|v,result| result[v[0][0]] += v[1]}
Sign up to request clarification or add additional context in comments.

1 Comment

With each_with_object you can get rid of the ; result (hash.each_with_object(Hash.new(0)) { |v, result| result[v[0][0]] += v[1] })
1

You can try following,

hash.group_by { |k,v| k.first }.transform_values { |v| v.map(&:last).sum }
# => {4=>450, 5=>300, 6=>400} 

1 Comment

You could shorten v.map(&:last).sum to v.sum(&:last)
1

Try this:

hash.each_with_object(Hash.new(0)) do |(ids, value), result|
  result[ids.first] += value
end
#=> { 4 => 450, 5 => 700 }

Comments

1

You could group the resulting hash based on customer id. Then sum all the values in the group:

hash = {
  [4, 190] => 250,
  [4, 191] => 200,
  [5, 192] => 300,
  [6, 200] => 400
}

hash.group_by { |entry| entry.shift.first }
    .transform_values { |sums| sums.flatten.sum }
#=> {4=>450, 5=>300, 6=>400}

group_by groups the key-value pairs based on the customer id. shift will remove the key (leaving only the values), while first selects the customer id (since it is the first element in the array). This leaves you with an hash { 4 => [[250], [200]], 5 => [[300]], 6 => [[400]] }.

You then flatten the groups and sum all the values to get the sum for each costomer id.

Comments

0

Another approach would be to group by ID.

hash.
  group_by { |(k, _), _| k }.
  each_with_object(Hash.new{0}) do |(k, arr), acc|
    acc[k] += arr.sum(&:last)
end

#⇒ {4=>450, 5=>700}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.