0

I have following models in my rails app:

class Student < ApplicationRecord
  has_many :tickets, dependent: :destroy
  has_and_belongs_to_many :articles, dependent: :destroy

class Article < ApplicationRecord
  has_and_belongs_to_many :students, dependent: :destroy

class Ticket < ApplicationRecord
  belongs_to :student, touch: true

I need to extract all Students who has less than articles and I need to extract all Students who's last ticket title is 'Something'.

Everything I tried so far takes a lot of time. I tried mapping and looping through all Students. But I guess what I need is a joined request. I am looking for the most efficient way to do it, as database I am working with is quite large.

Tuesday Four AM
  • 1,186
  • 1
  • 11
  • 18

3 Answers3

2

You asked "I need to extract all Students who has less than articles". I'll presume you meant "I need to extract all Students who have less than X articles". In that case, you want group and having https://guides.rubyonrails.org/active_record_querying.html#group.

For example, Article.group(:student_id).having('count(articles.id) > X').pluck(:student_id).

To address your second question, you can use eager loading https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations to speed up your code.

result = students.filter do |student|
  students.tickets.last.name == 'Something'
end
MCI
  • 888
  • 1
  • 7
  • 13
  • Thank you. I am getting error though ActiveRecord::StatementInvalid (PG::GroupingError: ERROR: column "articles.id" must appear in the GROUP BY clause or be used in an aggregate function LINE 1: SELECT "articles".* FROM "articles" GROUP BY "articles"."stu... ^ : SELECT "articles".* FROM "articles" GROUP BY "articles"."student_id" HAVING (count(articles.id) < '5')): – Tuesday Four AM Jun 21 '19 at 08:36
  • Than you for the hint regarding the eager loading - it worked out for me: studs.each do |client| t = client.tickets.last if t && pt.include?(t.pt) ticket_res << client puts "Client with payment: #{client.name}, #{client.id}, #{client.tickets.last.pt}" end end – Tuesday Four AM Jun 21 '19 at 09:02
  • Ah, I edited the answer and added the `.pluck(:student_id)` to address Postgres's complaints. This https://stackoverflow.com/questions/16418504/pgerror-in-group-by-clause post explains why you need it – MCI Jun 21 '19 at 19:27
2

go with @MCI's answer for your first question. But a filter/select/find_all or whatever (although I havn't heared about filter method in ruby) through students record takes n queries where n is the number of student records (called an n+1 query).

studs = Student.find_by_sql(%{select tmp.id from (
  select student_id as id from tickets where name='Something' order by tickets.created_at desc
) tmp group by tmp.id})
dileep nandanam
  • 2,827
  • 18
  • 20
1

Here association is HABTM so below query should work

x = 10
Student.joins(:articles).group("articles_students.student_id").having("count(articles.id) < ?",x)
Praveen
  • 240
  • 1
  • 5