Select minimum value from a group in activerecord - ruby-on-rails

I need to fetch like the winner bids, and a bid can be for a different date (don't ask why), so I need to select the bid with minimum bid price for each day.
Here are my models
Leilao
has_many :bids
Bid
belongs_to :leilao
#winner_name
#price
#date
I tried a solution already and got close to what I need. The problem is that, in some cases, when I create a new bid with lower price, I don't know why the results do not change.
Leilao.find(1).bids.having('MIN("bids"."price")').group('date').all
This seems to work, but as I said, it does not work in some cases when I create a new bid. But it worked properly once. So, if you do know what might be happening, please tell me.
I then searched for some way for doing this and I got the following
Leilao.find(1).bids.minimum(:price, :group => :date)
which works properly, but with this, I just fetch the dates and prices and I need all the bid data.
I could get it by doing this, but it feels really bad to me
winner_bids = Leilao.find(1).bids.minimum(:price, :group => :date)
winners_data = []
winner_bids.each do |date, price|
winners_data << Leilao.find(1).bids.where(price: price, date: date).first
end
winners_data
Any idea a better way to do this? Or what's wrong with my first approach?
Performance is not an issue, since this is just for academic propose but it just feels nasty for me
Also those Leilao.find(1) is just for explaining it here, I'm not using it allover the place, no.
Thanks in advance

see this
mysql> select * from bids;
+----+-------------+-------+------------+-----------+
| id | winner_name | price | date | leilao_id |
+----+-------------+-------+------------+-----------+
| 1 | A | 1.1 | 2012-06-01 | 1 |
| 2 | A | 2.2 | 2012-06-01 | 1 |
| 3 | A | 3.3 | 2012-05-31 | 1 |
| 4 | A | 4.4 | 2012-05-31 | 1 |
+----+-------------+-------+------------+-----------+
4 rows in set (0.00 sec)
mysql> select * from bids where leilao_id = 1 group by date order by price asc;
+----+-------------+-------+------------+-----------+
| id | winner_name | price | date | leilao_id |
+----+-------------+-------+------------+-----------+
| 1 | A | 1.1 | 2012-06-01 | 1 |
| 3 | A | 3.3 | 2012-05-31 | 1 |
+----+-------------+-------+------------+-----------+
2 rows in set (0.00 sec)
in rails
1.9.2-p290 :013 > Leilao.find(1).bids.order(:price).group(:date).all
[
[0] #<Bid:0x00000003553838> {
:id => 1,
:winner_name => "A",
:price => 1.1,
:date => Fri, 01 Jun 2012,
:leilao_id => 1
},
[1] #<Bid:0x00000003553518> {
:id => 3,
:winner_name => "A",
:price => 3.3,
:date => Thu, 31 May 2012,
:leilao_id => 1
}
]

As Amol said its not going to work, the SQL looks like
SELECT "whatever".* FROM "whatever" GROUP BY date ORDER BY price
But the group_by will be applied before order_by
I had this trouble when i had market table where is stored different types of records, i solved it by writing a class method, maybe not the best approach but working
def self.zobraz_trh(user)
markets = []
area = self.group(:area).all.map &:area
area.each do |market|
markets << self.where(["area = ? and user_id != ?", market,user]).order(:price).first
end
markets
end
where self is Market class

Related

Select unique record with latest created date

