Rails SQL Where Count - sql

I have a Posts Table and A Sec_photo table :
class Post < ActiveRecord::Base
has_many :sec_photos
I am trying to do an advanced search form where it finds posts based on their sum of sec_photos :
#posts = #posts.where(:sec_photos.count > 2) is failing and I have looked online and attempted many other solutions but none seem to work.
Does anyone have any ideas?
Ps: It's a necessity for the line to be in the form #posts = #posts.where as that coincides with other conditions.
The advanced search from searches for other fields like category_id and location_id as such
#posts = Post.all
#posts = #posts.where('category_id = ?', #advanced_search.category_search) if #advanced_search.category_search.present?
#posts = #posts.where('location_id = ?', #advanced_search.location_search) if #advanced_search.location_search.present?
#posts = #posts.where("strftime('%m %Y', created_at) = ?", #advanced_search.newdate_search.strftime('%m %Y')) if #advanced_search.newdate_search.present?
The last option would be to show posts with sec_photos either more than 2 or less than 2

You can do as following:
#posts = Post.whatever_scopes_you_use
#posts = #posts.joins(:sec_photos).group('posts.id').having('COUNT(sec_photos.id)
> 2')
This last line will select all posts having strictly more than 2 sec_photos associated. Of course, you can easily make a scope from this, accepting a count variable to make it dynamic ;-)
From my previous answer: How to return results filtered on relations count to a view in RAILS?

Related

Select model based on amount of related models with certain conditions

