I have a User that has_many messages.
I need a create a query that will
'Get me all users who's (message.opened == false) count < 3'
Right now, I am using User.all, iterating through all users, and counting manually. I understand that this isn't very efficient and it can be all done in one query, but I am new to SQL/ActiveRecord so need some help here.
Thanks
Assuming Rails 3 syntax. You can do something like:
User.joins(:messages).where(:messages => {:opened => false}).group(:user_id).having("COUNT(messages.id) < 3)
This should work:
User.includes(:messages).group("users.id").where("messages.opened = 0").having("count(messages.id) < 3")
This will create two queries, one for the grouped query, and one for eager loading the resulting users and messages with a join.
Here is solution to your problem
User.includes(:messages).group("users.id").where("messages.opened = 0").having("count(messages.id) < 3")
but what else you can do is to create a scope for this
scope :not_opened_less_three_count, includes(:messages).group("users.id").where("messages.opened = 0").having("count(messages.id) < 3")
And then you can use it anywhere you needed as follow
User.not_opened_less_three_count
Try this
User.includes(:messages).group('users.id').having('SUM(IFNULL(messages.opened = 0, 1)) < 3')
It works at least on MySQL, AND assuming your boolean true are 1 in database.
EDIT I had reversed the condition
PS IFNULL is there to handle if messages.opened can be NULL
Related
I am currently trying to filter out from selected data in Ruby on Rails those where the attribute "amount_available" is greater than zero. This would be no problem via #events.where(ticket_categories.amount_available > 0), but ticket_categories is an array with not a fixed length, because there can be multiple categories. How can you easily iterate through the array in the where clause and do this comparison?
I only need the events in the output where at least one associated category has the amount_available > 0.
This is my code:
#upcoming_events = #events.where("date >=?", Date.current)
#available_events = #upcoming_events.where(ticket_categories[0].amount_available > 0)
json_response(#available_events)
You can chain where conditions and you can add conditions that are based on associated models with joins:
available_events = #events
.where('date >= ?', Date.current)
.joins(:ticket_categories).where('ticket_categories.amount > 0')
.group(:id)
render json: available_events
Note: Database joins might return duplicate records (depending on your database structure and the condition) therefore the need to group the result set by id.
It is only a representation because the Events table is linked to TicketCategories via has_many. I use PostgresSQL and could now solve it with the following code:
#upcoming_events = #close_events.where("date >=?", Date.current)
available_events = []
#upcoming_events.each do |event|
event.ticket_categories.each do|category|
if category.amount_available > 0
available_events.push(event)
break;
end
end
end
render json: available_events
How could these two queries be merged in Rails 5?
Event
.where(starts_at: date.beginning_of_day..date.end_of_day)
.where(kind: "opening")
Event.where("cast(strftime('%w', starts_at) as int) = ?", date.wday)
.where(kind: "opening")
.where(weekly_recurring: true)
I need to take all these events in one query for performance.
Thanks for the help
If you want to combine results then
Event.where(starts_at: date.beginning_of_day..date.end_of_day).where(kind: "opening").where("cast(strftime('%w', starts_at) as int) = ?", date.wday).where(kind: "opening").where(weekly_recurring: true)
If you want two separate results in one query, i dont think active records has such method.
I have always easier to write complex queries such as the one you need directly in SQL; It will be faster for you to write than trying to shoehorn it into ActiveRecord.
In your case, I would do something like this:
query = "starts_at between #{date.beginning_of_day} and #{date.end_of_day}
and kind = 'opening'
and weekly_recurring = true
and cast(strftime('%w', starts_at) as int) = #{date.wday}"
Event.where(query)
I find this approach easier and more maintanable.
I have a Work model with a video_id, a user_id and some other simple fields. I need to display the last 12 works on the page, but only take 1 per user. Currently I'm trying to do it like this:
def self.latest_works_one_per_user(video_id=nil)
scope = self.includes(:user, :video)
scope = video_id ? scope.where(video_id: video_id) : scope.where.not(video_id: nil)
scope = scope.order(created_at: :desc)
user_ids = works = []
scope.each do |work|
next if user_ids.include? work.user_id
user_ids << work.user_id
works << work
break if works.size == 12
end
works
end
But I'm damn sure there is a more elegant and faster way of doing it especially when the number of works gets bigger.
Here's a solution that should work for any SQL database with minimal adjustment. Whether one thinks it's elegant or not depends on how much you enjoy SQL.
def self.latest_works_one_per_user(video_id=nil)
scope = includes(:user, :video)
scope = video_id ? scope.where(video_id: video_id) : scope.where.not(video_id: nil)
scope.
joins("join (select user_id, max(created_at) created_at
from works group by created at) most_recent
on works.user_id = most_recent.user_id and
works.created_at = most_recent.created_at").
order(created_at: :desc).limit(12)
end
It only works if the combination of user_id and created_at is unique, however. If that combination isn't unique you'll get more than 12 rows.
It can be done more simply in MySQL. The MySQL solution doesn't work in Postgres, and I don't know a better solution in Postgres, although I'm sure there is one.
I need to return an array of a category where a particular item of which it has_many, has a .count >= 2
I know this sytax is wrong, but I'm trying to figure out the correct way, any tips??
Model.where(model.has_many_relationship_item.count >= 2) ??
Thanks in advance!!
Category.joins(:items).group("cateogry.id").having("COUNT(items.id) >= 2")
The only way I know how to do this is:
Model.joins(:items).group('models.id').having('count(items.id) >= 2')
assuming (for example) the class of the related model is Item
Model.joins(:association).group('model_table.id').having('COUNT(association_table.id) >= 2')
I currently have a scope where I am attempting to find last record created in an association and select it if a particular boolean value is false
IE Foo has_many Bar's and Bar's has a boolean column named bazzed
scope :no_baz, joins(:bars).order("bars.id DESC").limit(1).where("bars.bazzed = 'f'")
The problem with this is that rails turns this query into something like this
SELECT "foos".* FROM "foos" INNER JOIN "bars" ON "bars"."foo_id" = "foos"."id" WHERE (bars.bazzed = 'f') ORDER BY bars.id DESC LIMIT 1
the problem lies that rails is calling the order and limit after the where clause, what i'm looking for is to do the order and limit first to try and find the last bar that has bazzed set to false.
Is there a native AR way to perform the query I am attempting to accomplish?
EDIT
I am trying to grab the foo's that have a bar where the last bar they have has bazzed set to false and only if the last bar that that foo has has a false bazzed.
Ok, I would suggest this for the query on the "foo" model:
Foo.bars.where("bars.bazzed = ?", 'f').all( :order => "created_at DESC").first
Note: 'f' can be replaced by false, depending on the value you use in your "bazzed" column, of course.
[Edit]
Ok, as I think I better understand the problem, here is a suggestion, but for a public method and not a scoped query.
def no_baz
all_no_baz_foos = Array.new
Foo.all.each do |foo|
last_bar = foo.bars.all.order("bars.id DESC").first
if last_bar.bazzed == 'f'
all_no_baz_foos << foo
end
end
return all_no_baz_foos
end
This method will return an Array with all the no_baz_foos record in it. As I did not test my code, you may have to change few things for it to work, but I think you get the idea.
For the "scope" method, I just can't find a way to chain correctly the queries to have the desired result. If anyone else knows how to achieve that using a scope, I'll be glad to hear the solution too.
Using a class method for now but the problem with that lies that it returns an array object and not an active record relation which is what i'm trying to return. Still attempting to get the query correctly done.