| id | user_id | created_at (datetime) |
| 1 | 1 | 17 May 2016 10:31:34 |
| 2 | 1 | 17 May 2016 12:41:54 |
| 3 | 2 | 18 May 2016 01:13:57 |
| 4 | 1 | 19 May 2016 07:21:24 |
| 5 | 2 | 20 May 2016 11:23:21 |
| 6 | 1 | 21 May 2016 03:41:29 |
How can I get the result of unique and latest created_at user_id record, which will be record id 5 and 6 in the above case?
What I have tried so far
So far I am trying to use group_by to return a hash like this:
Table.all.group_by(&:user_id)
#{1 => [record 1, record 2, record 4, record 6], etc}
And select the record with maximum date from it? Thanks.
Updated solution
Thanks to Gordon answer, I am using find_by_sql to use raw sql query in ror.
#table = Table.find_by_sql("Select distinct on (user_id) *
From tables
Order by user_id, created_at desc")
#To include eager loading with find_by_sql, we can add this
ActiveRecord::Associations::Preloader.new.preload(#table, :user)
In Postrgres, you can use DISTINCT ON:
SELECT DISTINCT ON (user_id) *
FROM tables
ORDER BY user_id, created_at DESC;
I am not sure how to express this in ruby.
Table
.select('user_id, MAX(created_at) AS created_at')
.group(:user_id)
.order('created_at DESC')
Notice created_at is passed in as string in order call, since it's a result of aggregate function, not a column value.
1) Extract unique users form the table
Table.all.uniq(:user_id)
2) Find all records of each user.
Table.all.uniq(:user_id).each {|_user_id| Table.where(user_id: _user_id)}
3) Select the latest created
Table.all.uniq(:user_id).each {|_user_id| Table.where(user_id: _user_id).order(:created_at).last.created_at}
4) Return result in form of: [[id, user_id], [id, user_id] ... ]
Table.all.uniq(:user_id).map{|_user_id| [Table.where(user_id: _user_id).order(:created_at).last.id, _user_id]}
This should return [[6,1], [2,5]]

Display latest messages from messages table, group by user

I'm trying to create an inbox for messaging between users.
Here are the following tables:
Messsages
Id | Message_from | message_to | message
1 | 2 | 1 | Hi
2 | 2 | 1 | How are you
3 | 1 | 3 | Hola
4 | 4 | 1 | Whats up
5 | 1 | 4 | Just Chilling
6 | 5 | 1 | Bonjour
Users
Id | Name
1 | Paul
2 | John
3 | Tim
4 | Rob
5 | Sarah
6 | Jeff
I'd like to display an inbox showing the list of users that the person has communicated and the last_message from either users
Paul's Inbox:
Name | user_id | last_message
Sarah| 5 | bonjour
Rob | 4 | Just Chilling
Tim | 3 | Hola
John | 2 | How are you
How do I do this with Active Records?
This should be rather efficient:
SELECT u.name, sub.*
FROM (
SELECT DISTINCT ON (1)
m.message_from AS user_id
, m.message AS last_message
FROM users u
JOIN messages m ON m.message_to = u.id
WHERE u.name = 'Paul' -- must be unique
ORDER BY 1, m.id DESC
) sub
JOIN users u ON sub.user_id = u.id;
Compute all users with the latest message in the subquery sub using DISTINCT ON. Then join to
table users a second time to resolve the name.
Details for DISTINCT ON:
Select first row in each GROUP BY group?
Aside: Using "id" and "name" as column names is not a very helpful naming convention.
How about this:
#received_messages = current_user.messages_to.order(created_at: :desc).uniq
If you want to include messages from the user as well, you might have to do a union query, or two queries, then merge and join them. I'm just guessing with some pseudocode, here, but this should set you on your way.
received_messages = current_user.messages_to
sent_messages = current_user.messages_from
(received_messages + sent_messages).sort_by { |message| message[:created_at] }.reverse
This type of logic is belongs to a model, not the controller, so perhaps you can add this to the message model.
scope :ids_of_latest_per_user, -> { pluck('MAX(id)').group(:user_id) }
scope :latest_per_user, -> { where(:id => Message.latest_by_user) }
Message.latest_per_user

Ruby on Rails - Calculate Rank

