this query isn't efficient - ruby-on-rails

i have a single search field that is querying three different columns from two different tables: "companies" and "industries" from a positions table and "schools" from an educations table. it is successfully returning all users that meet ALL fields entered into the search field (using select_tag). this is from my view:
<%= form_tag(search_path, :method => :get, :id => "people_search") do %>
<div class="row-fluid">
<div class="span4">
<table>
<tr>
<td>
<div class="search-table" style="padding-left:55px">
<%= select_tag "all", options_for_select((#visible_companies + #visible_industries + #visible_schools).uniq, params[:all]), { :placeholder => "Search by companies, industries and schools...", :multiple => true, :js => "if (evt.keyCode == 13) {form.submit();}" } %>
</div>
</td>
<td>
<%= submit_tag "Add", id: "send-button", style:"width:175px;" %>
</td>
</tr>
</table>
</div>
<% end %>
</div>
and controller:
#visible_positions = Position.where{ is_visible.eq('true') }
#visible_educations = Education.where{ is_visible.eq('true') }
#visible_companies = #visible_positions.order("LOWER(company)").map(&:company).uniq
#visible_industries = #visible_positions.order("LOWER(industry)").map(&:industry).uniq
#visible_schools = #visible_educations.order("LOWER(school)").map(&:school).uniq
#c = #visible_positions.where{company.in(my{params[:all]})}.map(&:user_id)
#i = #visible_positions.where{industry.in(my{params[:all]})}.map(&:user_id)
#s = #visible_educations.where{school.in(my{params[:all]})}.map(&:user_id)
#C = #visible_positions.where{company.in(my{params[:all]})}.map(&:company)
#I = #visible_positions.where{industry.in(my{params[:all]})}.map(&:industry)
#S = #visible_educations.where{school.in(my{params[:all]})}.map(&:school)
#blacklist = []
#cis = #c + #i + #s
#experiences = ([#C,#I,#S].reject(&:empty?).reduce(:&))
#cis.uniq.each do |user_id|
unless #C.empty?
#C.uniq.each do |company|
unless Position.find_all_by_company(company).map(&:user_id).include?(user_id) || Position.find_all_by_industry(company).map(&:user_id).include?(user_id) || Education.find_all_by_school(company).map(&:user_id).include?(user_id)
#blacklist << user_id
end
end
end
unless #I.empty?
#I.uniq.each do |industry|
unless Position.find_all_by_industry(industry).map(&:user_id).include?(user_id) || Position.find_all_by_company(industry).map(&:user_id).include?(user_id) || Education.find_all_by_school(industry).map(&:user_id).include?(user_id)
#blacklist << user_id
end
end
end
unless #S.empty?
#S.each do |school|
unless Education.find_all_by_school(school).map(&:user_id).include?(user_id) || Position.find_all_by_company(school).map(&:user_id).include?(user_id) || Position.find_all_by_industry(school).map(&:user_id).include?(user_id)
#blacklist << user_id
end
end
end
end
unless #c.empty? && #i.empty? && #s.empty?
#users = User.find(#cis - #blacklist)
end
the search looks like this (notice the single field), with a sample query included (notice the AND filter...i'm the only user in the database that fits all search terms ['dartmouth college' for school, 'world health organization' for company, 'internet' for industry]):
i realize this is not an efficient query and am thinking of ways to speed it up, but could use some ideas at this point.
happy turkey day :)

Based on your description rather then on understanding your code I figured out something like this
User.joins(:positions, :educations).where("lower(positions.company) like lower(?) and lower(positions.industry) like lower(?) and lower(educations.school) like lower(?) and positions.is_visible and educations.is_visible", "%#{company}%", "%#{industry}%", "%#{school}%")
or if there is only one company or industry in column
User.joins(:positions, :educations).where("(lower(positions.company) = lower(?) or lower(positions.industry) = lower(?)) and lower(educations.school) = lower(?) and positions.is_visible and educations.is_visible", company,industry, school)
But to put many industries, companies, schools as params will be more complicated
and create indexes
create index positions_lower_company on positions (lower(company));
create index positions_lower_industry on positions (lower(industry));
create index educations_lower_school on educations (lower(school));
I hope it will help somehow.

Related

I have two models (events & artists) pagy is working fine with my artist model, but my event model isn't displaying last records

I do have a more elaborate index method in my events controller
def index
if params[:q]
params[:q][:combinator] = "and"
params[:q][:groupings] = []
split_geo = params[:q][:address_or_city_or_state_or_country_or_continent_cont_all].split((/(,\s*)+/))
split_geo.map! do |a|
I18n.transliterate a
end
split_geo.each_with_index do |word, index|
params[:q][:groupings][index] = { address_or_city_or_state_or_country_or_continent_cont_all: word }
end
end
#q = Event.ransack(params[:q])
#pagy, #events = pagy(#q.result(distinct: true))
end
In my events index page I have:
<h2>Upcoming Events</h2>
<br>
<%== pagy_bootstrap_nav(#pagy) %>
<br>
<div class="event-list-wrapper">
<% #events.upcoming_events.each do |event| %>
<%= render 'event', event: event %>
<% end %>
</div>
When I remove my upcoming_events scope, the records will display correctly.
In my event.rb model I have:
scope :upcoming_events, -> { where('event_start_date > ?', Time.now).or(where('event_end_date > ?', Date.yesterday)) }
Am I missing something in order to get pagy with work with my event scope?

Ruby on Rails sort on priorities

Following were my query to get list of countries along with cities:
#countries_cities = Product.joins(:user).where("country is not null and country <> ''").where("city is not null and city <> ''").where(:users => {:merchant_status => 1}).group(:country, :city).select("country,city").as_json
The output result were as follow:
Object[city:"Bangkok",country:"Thailand"],Object[city:"Phuket",country:"Thailand"],Object[city:"Malaysia",country:"Kuala Lumpur"],Object[city:"Malaysia",country:"Penang"],Object[city:"Shanghai",country:"China"],Object[city:"Beijing",country:"China"]
cchs = #countries_cities.group_by{|cc| cc["country"]}
#search_location_country = cchs
And the view is:
<ul id="color-dropdown-menu" class="dropdown-menu dropdown-menu-right" role="menu">
<% #search_location_country.each do |country, cities| %>
<li class="input" style="background:#ECECEC; "><%= country.upcase %></li>
<% cities.each do |city| %>
<li class="input"><%= city["city"].titleize %></li>
<% end %>
<% end %>
</ul>
Now the Drop down result follow this pattern:
Thailand
-Bangkok
-Phuket
Malaysia
-Kuala Lumpur
-Penang
China
-Beijing
-Shanghai
How can I ensure that Malaysia will always place at the top of the drop down lists? Thanks!!
How about:
#countries_cities = Product.joins(:user)
.where.not(country: [nil, ''])
.where(users: {merchant_status: 1})
.group(:country, :city)
.order("country!= 'Malaysia'")
.select(:country, :city)
.as_json
In Postgres, false is sorted before true (see this answer here: Custom ORDER BY Explanation)
You can customize you query like this:
#countries_cities = Product.joins(:user)
.where.not(country: [nil, ''])
.where(:users => {:merchant_status => 1})
.group(:country, :city)
.order("ORDER BY
CASE WHEN country = 'Malaysia' THEN 1
ELSE 2
END ASC")
.select(:country, :city)
.as_json
So we set the order of Malaysia = 2, and others = 1 to ensure the result with Malaysia will be on the top.

Search by full name

I am trying to write a query that finds clients by their full name.
I have a model named Client with first name, last name, and middle name fields.
I also have this client_full_name method in my User model :
def client_full_name
"#{self.Client_fname} #{self.Client_mi} #{self.Client_lname}"
end
# FOR SEARCHES
def self.search_by_client_full_name(query)
where("client_full_name like ?", "%#{query}%")
end
I have this in my controller
def index
#FOR SEARCHES
if params[:search]
#clients = Client.search_by_client_full_name(params[:search])
else
#clients = Client.all
end
end
And I have this in my Index
<h2>Search for Clients</h2>
<%= form_tag(clients_path, :method => "get", id: "search-form") do %>
Search by Client Name: <br />
<%= text_field_tag :search, params[:search], placeholder: "Search Clients" %>
<%= submit_tag "Search", :client_full_name => nil %>
<% end %>
This is the Error that I keep getting
SQLite3::SQLException: no such column: client_full_name: SELECT "clients".* FROM "clients" WHERE (client_full_name like '%John Smith%')
Thank you for the help.
This can be solution for your question.
patient_first_name_cont = "Ajay"
patient_last_name_cont = "Kumar"
shared_context = Ransack::Context.for(Request)
search_parents = Request.includes(:request_status).ransack(
{ patient_first_name_cont: patient_first_name_cont }, context: shared_context
)
search_children = Request.includes(:request_status).ransack(
{ patient_last_name_cont: patient_last_name_cont }, context: shared_context
)
shared_conditions = [search_parents, search_children].map { |search| Ransack::Visitor.new.accept(search.base)}
#req = Request.joins(shared_context.join_sources).where(shared_conditions.reduce(&:and))
You method client_full_name is an instance method of class Client:
def client_full_name
"#{self.Client_fname} #{self.Client_mi} #{self.Client_lname}"
end
It ( client_full_name ) is not a column(at least it doesn't appear to be) of your clients table. That's why you're getting this error:
SQLite3::SQLException: no such column: client_full_name: SELECT "clients".* FROM "clients" WHERE (client_full_name like '%John Smith%')
to make your query work, you need to change your method search_by_client_full_name to this(NOTE- Query below will work only for MySQL):
def self.search_by_client_full_name(query)
where("CONCAT_WS(' ', Client_fname, Client_mi, Client_lname) LIKE :q", :q => "%#{query}%")
end
For SQLite you can use || for concatenation:
def self.search_by_client_full_name(query)
where("(Client_fname || Client_mi || Client_lname) LIKE :q", :q => "%#{query}%")
end
But || will return NULL if any of the column has NULL, to avoid that you'll have to write case

How to determine the order from a controller

I have a restaurant with many employees and each employee has many customer ratings.
I want to create a stats page that shows the employees ordered by their monthly ratings average.
In the employee model:
def avg_rating
date = Date.today
ratings_total = self.ratings.sum(:score, :conditions => {:created_at => (date.beginning_of_month..date.end_of_month)}).to_f
ratings_count = self.ratings.count(:conditions => {:created_at => (date.beginning_of_month..date.end_of_month)}).to_f
return (ratings_total/ ratings_count)
end
In the restaurant controller I have:
def restaurant_stats
#restaurant = Restaurant.find(params[:restaurant_id])
#employees = #restaurant.employees.all
end
In the restaurant stats view:
<table>
<% #employees.each do |employee| %>
<tr>
<td><%= employee.name %></td>
<td><%= employee.avg_rating %></td>
</tr>
<% end %>
</table>
I'm not sure how to get the employees in the correct order? I assume I would have to retrieve the values in the correct order in the restaurant_stats action instead of just #restaurant.employees.all but I'm not sure how to because of the functions used in the employees model
You could do, from the controller:
#employees = #restaurant.employees.all.sort_by {|employee| employee.avg_rating}
or more concisely
#employees = #restaurant.employees.all.sort_by(&:avg_rating)
Note that this will load all employees in memory for sorting.
Try in the restaurant controller:
#employees = #restaurant.employees.all.sort {|x,y| y.avg_rating <=> x.avg_rating }
or
#employees = #restaurant.employees.all.sort_by(:avg_rating)
you could create an array and sort it
I think below works but haven't checked it
#employees = #restaurant.employees.collect {|p| [ p.name, p.avg_rating ]}
#employees.sort!{ |a,b| (a[1] <=> b[1]) }

How do you get the rows and the columns in the result of a query with ActiveRecord?

Is there a way with ActiveRecord to execute a custom SQL query and have it return an array of arrays where the first row is the column names and each following row is the row data? I want to execute something like:
connection.select_rows_with_headers "SELECT id, concat(first_name, ' ', last_name) as name, email FROM users"
And have it return:
[["id","name","email"],["1","Bob Johnson","bob#example.com"],["2","Joe Smith","joe#example.com"]]
This would allow me to print the results of the custom query in an HTML table like this:
<table>
<% result.each_with_index do |r,i| %>
<tr>
<% r.each do |c| %>
<% if i == 0 %>
<th><%=h c %></th>
<% else %>
<td><%=h c %></td>
<% end %>
<% end %>
</tr>
<% end %>
</table>
Note that select_all doesn't work because the keys in each hash are unordered, so you've lost the ordering of the results as specified in the query.
Not EXACTLY what you're looking for, but maybe:
connection.execute('select * from users').all_hashes
and you'll get back
[{:id => 1, :name => 'Bob', :email => 'bob#example.com'},{:id => 1, :name => 'Joe', :email => 'joe#example.com'}]
and you could do:
results = connection.execute('select * from users').all_hashes
munged_results = []
columns = results.first.keys.map(&:to_s)
munged_results << results.first.keys.map(&:to_s)
munged_results += results.map{|r| columns.map{|c| r[c]} }
something like that
edit:
results = connection.execute('select * from users').all_hashes
munged_results = []
columns = User.column_names
munged_results << columns
munged_results += results.map{|r| columns.map{|c| r[c]} }
That should be ordered properly.
Beyond that there is the result object that is returned from #execute that can be interrogated for bits of information. Methods like #fetch_fields get you the fields in order and #fetch_row will get you each row of the result set as an array (works like an iterator).
edit again:
OK, here's a good solution, modify for whatever DB you're using:
class Mysql::Result
def all_arrays
results = []
results << fetch_fields.map{|f| f.name}
while r = fetch_row
results << r
end
results
end
end
That will get them without a ton of overhead.
Use it like this:
connection.execute('select salt, id from users').all_arrays

Resources