Get distinct records with a join left - sql

I know there are a lot of threads like that on SO but i still can't figure out what's wrong with my code. My models are:
User
class User < ActiveRecord::Base
has_many :events
end
Event
class Event < ActiveRecord::Base
belongs_to :user
belongs_to :venue
end
Venue
class Venue < ActiveRecord::Base
has_many :events
end
What i'm trying to do is to get all the recent venues used by a user (through the events he have created). My activerecord chaing is:
User
.first
.events
.joins(:venue)
.select('venues.id, venues.name, venues.address, events.beginning_at')
.uniq('venues.id')
and it produces the following query:
SELECT DISTINCT(venues.id), venues.name, venues.address, events.beginning_at
FROM "events" INNER JOIN "venues" ON "venues"."id" = "events"."venue_id"
WHERE "events"."user_id" = $1
ORDER BY "events"."beginning_at" ASC [["user_id", 1]]
However, what it gives me is a quite "randomish" list of recently used venues. ATM all the events are assigned to the same venue and the query above gives me ALL of those venues (so it goes down to one venue multiplied by number of all events that user created). What's more interesting - that query gives me 9 results but when i remove the DISTINCT method, i get 20 results (while there is just one venue in my db)
Can you give me any tip on what am i doing wrong?

Related

Rails: Query with inner join, ordering by association and no duplicate records

So I'm trying to create the ability to search appointments in my app and am having trouble with duplicate records and ordering.
To summarize:
An appointment is between two users with one being the booker and one being the bookee being booked. An appointment also has_many :time_options that a user can suggest, and then when the bookee selects and confirms one of these suggested time_options the start_time is applied to the similar start_time attribute on appointments. The models are structured as follows:
#appointment.rb
belongs_to :booker, foreign_key: :booker_id,
class_name: 'Profile'
belongs_to :bookee, foreign_key: :bookee_id,
class_name: 'Profile'
has_many :time_options, dependent: :destroy
scope :order_by_start_time, -> {
joins(:time_options)
.order("COALESCE(appointments.start_time, time_options.start_time) DESC")
}
def self.search(search)
if search
search_term = "%#{search}%"
joins(
"INNER JOIN profiles ON profiles.id IN (appointments.booker_id,
appointments.bookee_id)"
)
.where(
"CONCAT_WS(' ', profiles.first_name, profiles.last_name) ILIKE :term
OR appointments.service_title ILIKE :term",
term: search_term
)
else
where(nil)
end
end
#time_option.rb
belongs_to :appointment, touch: true
belongs_to :profile
#profile.rb
belongs_to :user, touch: true
has_many :booker_appointments, foreign_key: :booker_id,
class_name: 'Appointment',
dependent: :nullify
has_many :bookee_appointments, foreign_key: :bookee_id,
class_name: 'Appointment',
dependent: :nullify
I'm trying to let users search appointments by their appointment's service_title, or the other user's name (whether they are the booker or the bookee). I then want to order those search results by start_time. Either the start_time of the Appointment itself (which is only set when an appointment is confirmed), or if the appointment hasn't been confirmed yet and it's start_time is nil, then to instead use the earliest start_time of the associated time_options.
So in the controller, I'm doing something like:
def index
#appointments = Appointment.all_profile_appointments(#profile)
if params[:search]
#upcoming_appointments = #appointments.search(params[:search])
.order_by_start_time
.page(params[:page])
.per(12)
else
#upcoming_appointments = #appointments.order_by_start_time
.page(params[:page])
.per(12)
end
end
However, my search method returns duplicates, I believe because of the INNER JOIN. And if I add .distinct before or after the order_by_start_time method, I get the wonderful error: PG::InvalidColumnReference: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
So I'm pretty sure I just need to write a more complex, precise SQL statement; but, between needing to join the profiles table to search by name, needing join/coalesce the time_options table to order by start_time I'm not quite sure where to go next. For background, I'm using postgresql and Rails 4.
TL;DR: I'm trying to search for Appointment records by the name of the Profile who either booked or was booked, for the appointment. I'm then looking to order that query by appointments.start_time, or if no appointment start_time has been confirmed, then by the earliest time_options.start_time associated with the appointment. And I'm trying to do this without a pile of duplicate records.

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 })

Active Record query to find records that match all conditions in Rails has_many through relationship

I have two models, Apartments and Amenities, which are associated through ApartmentAmenities. I am trying to implement a filter where I only show apartments that have all of the amenities specified.
class Amenity < ActiveRecord::Base
has_many :apartment_amenities
has_many :apartments, through: :apartment_amenities
end
class ApartmentAmenity < ActiveRecord::Base
belongs_to :apartment
belongs_to :amenity
end
class Apartment < ActiveRecord::Base
has_many :apartment_amenities
has_many :amenities, through: :apartment_amenities
end
I've got a query working that will return all apartments that match at least one of the amenities of given set like so:
Apartment.joins(:apartment_amenities).where('apartment_amenities.amenity_id IN (?)', [1,2,3])
but this isn't quite what I'm going for.
Alright, after giving up for a few days then getting back to it, I finally found this question: How to find records, whose has_many through objects include all objects of some list?
Which led me to the answer that works properly:
def self.with_amenities(amenity_ids)
where("NOT EXISTS (SELECT * FROM amenities
WHERE NOT EXISTS (SELECT * FROM apartment_amenities
WHERE apartment_amenities.amenity_id = amenities.id
AND apartment_amenities.apartment_id = apartments.id)
AND amenities.id IN (?))", amenity_ids)
end

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.

Query a 3-way relationship in Active Record

I'm trying to figure out how to query this relationship without using find_by_sql
class User < ActiveRecord::Base
has_many :lists
end
class List < ActiveRecord::Base
has_many :list_items
belongs_to :user
end
class ListItem < ActiveRecord::Base
belongs_to :list
belongs_to :item
end
class Item < ActiveRecord::Base
has_many :list_items
end
this should be what we are using but How would I do this not by find_by_sql
in user.rb
def self.find_users_who_like_by_item_id item_id
find_by_sql(["select u.* from users u, lists l, list_items li where l.list_type_id=10 and li.item_id=? and l.user_id=u.id and li.list_id=l.id", item_id])
end
I've tried several different includes / joins / merge scenarios but am not able to get at what I'm trying to do.
thx
It's a bit difficult to tell exactly what query you're trying to do here, but it looks like you want the user records where the user has a list with a particular list_type_id and containing a particular item. That would look approximately like this:
User.joins(:lists => [:list_items]).where('lists.list_type_id = ? and list_items.item_id = ?', list_type_id, item_id)
This causes ActiveRecord to execute a query like the following:
SELECT "users".* FROM "users" INNER JOIN "lists" ON "lists"."user_id" = "users"."id" INNER JOIN "list_items" ON "list_items"."list_id" = "lists"."id" WHERE (lists.list_type_id = 10 and list_items.item_id = 6)
and return the resulting collection of User objects.