On Rails 4. I am building a contest application. I have three tables relevant to this use case:
Submissions - Attributes include:
:contest_year - The year the submission was created
:category_id - The contest category the submission is assigned to
:division_id - The contest division the submission is assigned to (out of five)
Scores - Attributes include:
:user_id - The ID of the judge who gives the score
:submission_id - The submission ID the score is tied to
:total_score - The integer number the judge provides as a score of the entry; usually out of ten
Judges - Attributes include:
:user_id - The user ID of the judge
:category_id - The category the judge is assigned to score
:division_id - The division the judge is assigned to score
One submission can have many scores (as there are multiple judges assigned to its category/division).
The first thing the app must do is find the submission's combined, final score out of all its child scores the various judges give it. I do this using a calculation in submission.rb:
def calculate_final_score
self.scores.average(:total_score)
end
So basically, it finds all the child scores of the submission and takes the average of those to find the final score. This final score is NOT an updated attribute of the submission table, it is a method calculation result.
Now, here is where my question is. I need to find a way to calculate the submission's ranking compared to other submissions with the SAME :contest_year, :category_id, and :division_id by comparing that above final score calculation. The ranking system MUST give the same rank when submissions have the same final score (a tie).
tl;dr, I need ranking behavior like this (sample submissions table):
----------------------------------------------------------------------
| ID | Contest Year | Division ID | Category ID | Final Score | Rank |
|--------------------------------------------------------------------|
| 1 | 2013 | 1 | 1 | 10 | 1 |
|--------------------------------------------------------------------|
| 2 | 2013 | 1 | 1 | 10 | 1 |
|--------------------------------------------------------------------|
| 3 | 2013 | 1 | 1 | 8 | 2 |
|--------------------------------------------------------------------|
| 4 | 2013 | 1 | 1 | 6 | 3 |
|--------------------------------------------------------------------|
| 4 | 2013 | 1 | 1 | 5 | 4 |
|--------------------------------------------------------------------|
| 5 | 2013 | 2 | 1 | 8 | 1 |
|--------------------------------------------------------------------|
| 6 | 2014 | 1 | 2 | 9 | 1 |
|--------------------------------------------------------------------|
| 7 | 2014 | 1 | 2 | 7 | 2 |
----------------------------------------------------------------------
This ranking information will be placed in my Active Admin submissions' index table page (and also as part of the CSV table output).
I am not much familiarly with Rails (or with Ruby). But in our Django application we had a similar situation where we needed to calculate ranks on some computed data. As we were using PostgreSQL as the DB backend, we used Postges's window functions (http://www.postgresql.org/docs/9.1/static/functions-window.html, http://www.postgresql.org/docs/9.1/static/functions-window.html, http://www.postgresql.org/docs/9.1/static/sql-expressions.html#SYNTAX-WINDOW-FUNCTIONS) for rank calculation. Following is a sample PostgresSQL query which can produce the result required by your question.
sql_query = %Q{SELECT
*, dense_rank() OVER (
PARTITION BY contest_year, category_id, division_id
ORDER BY final_score DESC
)
FROM (
SELECT
Submissions.id,
Submission.contest_year AS contest_year,
Submission.category_id AS category_id,
Submission.division_id AS division_id,
AVG(Scores.total_score) AS final_score
FROM Submissions
INNER JOIN Scores ON (Submissions.id = Scores.submission_id)
GROUP BY
Submissions.id,
Submission.contest_year,
Submission.category_id,
Submission.division_id
) AS FinalScores}
submissions_by_rank = Submission.find_by_sql(sql_query)
Note: You will need to add ORDER BY clause in the query if you want to order the result in a particular fashion.
In Ruby, assuming you have that #calculate_final_score method set up already:
class Submission < ActiveRecord::Base
def self.rank_all_submissions_by_group
keys = [ :contest_year, :category_id, :division_id ]
submission_groups = Submission.all.group_by do |sub|
values = keys.map { |k| sub.send(key) }
Hash[key.zip(values)]
end
return ranked_submissions_by_group = submission_groups.map do |group, submissions|
group[:ranked_submissions] = submissions.map do |s|
[s, s.calculate_final_score]
end.sort_by(&:last)
end
end
end
The data that'll be returned will look like:
[
{
:contest_year => 2013,
:division_id => 1,
:category_id => 1,
:ranked_submissions => [
[ <Submission>, 10 ],
[ <Submission>, 10 ],
[ <Submission>, 8 ],
[ <Submission>, 6 ],
[ <Submission>, 5 ],
],
},
{
:contest_year => 2013,
:division_id => 2,
:category_id => 1,
:ranked_submissions => [
[ <Submission>, 8 ],
],
},
{
:contest_year => 2014,
:division_id => 1,
:category_id => 2,
:ranked_submissions => [
[ <Submission>, 9 ],
[ <Submission>, 7 ],
],
},
]
It won't be very performant though, since it just goes through all submissions for all time, makes a lot of copies (I think?), and I'm not using the right sorting algorithm.
If you have any issues/concerns, let me know, and I can update my answer.
Additional Help
If you're looking for a way to run a raw query in Ruby, consider this trivial example a suggestion:
full_submissions_table_as_hash = ActiveRecord::Base.connection.select_all(<<-mysql)
SELECT *
FROM #{Submissions.table_name}
mysql
Another trivial example showing how the data can be rendered from a controller:
# assuming routes are properly set up to actions in HomeController
class HomeController < ActionController::Base
def ranked_submissions_text
render :text => Submission.rank_all_submissions_by_group
end
def ranked_submissions_json
render :json => Submission.rank_all_submissions_by_group
end
end

