I want write properly query to get the most hated news.
Current query:
#most_hated_news = News.joins(:likes).where('likes.like = ?', false).order('likes.like DESC').first
Schema of likes table:
# Table name: likes
#
# id :integer not null, primary key
# like :boolean
# person_id :integer not null
# news_id :integer not null
I want to get news, that has most likes equal false.
The problem is, that query doesn't care ordering likes with false value and return news with most positive likes.
It return news with 3 positive(most) likes and 1 negative. I have in database news with 2 negative(most).
How to write correctly?
I would try something like this:
#most_hated_news = News.joins(:likes)
.where('likes.like = ?', false)
.group('news.id')
.order('COUNT(likes.id) DESC').first
Related
I am trying to optimise the performance of a query in Active Record. Previously I would do two SQL queries and it should be possible to do it in one.
These are the tables that I am running the queries on:
# Table name: notifications
#
# id :integer not null, primary key
# content :text(65535)
# position :integer
# Table name: dismissed_notifications
#
# id :integer not null, primary key
# notification_id :integer
# user_id :integer
This is the existing query:
where.not(id: user.dismissed_notifications.pluck(:id))
which produces:
SELECT `dismissed_notifications`.`id` FROM `dismissed_notifications` WHERE `dismissed_notifications`.`user_id` = 655
SELECT `notifications`.* FROM `notifications` WHERE (`notifications`.`id` != 1)
This is the SQL I would like to get, which returns the same records:
select *
from notifications n
where not exists(
select 1
from dismissed_notifications dn
where dn.id = n.id
and dn.user_id = 655)
You can write not exists Query like below
where('NOT EXISTS (' + user.dismissed_notifications.where('dismissed_notifications.id = notifications.id').to_sql + ')')
OR
There is also another way to reduce the number of queries is use select instead of pluck, it will create sub-query instead pulling records from database. Rails ActiveRecord Subqueries
where.not(id: user.dismissed_notifications.select(:id))
Which will generate below SQL query
SELECT `notifications`.*
FROM `notifications`
WHERE (
`notifications`.`id` NOT IN
(SELECT `dismissed_notifications`.`id`
FROM `dismissed_notifications`
WHERE `dismissed_notifications`.`user_id` = 655
)
)
I have an table that includes these field :
table phonebook
id
user_id
number
name
added
card_id
speeddial
updated_at
sms_group_name
some records have same sms_group_name and number,but there is some duplicate number for same sms_group_name. First I want to take distinct number for each sms_group_name and group_concat with , delimiter.
query result must be like this :
user_id, number,number,number,number,sms_group_name
select where condition is user_id
I tried all of them :
# #a = Phonebook.select(["DISTINCT number","sms_group_name"]).where(user_id: session[:user_id]).order(:sms_group_name).distinct
# #a = Phonebook.where(user_id: session[:user_id])
# Product.where.not(restaurant_id: nil).select("DISTINCT ON(name) name, restaurant_id, price, updated_at").order("name, updated_at")
# #a = Phonebook.where(user_id: session[:user_id]).select("DISTINCT ON(number) number, added, user_id, speeddial, updated_at,sms_group_name").order("sms_group_name")
# Phonebook.select("DISTINCT(number), *").where("user_id = ?", session[:user_id]).order("sms_group_name ASC").group_by(&:sms_group_name)
# Location.where("calendar_account_id = ?", current_user.calendar_accounts.first).group(:alias).order("alias ASC").group_by(&:category)
# #a = Phonebook.where("user_id = ?", session[:user_id]).order("sms_group_name ASC").group(:sms_group_name)
# #a = Phonebook.select("DISTINCT(number), sms_group_name").where("user_id = ?", session[:user_id]).order("sms_group_name ASC").group_by(&:sms_group_name)
# #a = Phonebook.select(:number)distinct.where("user_id = ?", session[:user_id]).order("sms_group_name ASC").group_by(&:sms_group_name)
# #a = Phonebook.select("DISTINCT(number), sms_group_name").group("sms_group_name")
most of them give error or does not work.
how can achive this ?
I tried select all values into object array ,after that I tried to eliminate them, but there is no suitable solution for now.
what can be solutions for both:
1- solution by using query and may be added one block
2- solution by using hash or array
best regards,
I have a Rails application with a Movie model. The Movie model has 'name' and 'release_date' as regular attributes, as well as a scope used to search for movie names with elasticsearch.
class Movie < ActiveRecord::Base
scope :movie_name_search, -> (term) {
movie_ids = elasticSearch(term, :name).map(&id)
Movie.where(id: movie_ids).reorder('').order_by_ids(movie_ids) unless movie_ids.nil?
}
end
I then set up my active admin to show this data
ActiveAdmin.register Promotion do
filter :movie_name_search, as: :string, label: "Movie Name"
index do
actions
column :name
column :release date, sortable: :release_date
end
end
Putting in a movie name into the search bar works perfectly, and sorting against release_date works perfectly, but I can't do both at the same time. Once I'm using the filter for movie names, the sort by date doesn't work. It only works when I remove the reorder and new order.
scope :movie_name_search, -> (term) {
movie_ids = elasticSearch(term, :name).map(&id)
Movie.where(id: movie_ids) unless movie_ids.nil?
}
It would appear that the ordering I enforce in the scope takes precedence over the sort of the column but I have no idea why. Any ideas?
You're resetting the scope chain when you call Movie.where in movie_search_name. You want to send where to self instead (i.e. just delete the Movie. part), so that prior conditions are preserved.
scope :movie_name_search, -> (term) {
movie_ids = elasticSearch(term, :name).map(&id)
where(id: movie_ids) unless movie_ids.nil?
}
Edit: I understand the issue now
Like you say, Elastic Search is returning an array of ids in sorted order, but where does not respect that order. It just pulls records from the database as it finds them, so long as their ids are in the array. It's no good for us to sort the records afterwards as an array, because then ActiveRecord can't apply additional clauses to the query. It has to be done as part of the query.
SQL does provide a way to enforce an arbitrary order: ORDER BY CASE, but it's not built in to Rails. Fortunately, it's not hard to add.
Let's suppose your search results are [2, 1, 3]. In SQL, we can retrieve them in order like this:
SELECT * FROM movies
WHERE id IN (2, 1, 3)
ORDER BY CASE id
WHEN 2 THEN 0
WHEN 1 THEN 1
WHEN 3 THEN 2
ELSE 3 END;
To make this compatible with ActiveRecord, we can add a class method to Movie:
app/models/movie.rb
class Movie < ActiveRecord::Base
# ...
def self.order_by_ids_array(ids)
order_clause = "CASE id "
ids.each_with_index do |id, index|
order_clause << sanitize_sql_array(["WHEN ? THEN ? ", id, index])
end
order_clause << sanitize_sql_array(["ELSE ? END", ids.length])
order(order_clause)
end
end
Now your ActiveAdmin scope uses both where and order_by_ids_array:
scope :movie_name_search, -> (term) {
movie_ids = elasticSearch(term, :name).map(&id)
where(id: movie_ids).order_by_ids_array(movie_ids) unless movie_ids.nil?
}
Reference:
http://www.justinweiss.com/articles/how-to-select-database-records-in-an-arbitrary-order/
Edit II: A real hack
Note: This requires a recent version of ActiveAdmin that uses Ransack.
The issue we're having is that filters don't play well with sorting. So here's
the new plan: let's add another column to our index table that shows the search
rank of each movie. This column will only appear when we've filtered by movie
name, and it will be sortable. This way there will be no "default" sort, but
you can sort by anything you want, including search ranking.
The trick is to insert a computed column into the query using a CASE like
above, but in the SELECT clause. We'll call it search_rank and it can be
accessed on any returned movie as movie.attributes['search_rank'].
app/admin/movies.rb
ActiveAdmin.register Movie do
filter :movie_search, as: string, label: 'Movie Name'
index do
# only show this column when a search term is present
if params[:q] && params[:q][:movie_search]
# we'll alias this column to `search_rank` in our scope so it can be sorted by
column :search_rank, sortable: 'search_rank' do |movie|
movie.attributes['search_rank']
end
end
end
end
Now in our model, we need to define the movie_search scope (as a class method)
and mark it as ransackable.
app/models/movie.rb
class Movie < ActiveRecord::Base
def self.ransackable_scopes(opts)
[:movie_search]
end
def self.movie_search(term)
# do search here
ids = elasticSearch(term, :name).map(&id)
# build extra column
rank_col = "(CASE movies.id "
ids.each_with_index do |id, index|
rank_col << "WHEN #{ id } THEN #{ index } "
end
rank_col << 'ELSE NULL END) AS search_rank'
select("movies.*, #{ rank_col }").where(id: ids)
end
end
I have setup my databases like this :
Product :
# id :integer not null, primary key
# code :string
# name :string
# category_id :integer
...
Order items :
# id :integer not null, primary key
# order_id :integer
# product_id :integer
# color_id :integer
# qty :integer default("0")
# price :money
...
Order :
# id :integer
# state :string
# placed_on :datetime
...
Now this setup make it really hard for me to pick the best selling products in each week from each category. How can I fix this? Another database to keep track of sales? Please help.
What you basically need is to join categories, products, order_items and orders tables.
Joining can be done with the following code:
rel = Category.joins(products: [order_items: :order])
#=> SELECT "categories".* FROM "categories" INNER JOIN "products" ON "products"."category_id" = "categories"."id" INNER JOIN "order_items" ON "order_items"."product_id" = "products"."id" INNER JOIN "orders" ON "orders"."id" = "order_items"."order_id"
Based on this you can filter on dates interval.
Let's assume we already have d1 and d2 values, which define start and end of the interval we are interested in:
rel = rel.where('orders.placed_on >= ? AND orders.placed_on <= ?', d1, d2)
Now you can aggregate on fields:
result = rel.select('categories.id, categories.name, SUM(order_items.qty) as qty, SUM(order_items.qty * order_items.price) as total')
.group('categories.id, categories.name')
sample = result.first
sample.id # => 1
sample.name # => "Category 1"
sample.qty # => 100.0
sample.total # => 500.0
This looks like a simple database query to me. The following should be the simple and straight-forward steps to accomplish it.
Join the three tables
Filter it by date
Group by product_id
Aggregate the qty
And, sort by aggregated value.
I'm not confident about the method to get the date. Please fill it in yourself in the query below.
SELECT P.id, P.name, P.category_id, SUM(qty) as LastWeekSales
FROM Product as P INNER JOIN Order Items as OI
ON P.id = OI.product_id
INNER JOIN Order as O
ON O.id = OI.order_id
WHERE O.placed_on <= GetTodaysDate() AND O.placed_on > GetOneWeekBacksDate()
GROUPBY P.category_id
ORDERBY WeekSales
All you need to do is, prepare this query in ruby-on-rails. I would do it but I don't know anything about ruby-on-rails.
+1 on taking care of this in the model. Here is some working drop-in code if you need it in the meantime. I'm practicing manipulating hashes tonight in case you couldn't tell, ha.
Add to Order model:
def self.last_week
Order.where(" created_at >= ? ", 1.week.ago.utc)
end
Add to whatever controller:
#qty_hash = category = Hash.new 0;
#output_hash = Hash.new { |hash, key| hash[key] = {} }
#recently_ordered_items = OrderItem.find_all_by_order_id(Order.last_week)
#recently_ordered_items.each { |i| #qty_hash[i.product_id] += i.qty }
#recent_products=Product.find_all_by_id(#qty_hash.keys)
#qty_hash.each do |key, value|
#recent_products.each { |i| category = i.category_id if i.id == key }
#output_hash[category] = #output_hash[category].merge(#qty_hash.slice(key))
end
#output_hash is the output and is in the format:
{1=>{3=>9}, 2=>{4=>8, 6=>5, 7=>4}}
In this case the categories are 1 and 2, product ids are 3 (9 sold), 4 (8 sold), 6 (5 sold), and 7 (4 sold)
Tested and working. Good luck.
I have the following models:
# == Schema Information
#
# Table name: quotes
#
# id :integer not null, primary key
# bound_rate_id :integer
class Quote < ActiveRecord::Base
#snip
end
# == Schema Information
#
# Table name: rates
#
# id :integer not null, primary key
# quoted_premium :integer
class Rate < ActiveRecord::Base
#snip
end
I want to create a query that would calculate the same thing as this loop:
sum = 0
for quote in Quote.all
rate = Rate.find(quote.bound_rate_id)
sum += rate.quoted_premium
end
How would I do this using ActiveRecord's query interface? (I am using Rails 4.)
EDIT: I already have ActiveRecord instances from previous queries over Quote, so I would prefer to have my query start from the quotes table and join to the rates table, not the other way around. Like this:
some_quotes = Quote.where(:some_other_property, my_param);
sum_of_rates = some_quotes.?????
Try this out
sum = Rate.where(:id => Quote.pluck(:bound_rate_id).compact).sum(:quoted_premium)
After adding relations try this out
sum = Quote.joins(:rate).sum('rates.quoted_premium') # it will get sum of all query's quoted_premium
To get sum of some specific add where clause
sum = Quote.joins(:rate).where(:bound_rate_id => [list of Rate ids]).sum('rates.quoted_premium')
If you get a Mysql2::Error: Unknown column 'rates.bound_rate_id' in 'on clause' error, specify how ActiveRecord should put together the join
sum = Quote.joins('INNER JOIN rates ON quotes.bound_rate_id = rates.id').sum('rates.quoted_premium')