Find records with the condition from other model rails - sql

I have Bank and Rating models. Bank has many ratings. Also, bank can be active and inactive (when it's license is suspended).
Inactive bank has date field (license_suspended) in DB with the date, when license was suspended. Active banks has nil in this field.
I need to find ratings only for active banks. I can find all banks with license_suspended: nil and then find associated rating with current date and add it one-by-one to array, but I think there is a better way to do it. I need something like this:
#ratings = Rating.where(date: Date.today.beginning_of_month, bank: bank.license_suspended.blank?)
Thanks!

class Bank < ActiveRecord::Base
has_many :ratings
scope :active, -> { where(license_suspended: nil) }
end
class Rating < ActiveRecord::Base
belongs_to :bank
end
I think this will do what you want:
Rating.joins(:bank).where(date: Date.today).where(bank: {license_suspended: nil})
Or this:
Rating.joins(:bank).where(date: Date.today).merge(Bank.active) //this way you reuse active scope from Bank model
This will result in the following query:
SELECT "ratings".* FROM "ratings" INNER JOIN "banks" ON "banks"."id" = "ratings"."bank_id" WHERE "ratings"."date" = 'today_date' AND banks.license_suspended IS NULL

Assuming Ratings belong to a Bank, this looks like it'll do what you want:
Rating.joins(:bank).where(date: Date.today.beginning_of_month, bank: {license_suspended: nil}

Related

Select users whose last associated object was created less than x days before

I am building a Rails 4.2.7.1 which uses Postgres database and I need to write a feature for certain group of users.
class User < ActiveRecord::Base
has_many :payments
end
class Payment < ActiveRecord::Base
belongs_to :user
end
I need to select users from certain location who have exactly one payment and I also need to be able to pick users whose payment created_at attribute is exactly x
I tried
location.users
.without_deleted
.where(num_payments: 1)
.joins(:payments)
.where('payments.user_id = users.id').order('created_at
DESC').where("payments.created_at < ?", Date.today).group('users.id')
but it did not give me expected results.
Thanks!
You should start from User since this is what you want at end, and take joins with payments since you want to query it along.
User.joins(:payments)
.where(location_id: location.id, num_payments: 1)
.where(payments: { created_at: Date.today })

How to select parent by most recent child with Activerecord

I have a user model which has many subscriptions. I need to make two selections:
active users defined as user with a subscription in the last month
inactive users the ones that don't meet the (1) criteria
My subscription model has a simple scope .latest which is defined as ordered("created_at DESC").first.
To make selection (1) I use:
User.joins(:subscriptions).where("subscriptions.created_at > ?", 1.month.ago).distinct
This works, no problem there. However, I can't seem to define a working query for selection (2). Currently I use selection (1) and 'subtract' that from User.all to get the remaining users. This feels a bit like a hack.
The selection I need is:
all users whose most recent subscription was created more than 1 month ago
It's the most recent part of the query that has me stuck.
Any help appreciated.
Quick & dirty way: use complex SQL like this
Assume you are using auto incremental ID
User.select("users.*, MAX(subscriptions.id) as last_subscription_id")
.joins(:subscriptions)
.group("subscriptions.user_id")
.having("last_subscription_id = (select id from subscriptions where user_id=users.id and created_at < '2017-10-01 09:23:28.182475' order by id desc limit 1)")
Recommended way
Add last_subscription_id to users table and setup a belongs_to relationship user belongs_to last_subscription then do the joins normally. You need to update last_subscription_id of an user when new subscription for this user is created too.
Example: User class looks like this (I include has_many :subscriptions to show that we have 2 relations now)
class User < ActiveRecord::Base
has_many :subscriptions
belongs_to :last_subscription, class_name: 'Subscription', foreign_key: :last_subscription_id
end
And query will be
User.joins(:last_subscription).where("subscriptions.created_at < ?", 1.month.ago)
for the most recent part you can do this.
User.joins("LEFT JOIN subscriptions ON subscriptions.user_id = users.id").order("subscriptions.created_at DESC").select("subscriptions.created_at AS max_date").group_by(&:id).select{|key, value| value[0].max_date < 1.month.ago}.values.flatten

Ruby on Rails Active Record inner join not working

I have 2 models: Engagement, user. Engagement basically refers to the items booked by the user. each engagement is for a specific user. user in engagement has a foreign key. I want to join engagement with user so that i can see the details of the user.
Here are my models
class Engagement < ActiveRecord::Base
belongs_to :food_item
belongs_to :user
end
class User < ActiveRecord::Base
has_many :engagements
has_many :food_item, through: :engagements
end
I am running below query:
Engagement.joins("INNER JOIN users ON users.id = engagements.user_id")
It is not joining both the tables.
Any help will be appreciated
Your query is right.
You're doing a inner join and only returning engagements that have a relation to user.
To return the user data you should do something like this: Engagement.select('tasks.*, users.*').joins(:user). This way the object will have engagement and user attributes. But that is not the Rails way.
The "correct" way is:
engagements = Engagement.includes(:user)
engagements.first.user # this will return the user data
This way you're getting all engagements and preloading the user data, this way avoiding n + 1 queries (Eager-loading) ;)
Try this Engagement.joins(:users)
It should work.

ActiveRecord: Exclude group if at least one record within it doesn't meet condition

I have two models: an owner and a pet. An owner has_many :pets and a pet belongs_to :owner.
What I want to do is grab only those owners that have pets which ALL weigh over 30lbs.
#app/models/owner.rb
class Owner < ActiveRecord::Base
has_many :pets
#return only those owners that have heavy pets
end
#app/models/pet.rb
class Pet < ActiveRecord::Base
belongs_to :owner
scope :heavy, ->{ where(["weight > ?", 30])}
end
Here is what is in my database. I have three owners:
Neil, and ALL of which ARE heavy;
John, and ALL of which ARE NOT heavy;
Bob, and SOME of his pets ARE heavy and SOME that ARE NOT heavy.
The query should return only Neil. Right now my attempts return Neil and Bob.
You can form a group for each owner_id and check, if all rows within group match required condition or at least one row doesn't match it, you can achieve it with group by and having clauses:
scope :heavy, -> { group("owner_id").having(["count(case when weight <= ? then weight end) = 0", 30]) }
There is also another option, more of a Rails-ActiverRecord approach:
scope :heavy, -> { where.not(owner_id: Pet.where(["weight <= ?", 30]).distinct.pluck(:owner_id)).distinct }
Here you get all owner_ids that don't fit condition (searching by contradiction) and exclude them from the result of original query.
Isn't this simply a matter of finding the owners for whom the minimum pet weight is greater than some value:
scope :heavy, -> { group("owner_id").joins(:pets).having("min(pets.weight) >= ?", 30)}
Or conversely,
scope :light, -> { group("owner_id").joins(:pets).having("max(pets.weight) < ?", 30)}
These are scopes on the Owner, by the way, not the Pet
Another approach is to turn this into a scope on Owner:
Owner.where(Pet.where.not("pets.owner_id = owners.id and pets.weight < ?", 30).exists)
Subtly different, as it is checking for the non-existence of a per with a weight less than 30, so if an owner has no pets then this condition will match for that owner.
In database terms, this is going to be the most efficient query for large data sets.
Indexing of pets(owner_id, weight) is recommended for both these approaches.
What if you do it in two steps, first you get all owner_ids that have at least 1 heavy pet, then get all owner_ids that have at least 1 not-heavy pet and then grab the owners where id exists in the first array but not in the second?
Something like:
scope :not_heavy, -> { where('weight <= ?', 30) }
...
owner_ids = Pet.heavy.pluck(:owner_id) - Pet.not_heavy.pluck(:owner_id)
owners_with_all_pets_heavy = Owner.where(id: owner_ids)
You can just add a the uniq to your scope:
scope :heavy_pets, -> { uniq.joins(:pets).merge(Pet.heavy) }
It works on a database level, using the distinct query.

Resume of product query

I have the following schema table:
I have three activerecord models with their associations. I am struggling with a query which will show the following information for each product:
Product Name, Money Total, Quantity Sold Total
It should also take account on the status of the order that the product_line are associated with, which it should be equal to "successful".
I also want a second one query which it will show the above but it will have restriction based on the month (based on the orders.created_at column). For example if I want the sales for January of this product.
Product Name, Total Money so far, Quantity total, Month
I managed to create something but I think it isn't very optimized and I used ruby's group_by which it is doing many additional queries on the view. I would appreciate how you usually start thinking about creating a query like that.
Update
I think I almost managed to solve the first query and it is the following:
products = Product.joins(:product_lines).select("products.name, SUM(product_lines.quantity) as sum_amount, SUM(product_lines.quantity*products.price) as money_total"),group("products.id")
I tried to split each columns separately and find out how I could calculate it. I haven't take into account the order status though.
The associations are the following:
ProbudtLine
class ProductLine < ActiveRecord::Base
belongs_to :order
belongs_to :cart
belongs_to :product
end
Product
class Product < ActiveRecord::Base
has_many :product_lines
end
Order
class Order < ActiveRecord::Base
has_many :product_lines, dependent: :destroy
end
I finally did it.
First query:
#best_products_so_far = Product.joins(product_lines: :order)
.select("products.*, SUM(product_lines.quantity) as amount_total, SUM(product_lines.quantity*products.price) as money_total")
.where("orders.status = 'successful'")
.group("products.id")
Second query:
#best_products_this_month = Product.joins(product_lines: :order)
.select("products.*, SUM(product_lines.quantity) as amount_total, SUM(product_lines.quantity*products.price) as money_total")
.where("orders.status = 'successful'")
.where("extract(month from orders.completed_at) = ?", Date.today.strftime("%m"))
.group("products.id")