Parent entity sorted by votes difference - sql

I have three models that look like this (I just left the stuff important for the question):
class Symbol < ActiveRecord::Base
has_many :mnemonic
end
class Mnemonic < ActiveRecord::Base
belongs_to :symbol
has_many :mnemonic_votes
end
class MnemonicVote < ActiveRecord::Base
belongs_to :mnemonic
attr_accessible :vote_up
end
:vote_up is of boolean type which if true means someone upvoted the mnemonic, and if false means someone downvoted it.
I would like to get top three mnemonics by vote difference. Let's say there are 5 mnemonic records in the database with the following number of up/down votes (MnemonicVote records with true/false as :vote_up field):
mnemonic up down total
mnemonic1 3 2 1
mnemonic2 17 3 14
mnemonic3 2 5 -3
mnemonic4 11 7 4
mnemonic5 5 5 0
I would like to get the following three mnemonics (with counts) by descending order:
mnemonic2 14
mnemonic4 4
mnemonic1 1
I wrote this actual code which gives me the result I want, but I am aware it sucks and I don't like how I did it because the data gets grouped and sorted after all the MnemonicVote records associated with a certaing Mnemonic record are fetched from the DB:
#mnemonics = Mnemonic.where(symbol_id: self.id) # here I fetch all Mnemonics associated with Symbol table
#mnemonics.sort_by { |mnemonic| mnemonic.votes_total }.reverse!
return #mnemonics.take(3)
where mnemonic.votes_total is a calculated attribute on Mnemonic object. I would like to get the same result by using a single AR (or even SQL) query. How can this be accomplished? Thanks.

I believe this is what you want:
Mnemonic.
joins(:mnemonic_votes).
select("mnemonics.*, SUM(IF(mnemonic_votes.upvote, 1, -1)) AS vote").
group("mnemonics.id").
order("vote DESC").
map { |m| [m.symbol, m.vote.to_i] }

Both answers were on the right track, except the IF clause that did not work with PostgreSQL (I would get function if(boolean, integer, integer) does not exist). Here is my final solution in case someone needs it:
Mnemonic.
select("mnemonics.*, SUM(CASE WHEN mnemonic_votes.vote_up THEN 1 ELSE -1 END) AS votes_total").
joins(:mnemonic_votes).
where(symbol_id: self.id).
group("mnemonics.id").
order("votes_total DESC").
limit(3)

Related

Select oldest HABTM record with group by clause

I want to show a line chart on the admin page (with chartkick) with the incremental number of scores related to their earliest export date.
I have the following models:
# score.rb
class Score < ApplicationRecord
has_and_belongs_to_many :export_orders, join_table: :scores_export_orders
end
# export_order.rb
class ExportOrder < ApplicationRecord
has_and_belongs_to_many :scores, join_table: :scores_export_orders
end
How do I select, for each Score having at least one ExportOrder, the corresponding ExportOrder with the earliest created_at (in date only format)?
I had a look at this, but my situation has a HABTM relationship instead of a simple has_many.
I tried this code, to get at least a mapping between oldest export date and number of scores:
sql = "
SELECT
COUNT(DISTINCT scores.id), MIN(export_orders.created_at::date)
FROM
scores
INNER JOIN
scores_export_orders
ON
scores.id = scores_export_orders.score_id
INNER JOIN
export_orders
ON
export_orders.id = scores_export_orders.export_order_id
GROUP BY
export_orders.created_at::date
".split("\n").join(' ')
query = ActiveRecord::Base.connection.execute(sql)
query.map { |v| [v['count'], v['min']] }
but the total number of scores is greater than all scores having an export date.
Any ideas?
Try:
class Score < ApplicationRecord
has_and_belongs_to_many :export_orders, join_table: :scores_export_orders
def earliest_export_date
export_orders.pluck(&:created_at).min
end
end
This will let you call #score.earliest_export_date, which should return the value you want.
I also think it's the most performant way to do it in ruby, although someone may correct me on that.
The following has better performance than Mark's solution since it relies on pure SQL. Basically, the GROUP BY clause required grouping by scores_export_orders.score_id rather than export_orders.created_at:
sql = "
SELECT
COUNT(DISTINCT scores_export_orders.score_id), MIN(export_orders.created_at::date)
INNER JOIN
scores_export_orders
INNER JOIN
export_orders
ON
export_orders.id = scores_export_orders.export_order_id
GROUP BY
scores_export_orders.score_id
".split("\n").join(' ')
query = ActiveRecord::Base.connection.execute(sql)
query.map { |v| [v['count'], v['min']] }
I couldn't find an exact equivalent in ActiveRecord instructions (all of such attempts were giving me strange results), so executing the SQL will also do the trick.

SQL query on rails