I have a Post that has_many :comments. Let's say Comment has the following field: another_model_id. I would like to select Posts, that have from 2 to 5 comments with another_model_id = 10 (for example). I tried several constructions but with no success :(
Example of what I tried:
# Invalid SQL syntax error
Post
.joins(:comments)
.select("count(comment.another_model_id = 10) as comments_count)
.where("comments_count BETWEEN 2 AND 5")
I have literally no idea where to dig. Is it possible to achieve that in a single query? Thanks!
Post
.joins(:comments)
.where(comments: { another_model_id: 10 })
.group('posts.id')
.having('count(comments.id) > 2 AND count(comments.id) < 5')
Using counter_cache is the best practice for your scenario:
In your Comment model, you need to set the counter_cache:
belongs_to :post, counter_cache: true
Then simply you can use this query:
Post.joins(:comments)
.where(comments: { another_model_id: 10 })
.where('comments_count > ? AND comments_count < ?', 2, 5)

Order comments from certain users first, then by comment score

In a Rails 4 project I have articles which has_many comments.
I’m trying to show a list of comments under an article ordered by the comment score (an integer column in the comment table), but where comments from a small number of users are shown first (eg. admins & mods). These users would be passed as an array of user_ids to the query (user_id is also a column in the comment table):
class Comment < ActiveRecord::Base
# article_id :integer
# user_id :integer
# score :integer
belongs_to :article
belongs_to :user
scope :by_users, -> (user_ids) { order("user_id IN (?), score DESC", user_ids) }
Thus:
some_article.comments.by_users([1,2,3])
This scope gives a syntax error, and I can’t quite work out a query that orders comments so that ALL an article’s comments are returned, shown first from users with a user_id in the passed array, then secondarily ordered by comment score.
Update:
mahemoff’s great conditional idea doesn’t work for Postgres, but lead to this non-working attempt:
scope :by_users, -> (user_ids) { order("case when (user_id IN (?)) then 0 else 1 end, score DESC", user_ids) }
Which gives me a PostgreSQL syntax error relating to the placeholder ? that I haven’t been able to fix.
With mahemoff & mu is too short’s help and suggestions, this is my working scope:
scope :by_users, -> (user_ids) { order([sanitize_sql("case when (user_id IN (#{user_ids.join(',')})) then 0 else 1 end"), "score DESC" ]) }
It’s somewhat imperfect, as Rails’ order apparently doesn’t support the ? placeholder, so instead I’ve used string interpolation and (even though user_ids isn’t user exposed) sanitized the sql.
...a frustrating problem with this though, is that it’s impossible to use .last to retrieve the final record. some_article.by_users[1,2].last now throws an error as AR can’t work out where to reverse the ordering:
ORDER BY case when (user_id IN (1 DESC, 2)) then 0 else 1 end DESC, score ASC LIMIT 1
sigh...
Try this:
class Comment < ActiveRecord::Base
scope :by_users, -> (user_ids) { order("if(user_id IN (#{ids.join ','}), 0, 1), score DESC") }

Rails - Pass current_user into SQL query

I'm looking for a way to pass the Rails/Devise variable "current_user" into a SQL query.
I have an app with two models, Users and Tips. Users can friend other users and send tips to each other. I'm trying to display a user's friend list ordered by the number of tips that the user has sent to each friend, so that the friend to whom the user has sent the most tips shows up at the top of the friend list, and so on.
I've read that RoR isn't equipped to handle this kind of query easily, so I've been able to put together the following SQL query, which works fine:
def friend_list
#friends = User.find_by_sql("SELECT users.*,
COUNT(tips.id) AS c FROM users, tips
WHERE tips.recipient_id = users.id
AND tips.user_id = 3
GROUP BY users.id ORDER BY c DESC")
end
The only problem is, I have manually entered a user.id there ("3") when ideally that "3" would be replaced by "current_user" so that each time a user loads this list they get their friend list ranked by who they themselves have sent tips to, rather than everyone just seeing user 3's ranking.
In an ideal world this would look something like:
AND tips.user_id = current_user
but that doesn't work. How can I pass a variable into this query so that it is different for each person viewing?
UPDATE:
User model (excerpt):
has_many :tips
has_many :received_tips, :class_name => "Tip", :foreign_key => "recipient_id"
Tip model (excerpt):
belongs_to :user
belongs_to :recipient, :class_name => "User"
You cannot access current_user in models if using devise...though there are few good ways to do it
##method one---in your controller
#users=User.find(current_user.id)
##method two---in your controller
#users=User.get_user_details(current_user.id)
##in model
def self.get_user_details(current_user_id)
User.find(current_user_id)
end
###you can also use in this way in controller
#all_videos = Video.where("videos.user_id !=?",current_user.id)
.........so this can be your solution............
##in controller or pass current_user and user it in your model
def friend_list
#friends = User.joins(:tips).select("users.* and count(tips.id) as c").where ("tips.users_id= ?",current_user.id).group("users.id").order("c DESC")
##or
#friends = User.all(:joins => :tips, :select => "users.*, count(tips.id) as tips_count", :group => "users.id",:order=>"tips_count DESC")
end
You can try this
def friend_list
#friends = User.find_by_sql("SELECT users.*,
COUNT(tips.id) AS c FROM users, tips
WHERE tips.recipient_id = users.id
AND tips.user_id = ?
GROUP BY users.id ORDER BY c DESC", current_user.id)
end
Anyway it is not that hard to do this query using active_record.
I know this is old, but I want to add that you could also use string injection.
def friend_list
#Declare the query string you want to pass
#query = "SELECT users.*,
COUNT(tips.id) AS c FROM users, tips
WHERE tips.recipient_id = users.id
AND tips.user_id = #{current_user.id}
GROUP BY users.id ORDER BY DESC"
#friends = User.find_by_sql(#query)
end

Filtering Parents by Children

I'm doing a simple blog application - There are posts, which have many tags through a posts_tags table (my models are below). What I have implemented is if a user clicks a tag, it will show just the posts with that tag. What I want is for the user to them be able to select another tag, and it will filter to only the posts that have both of those tags, then a third, then a fourth, etc. I'm having difficulty making the active record query - especially dynamically. The closest I've gotten is listed below - however its in pure SQL and I would like to at least have it in ActiveRecord Rubyland syntax even with the complexity it contains.
Also, the "having count 2" does not work, its saying that "count" does not exist and even if I assign it a name. However, it is outputting in my table (the idea behind count is that if it contains a number that is as much as how many tags we are searching for, then theoretically/ideally it has all the tags)
My current test SQL query
select posts_tags.post_id,count(*) from posts_tags where tag_id=1 or tag_id=3 group by post_id ### having count=2
The output from the test SQL (I know it doesnt contain much but just with some simple seed data).
post_id | count
---------+-------
1 | 2
2 | 1
My Models:
/post.rb
class Post < ActiveRecord::Base
has_many :posts_tags
has_many :tags, :through => :posts_tags
end
/tag.rb
class Tag < ActiveRecord::Base
has_many :posts_tags
has_many :posts, :through => :posts_tags
end
/poststag.rb
class PostsTag < ActiveRecord::Base
belongs_to :tag
belongs_to :post
end
Give a try to:
Post.joins(:tags).where(tags: {id: [1, 3]}).select("posts.id, count(*)").group("posts.id").having("count(*) > 2")
I think "count = 2" is not correct. It should be "count(*) = 2". Your query then will be
select post_id,count(post_id)
from posts_tags
where tag_id = 1 or tag_id = 3
group by post_id
having count(post_id) = 2
In general you want to stay away from writing raw sql when using rails. Active Record has great helper methods to make your sql more readable and maintainable.
If you only have a few tags you can create scopes for each of them (http://guides.rubyonrails.org/active_record_querying.html#scopes)
Since people are clicking on tags one at a time you could just query for each tag and then use the & operator on the arrays. Because you have already requested the exact same set of data from the database the query results should be cached meaning you are only hitting the db for the newest query.

Need help finding the first created record of each group, in Rails ActiveRecord

I have a table called FeedItems, and basically the user needs to see the first created feed item for each post id.
At the moment I'm using 'group', and SQLite is giving me the last created, for some reason. I've tried to sort the list of feed items before grouping, but it makes no difference.
user_id | post_id | action
----------------------------------------
1 1 'posted'
3 2 'loved' <--- this, being created afterwards
should not appear in the query.
I know this has to do with an INNER JOIN, and I have seen some similar examples of this being done, however the difference is that I'm not sure how to do this AND use the existing query I already have to find out if the users are friends with the current user.
Here's the code for the model:
class FeedItem < ActiveRecord::Base
belongs_to :post
belongs_to :user
default_scope :order => 'created_at desc'
scope :feed_for, lambda { |user| feed_items_for(user).group(:post_id) }
private
def self.feed_items_for(user)
friend_ids = %(SELECT friend_id FROM friendships WHERE (user_id = :user_id OR friend_id = :user_id))
ghosted_ids = %(SELECT pending_friend_id FROM friend_requests WHERE user_id = :user_id)
where("user_id IN (#{friend_ids}) OR user_id IN (#{ghosted_ids}) OR user_id = :user_id", {:user_id => user.id})
end
end
Any help would be greatly appreciated, thanks.
ActiveRecord provides you with dynamic attribute-based finders, which means that you have
Feeds.find_last_by_post_id(117)
If you would prefer to have the fist, you have:
Feeds.where(:post_id => 117).first
And you can always do, which I'm not sure is a "best practice":
Feeds.where(:post_id => 117).order('created_on DESC').first
You can read more about it at:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html