Fetching rows based on integer interval of a column - ruby-on-rails

I am using rails 5.0.0 and postgresql
I have 3 tables namely orders, cart_items and products. The relation among them is as follows :
orders has_many cart_items
cart_item belongs_to product
Products table contains the price of the product.
What I want to do is fetch the number of orders between the price range of 2500 like orders which contain products worth between 0-2500, 2500-5000 and so on.
Thanks in advance.

The concept here is to group each orders by their total_price divided by 2500 (we're speaking about integer division here), so you will be able to group by their "sliced category".
Because:
300 / 2500 = 0
400 / 2500 = 0
2500 / 2500 = 1
3000 / 2500 = 1
3000 / 2500 = 1
4999 / 2500 = 1
5000 / 2500 = 2
etc. It's the result of this integer division that will create what I called earlier the "sliced category" (1 => 0-2500, 2 => 2500-5000, 3 => 5000-7500, etc). Since your total price may be decimal or floats, I "round" the value and cast them with ::integer.
The query can be something like this:
SELECT count(*), (sliced_2500_category + 1) * 2500 AS sliced_2500_group
FROM (
SELECT c.order_id, ROUND(SUM(price))::integer / 2500 AS sliced_2500_category
FROM cart_items c
INNER JOIN products p ON c.product_id = p.id
GROUP BY c.order_id
) orders_by_2500_category
GROUP BY sliced_2500_category;

Make sure your cart_items also belongs_to :orders
and product model has_many cart_items
I'm guessing you want to select the invoices/carts which are in the range of 0-2500 and so on..
Raw SQL:
SELECT order_id, sum(price)
FROM cart_items c INNER JOIN products p
ON c.product_id = p.id
GROUP_BY order_id
HAVING sum(price) BETWEEN 0 AND 2500
Required statement for controller:
CartItem.joins(:product)
.select(:order_id, 'sum(price)')
.group(:order_id)
.having('sum(price) BETWEEN 0 AND ?', params[:your_upper_limit_variable])

Related

How to get the top 5 per enum of a model in rails?

Let's say I have a model named post, which has an enum named post_type which can either be
admin, public or user
#app/models/post.rb
class Post < ApplicationRecord
enum post_type: [ :admin, :public, :user ]
end
How can I select 5 last created posts from each category?
I can't think of any other solution than this:
PER_GROUP = 5
admin_posts = Post.admin.order(created_at: :desc).limit(PER_GROUP)
user_posts = Post.user.order(created_at: :desc).limit(PER_GROUP)
public_posts = Post.public.order(created_at: :desc).limit(PER_GROUP)
Is there any way I could fetch all the rows in the required manner from just a single query to the database.
STACK
RAILS : 6
PostgresSQL: 9.4
I am not sure how to translate into RAILS, but it is straight forward Postgres query. You use the row_number window function in a sub-select then keep only rows with row_number less than or equal 5 on the outer select.
select *
from (select post_txt
, posted_type
, row_number() over (partition by posted_type) rn
from enum_table
) pt
where rn <= 5
order by posted_type;
One thing to look out for is the sorting on an enum. Doing so gives results in order of the definition, not a "natural order" (alphanumeric in this case). See example here.
Thanks to #Belayer i was able to come up with a solution.
PER_GROUP = 5
sub_query = Post.select('*', 'row_number() over (partition by "posts"."post_type" ORDER BY posts.created_at DESC ) rn').to_sql
#posts = Post.from("(#{sub_query}) inner_query")
.where('inner_query.rn <= ?', PER_GROUP')
.order(:post_type, created_at: :desc)
.group_by(&:post_type)
Since i am only loading 5 records across just a few different types group_by will work just fine for me.

PSQL Query summing columns on a "has many through" relationship yielding duplicates

My goal is to generate an ActiveRecordRelation containing each Customer with it's balance (sale_line_items.total + sale_adjustments.effect_to_balance - sale_payment.amount)
class Customer < ActiveRecord:Base
has_many :sales
class Sale < ActiveRecord:Base
belongs_to :customer
has_many :sale_adjustments
has_many :sale_payments
class SaleLineItem < ActiveRecord:Base
belongs_to :sale
class SalePayment < ActiveRecord:Base
belongs_to :sale
class SaleAdjustment < ActiveRecord:Base
belongs_to :sale
My current code:
customers = Customer.all
customers = customers.joins("LEFT OUTER JOIN sales ON customers.id = sales.customer_id")
customers = customers.joins("LEFT OUTER JOIN sale_line_items ON sale_line_items.sale_id = sales.id")
customers = customers.joins("LEFT OUTER JOIN sale_adjustments ON sale_adjustments.sale_id = sales.id")
customers = customers.joins("LEFT OUTER JOIN sale_payments ON sale_payments.sale_id = sales.id")
customers = customers.select("customers.*,
COALESCE(SUM(sale_line_items.total),0) +
COALESCE(SUM(sale_adjustments.effect_to_balance),0) -
COALESCE(SUM(sale_payments.amount),0) AS customer_balance")
customers.group("customers.id").distinct
The problem is that if there are more than one payments or adjustments, the line items are duplicated in the resulting table, essentially increasing the total by a factor of that number. I think i understand enough to identify the problem, but not enough to come up with a solution. The rails generated query is below.
[1m[36mCustomer Load (2.2ms)[0m [1mSELECT DISTINCT customers.*,
COALESCE(SUM(sale_line_items.total),0) +
COALESCE(SUM(sale_adjustments.effect_to_balance),0) -
COALESCE(SUM(sale_payments.amount),0) AS customer_balance
FROM "customers" LEFT OUTER JOIN sales ON customers.id = sales.customer_id
LEFT OUTER JOIN sale_line_items ON sale_line_items.sale_id = sales.id
LEFT OUTER JOIN sale_adjustments ON sale_adjustments.sale_id = sales.id
LEFT OUTER JOIN sale_payments ON sale_payments.sale_id = sales.id
WHERE "customers"."business_id" = $1 GROUP BY customers.id[0m [["business_id", "bd0c474c-db6e-43bc-95ca-90541d3840d1"]]
An example of the error is:
A customer with one sale that has one line item of $50
total sales: $50, balance: $50
Add one payment of $10
total sales: $50, balance: $40
Problems occur on the second payment, lets say $1
total sales $100, balance $89
That extra payment creates a duplicate row for the line item. It will go to $150 on a third payment and so on...
Any help is greatly appreciated - i've been working on this all night and at this point i'm just going around in circles.
I have worked on something similar while I was calculating invoice balance for a PHP-MySQL project. I ended up changing my database structure and application flow. I modified the invoices table to include invoice_total and total_payment columns which will be updated with creation and edition of Invoice and Payment modules.
Although you can use sub-queries to get the desired result like:
SELECT DISTINCT customers.*,
COALESCE(SUM(sli.total),0) +
COALESCE(SUM(sa.effect_to_balance),0) -
COALESCE(SUM(sp.amount),0) AS customer_balance
FROM "customers" LEFT OUTER JOIN sales ON customers.id = sales.customer_id
LEFT OUTER JOIN (SELECT sale_id, SUM(total) total FROM sale_line_items) sli ON sli.sale_id = sales.id
LEFT OUTER JOIN (SELECT sale_id, SUM(effect_to_balance) effect_to_balance FROM sale_adjustments) sa ON sa.sale_id = sales.id
LEFT OUTER JOIN (SELECT sale_id, SUM(amount) amountFROM sale_payments) sp ON sp.sale_id = sales.id
WHERE "customers"."business_id" = $1 GROUP BY customers.id
Hope this will solve your problem :)
The rails code modeled after Manoj Monga's answer:
customers = Customer.all
customers = customers.joins("LEFT OUTER JOIN sales ON customers.id = sales.customer_id")
customers = customers.joins("LEFT OUTER JOIN (SELECT sale_id, SUM(total) total FROM sale_line_items GROUP BY sale_id) sli ON sli.sale_id = sales.id")
customers = customers.joins("LEFT OUTER JOIN (SELECT sale_id, SUM(effect_to_balance) effect_to_balance FROM sale_adjustments GROUP BY sale_id) sa ON sa.sale_id = sales.id")
customers = customers.joins("LEFT OUTER JOIN (SELECT sale_id, SUM(amount) amount FROM sale_payments GROUP BY sale_id) sp ON sp.sale_id = sales.id")
customers = customers.select("customers.*,
COALESCE(SUM(total), 0) as sales_total,
COALESCE(SUM(total), 0) +
COALESCE(SUM(effect_to_balance),0) -
COALESCE(SUM(amount),0) AS customer_balance")
customers.group("customers.id").distinct

Rails 4 - Getting most sold objects with a relationship in between

I have a Product model with :name and price. I also have a Order model with :amount (of units sold) and belongs_to :product
How could I get a array of the top 5 most sold object, in terms on units sold?
I was thinking of getting something like:
{"Razors"=>4, "Axes"=>2, "Cars"=>1, ...}
P.S.: If possible, how would it be getting the top 5 most sold objects, in terms of income made?
You can pull all needed data with one single SQL query.
You didn't mention database you are using, but here are some examples:
MySQL:
SELECT products.*, SUM(amount) total_amount FROM orders
LEFT JOIN products on orders.product_id = products.id
GROUP BY product_id ORDER BY total_amount DESC LIMIT 5
PostgreSQL:
SELECT products.*, SUM(amount) total_amount FROM orders
LEFT JOIN products on orders.product_id = products.id
GROUP BY products.id, products.name ORDER BY total_amount DESC LIMIT 5
Conctruct the query using Rails or use find_by_sql method to insert raw SQL.
This is a very long winded solution but answers your original question I think.
#Get only products that have a price and name. (may not be required).
products = Product.includes(:orders).where.not(name: nil, price: nil)
#Sets up an empty hash
hash = {}
products.each do |product|
product.orders.each do |order|
hash[product.name] = [order.amount]
end
end
Then you can sort your hash based on the values to sort by the amount of units sold (and also limit to 5)
Hash[hash.sort_by{|k, v| v}.reverse].take(5)
Interested to what others suggest via an ActiveRecord or SQL query though.

Add columns of same email id in rails

I have a table which has participant details
id name email level_id tournament_id
-------------------------------------------------------
20061 ABC abc#gmail.com 1 1
20062 xyz xyz#gmail.com 1 1
20063 ABC abc#gmail.com 2 1
20065 xyz xyz#gmail.com 2 1
and another table LevelScore
id participant_id level_id score
-----------------------------------------------------
1 20061 1 20
2 20062 2 30
3 20063 3 10
4 20061 4 30
5 20065 5 50
I want to add scores of same participants at different level, for eg: participant_id = 20061 & participant_id = 20063 are same participants participanting at different levels and i know that because they have same email.
Now i want to compute total score of participant i.e participant_score_level_1+participant_score_level_2+participant_score_level_3+participant_score_level_4 = Total score
How can i do it.
You can use participant.group(:email) for grouping the records according to the e-mail.
Try SELECT id, name, email, SUM(score) as score FROM participants GROUP BY email;. Then you can use result of the query to populate the new table.
You can group_by on email and in same query you can find sum of score also like below :
#participants = Participant.group(:email).select(:email, "SUM(score) as new_score")
Now When you try to create new table with unique email and sum of square you can do it like this :
#participants = Participant.group(:email).select(:email, "SUM(score) as new_score")
#participants.each do |participant|
Model.create(:email => participant.email, :sum_score => participant.new_score)
end

Limit an array by the sum of a value within the records in rails3

So lets say I have the following in a Post model, each record has the field "num" with a random value of a number and a user_id.
So I make this:
#posts = Post.where(:user_id => 1)
Now lets say I want to limit my #posts array's records to have a sum of 50 or more in the num value (with only the final record going over the limit). So it would be adding post.num + post2.num + post3.num etc, until it the total reaches at least 50.
Is there a way to do this?
I would say to just grab all of the records like you already are:
#posts = Post.where(:user_id => 1)
and then use Ruby to do the rest:
sum, i = 0, 0
until sum >= 50
post = #posts[i].delete
sum, i = sum+post.num, i+1
end
There's probably a more elegant way but this will work. It deletes posts in order until the sum has exceed or is equal to 50. Then #posts is left with the rest of the records. Hopefully I understood your question.
You need to use the PostgreSQL Window functions
This gives you the rows with the net sum lower than 50
SELECT a.id, sum(a.num) num_sum OVER (ORDER BY a.user_id)
FROM posts a
WHERE a.user_id = 1 AND a.num_sum < 50
But your case is trickier as you want to go over the limit by one row:
SELECT a.id, sum(a.num) num_sum OVER (ORDER BY a.user_id)
FROM posts a
WHERE a.user_id = 1 AND a.num_sum <= (
SELECT MIN(c.num_sum)
FROM (
SELECT sum(b.num) num_sum OVER (ORDER BY b.user_id)
FROM posts b
WHERE b.user_id = 1 AND b.num_sum >= 50
) c )
You have to convert this SQL to Arel.

Resources