Efficient sorting of rows by multiple columns in Rails 3

I have a table name transactions with following columns
created_at | debit | credit | balance | account_id
2012-2-2 02:22:22 | 3000 | 0 | 0 | 8
2012-2-2 07:22:22 | 500 | 1000 | 0 | 8
2012-2-2 09:22:22 | 0 | 8000 | 0 | 8
2012-3-3 19:17:20 | 1000 | 0 | 0 | 8
2012-3-3 04:02:22 | 0 | 8000 | 0 | 8
Before calculating balance I need to
sort transactions by date (i.e. daily)
then sort by debit (higher debit must come first)
I have a million transactions distinguished by account_id. What is efficient way of sorting in such a scenario ?
Any answer will be appreciated.
This sounds a lot like this question
Transaction.all(:order => "created_at DESC, debit DESC")
This kind of querying is exactly what relational databases are good at and with proper indexing, it should be efficient for you.
For a particular account…
Transaction.find_by_account_id(some_id, :order => "created_at DESC, debit DESC")
If this is setup as a proper ActiveRecord association on the account, you can set the order there so the association is always returned in the preferred order:
class Account
has_many :transactions, :order => "created_at DESC, debit DESC"
end
This way, any time you ask an account for its transactions like Account.find(12345).transactions they'll be returned in the preferred sorting automatically.

Does it make sense to convert DB-ish queries into Rails ActiveRecord Model lingo?

mysql> desc categories;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(80) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
mysql> desc expenses;
+-------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| created_at | datetime | NO | | NULL | |
| description | varchar(100) | NO | | NULL | |
| amount | decimal(10,2) | NO | | NULL | |
| category_id | int(11) | NO | MUL | 1 | |
+-------------+---------------+------+-----+---------+----------------+
Now I need the top N categories like this...
Expense.find_by_sql("SELECT categories.name, sum(amount) as total_amount
from expenses
join categories on category_id = categories.id
group by category_id
order by total_amount desc")
But this is nagging at my Rails conscience.. it seems that it may be possible to achieve the same thing via Expense.find and supplying options like :group, :joins..
Can someone translate this query into ActiveRecord Model speak ?
Is it worth it... Personally i find the SQL more readable and gets my job done faster.. maybe coz I'm still learning Rails. Any advantages with not embedding SQL in source code (apart from not being able to change DB vendors..sql flavor, etc.)?
Seems like find_by_sql doesn't have the bind variable provision like find. What is the workaround? e.g. if i want to limit the number of records to a user-specified limit.
Expense.find(:all,
:select => "categories.name name, sum(amount) total_amount",
:joins => "categories on category_id = categories.id",
:group => "category_id",
:order => "total_amount desc")
Hope that helps!
Seems like find_by_sql doesn't have the bind variable provision like find.
It sure does. (from the Rails docs)
# You can use the same string replacement techniques as you can with ActiveRecord#find
Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
Well this is the code that finally worked for me.. (Francois.. the resulting sql stmt was missing the join keyword)
def Expense.get_top_n_categories options={}
#sQuery = "SELECT categories.name, sum(amount) as total_amount
# from expenses
# join categories on category_id = categories.id
# group by category_id
# order by total_amount desc";
#sQuery += " limit #{options[:limit].to_i}" if !options[:limit].nil?
#Expense.find_by_sql(sQuery)
query_options = {:select => "categories.name name, sum(amount) total_amount",
:joins => "inner join categories on category_id = categories.id",
:group => "category_id",
:order => "total_amount desc"}
query_options[:limit] = options[:limit].to_i if !options[:limit].nil?
Expense.find(:all, query_options)
end
find_by_sql does have rails bind variable... I don't know how I overlooked that.
Finally is the above use of user-specified a potential entry point for sql-injection or does the to_i method call prevent that?
Thanks for all the help. I'm grateful.

Resources