I am trying to find all items that are completed inside a list. I tried doing a query inside the Todoitem model, but it is showing [#<Todoitem id: nil>]
What am I doing wrong?
The query I thought goes:
Todoitem.find_by_sql("SELECT count(*) AS count_all FROM todoitems ti INNER JOIN todolists tl WHERE (ti.completed = 'true' AND ti.todolist_id = tl.id)")
my models are like this:
class Todolist < ApplicationRecord
belongs_to :user, required: false
has_many :todoitems, dependent: :destroy
end
class Todoitem < ApplicationRecord
belongs_to :todolist
end
To find all items that are in a todolist and is completed, your query will work. (I am assuming you are storing the boolean value for completed as a string since you used a string in your original query)
However, since you are only selectingcount(*) AS count_all, you will have to look at count_all from the first element in your array like this:
Todoitem.find_by_sql("SELECT count(*) AS count_all FROM todo....").first.count_all
This will return a number of completed todoitems in all todolists in total.
The more active record way of doing this would be like this:
Todolist.joins(:todoitems).where(todoitems:{completed:'true' }).count
To get the number of lists that has at least one completed todoitem, you can use:
Todolist.joins(:todoitems).group(:id).where(todoitems:{completed:'true' }).length
To find how many items that are completed in each list you can use:
Todolist.joins(:todoitems).group(:id).where(todoitems:{completed:'true' }).count
This will return a hash with the id of the list as a key, and the number of completed items as values.
To get the number of completed items in a speciffic task you can use:
Todolist.joins(:todoitems).where(id: id, todoitems:{completed:'true' }).count
You can follow this way.
Rails 5 - Postgresql
In this query, it will show only todolist that has todoitems.
Todolist.select('todolists.id, todolists.title, count(todoitems.todolist_id) as total').joins(:todoitems).group("todolists.id")
Another, This will show only todolist that has no todoitems with 0 result.
Todolist.select('todolists.id, todolists.title, count(todoitems.todolist_id) as total').left_joins(:todoitems).group("todolists.id")
I hope this help you.

rails return list of users with average rating above 5

I have 2 models User and Rating as follows:
class User < ActiveRecord::Base
has_many :ratings
end
class Rating < ActiveRecord::Base
belongs_to :user
end
Each user receives multiple ratings from 1 - 10. I want to return all users with an average rating of > 5. I've got this so far...
User.joins(:ratings).where('rating > ?', 5)
But that code returns all Users with any rating above 5. I want Users with an Average rating above 5.
I've seen other posts like this and that are asking similar questions, but I'm having a brainfart today, and can't simulate their question into an appropriate answer.
If you're looking at all users, why join first?
#avg = Ratings.group(:user_id).average("rating") #returns hash which contains arrays
#avg.each do |avg| #0 is user_id, 1 is value
puts avg[0] if avg[1] > 5
end
You need to defined method average for user rating.
Check link below this is good example of moving float to average.
How do I create an average from a Ruby array?
Hope this helps someone in the future. This will find the average rating of each user through the ratings table, and return all users with an average rating above 5.
User.joins(:ratings).merge(Rating.group(:user_id).having('AVG(rating) > 5'))
.having was my missing link. More examples of .having here and here

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.

How do I Create a Rails Model from a Subset of Table Records

I am trying to create several models that all pull from the same table. How do I limit the table records in each model? And before you tell me to change my data structure, this is a reporting application that is pulling from a preexisting backing DB over which I have no control.
My table looks something like:
Vehicle_Table
id vehicle_type name
--------------------
1 Car Foo
2 Car Bar
3 Motorcycle Baz
4 Car Barf
And I want to build models for Car and Motorcycle like:
class Car < ActiveRecord::Base
set_table_name 'Vehicle_Table'
end
and
class Motorcycle < ActiveRecord::Base
set_table_name 'Vehicle_Table'
end
But I have no idea how to say, "Hey Active Record, I only want records where vehicle_type = motorcycle in the motorcycle model."
I'm sure this is friggin' obvious, but all of my Google searches return ways to FIND subsets in a model rather than RESTRICT a model to a specific subset of records.
This is called Single Table Inheritance (STI).
If you had a column named type in your table, it would likely work automatically. But you can change this column name that Rails uses to tell types apart.
http://api.rubyonrails.org/classes/ActiveRecord/Base.html
Single table inheritance
Active Record allows inheritance by storing the name of the class in a column that by default is named “type” (can be changed by overwriting Base.inheritance_column). This means that an inheritance looking like this:
class Company < ActiveRecord::Base; end
class Firm < Company; end
class Client < Company; end
class PriorityClient < Client; end
When you do Firm.create(:name => "37signals"), this record will be saved in the companies table with type = “Firm”. You can then fetch this row again using Company.where(:name => '37signals').first and it will return a Firm object.
So, try this code
class Car < ActiveRecord::Base
set_table_name 'Vehicle_Table'
self.inheritance_column = :vehicle_type
end
Commented above but had limited editing abilities. I came across this exact problem and found the second half of the solution elsewhere. STI will allow you to get a subset of a table based on a column in the table but it will key off of the class name to find the records for that class. For example:
class Company < ActiveRecord::Base; end
class Client < Company; end
This will look at the table named Company for records that have the value 'Client' in a column named 'Type'.
You can override the column that STI checks by doing
class Company < ActiveRecord::Base
self.inheritance_column = :company_type
end
But it still looks for that column to contain 'Client'. You can override what value it looks for by doing this:
class Client < Company
def self.sti_name
1
end
end
This will now look at the company_type column for rows with a value of 1.
For Rails-4.2 this is nearly identical but does not need a class method:
private
self.inheritance_column = :company_type
def sti_name
1
end