Rank Query Results - Rails - sql

I've got the following query that searches my customer database and does so successfully:
SELECT "customers".* FROM "customers" WHERE ((("customers"."first_name" IN ('John', 'Doe') OR "customers"."last_name" IN ('John', 'Doe')) OR "customers"."email" IN ('John', 'Doe')) OR "customers"."main_phone" IN ('John', 'Doe'))
The equivalent Rails query is:
array = ["John","Doe","111-111-1111"]
Customer.where(first_name: array).or(customers.where(last_name: array)).or(customers.where(email: array)).or(customers.where(main_phone: array))
This works fine, however I am wanting to rank the results. For example, if record # 1 matches for both first and last name, I want that record to display at the top of my results. How could I do this?

The combination of where and or actually create a single SQL query with no "precedence" of the results
You will have to
1. break it down to separate queries
2. for each record found, count in how many queries it was found
3. order your results by this counter in descending order
Here is some code (it can be prettier):
class Customer < ApplicationRecord
def self.relevance_search(query)
matches = []
matches << Customer.where(first_name: query)
matches << Customer.where(last_name: query)
matches << Customer.where(email: query)
matches << Customer.where(main_phone: query)
counter = {}
matches.each do |match|
match.each do |match_result|
id = match_result.id
if counter.key? id
counter[id] += 1
else
counter[id] = 1
end
end
end
counter.map { |id, occurrences| { relevance: 100*(occurrences/4.0), customer: Customer.find(id) } }.sort_by { |r| r[:relevance] }.reverse!
end
end
Usage:
Customer.relevance_search ["John", "Doe", "111-111-1111"]
The return value is and array ordered by relevance in %
Enjoy :)

Related

Ruby on Rails - Active Record FIlter where the value of a referenced table is > 0

I am currently trying to filter out from selected data in Ruby on Rails those where the attribute "amount_available" is greater than zero. This would be no problem via #events.where(ticket_categories.amount_available > 0), but ticket_categories is an array with not a fixed length, because there can be multiple categories. How can you easily iterate through the array in the where clause and do this comparison?
I only need the events in the output where at least one associated category has the amount_available > 0.
This is my code:
#upcoming_events = #events.where("date >=?", Date.current)
#available_events = #upcoming_events.where(ticket_categories[0].amount_available > 0)
json_response(#available_events)
You can chain where conditions and you can add conditions that are based on associated models with joins:
available_events = #events
.where('date >= ?', Date.current)
.joins(:ticket_categories).where('ticket_categories.amount > 0')
.group(:id)
render json: available_events
Note: Database joins might return duplicate records (depending on your database structure and the condition) therefore the need to group the result set by id.
It is only a representation because the Events table is linked to TicketCategories via has_many. I use PostgresSQL and could now solve it with the following code:
#upcoming_events = #close_events.where("date >=?", Date.current)
available_events = []
#upcoming_events.each do |event|
event.ticket_categories.each do|category|
if category.amount_available > 0
available_events.push(event)
break;
end
end
end
render json: available_events

Rails: Query n IDs before and after some ID

I would like to query n users before and after some user.
Edit: for clarity
def local_query(id, range)
local_min = id - range*0.5
local_max = id + range*0.5
local_users = User.where(id: [local_min..local_max])
return local_users
end
Guessing that you want to query user by limiting ID(specific range of ID) i think following should work
user_id_min_range = 5
user_id_max_range = 10
User.where(id: (user_id_min_range..user_id_max_range))
Above code will return users whose ID are between 6-10
Or you can also do like following
User.where("id > ?", 5).order(id: :asc).limit(5)
Above query will select users with id greater than 5 and order them in Ascending ID and return top 5 users.
If I understand your question correctly you could use this scopes:
scope :next_users, ->(start_id, user_limit) {
order(id: :asc).where("id > ?", start_id).limit(user_limit)
}
scope :before_users, ->(start_id, user_limit) {
order(id: :desc).where("id < ?", start_id).limit(user_limit)
}
They will select n of the next or before users for a predefined user id.

Order a query based on comparison of associated records

class Man
has_many :sons
# id
end
class Son
belongs_to :man
# id, man_id, age
end
I was to retrieve men from the DB and I want them ordered based on the age of their oldest son. Here's an example.
first_man = Man.create
first_man.sons.create(age: 10)
first_man.sons.create(age: 5)
second_man = Man.create
second_man.sons.create(age: 20)
second_man.sons.create(age: 5)
third_man = Man.create
third_man.sons.create(age: 19)
third_man.sons.create(age: 8)
Man.order('[some order]').to_a
=> [second_man, third_man, first_man]
How do I get ActiveRecord to do this?
Edit
I get invalid SQL when I try to do Man.joins(:sons).order("sons.age DESC").uniq.
ActiveRecord::StatementInvalid:
PG::InvalidColumnReference: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
LINE 1: ...sons"."man_id" = "men"."id" ORDER BY sons...
^
: SELECT DISTINCT "men".* FROM "men" INNER JOIN "sons" ON "sons"."man_id" = "men"."id" ORDER BY sons.age DESC LIMIT 15
Not tried it but i guess this should work
Man.includes(:sons).order("sons.age").to_a
Try this
Man.joins(:sons).order('sons.age DESC').uniq
Updated
Maybe this will help but is's ugly
Son.order('age DESC').map(&:man).uniq

ActiveRecord Hash form to say update_all("prio = prio + 200")

I want to change attributes of a lot of records via update_all.
The user should be able to change the prio relatively (record 1 has prio 0, record 2 prio 200, the user can choose to give them +200 so they end up with prio 200 and 400 respectively).
and change other columns at same time.
The preferred way in rails is
Model.update_all(name: name)
but for prio I have only the string form that I am aware of right now -
Model.update_all("prio = prio + #{change_of_prio}")
How can I change the latter into the Hash-Form of rails? Or, how can I do both in one update_all statement (without loosing the advantage of rails doing the necessary escaping when calling it with a Hash)?
You have to use array notation. This would work:
Model.update_all(["prio = prio + :change_of_prio, name = :name", {change_of_prio: 200, name: "foo"}])
# UPDATE `models` SET prio = prio + 200, name = 'foo'
:change_of_prio and :name are named placeholders referring to corresponding values in the hash. Rails handles the escaping for you.
The #update_all could be applied on enumerator to set or update all the record wot the specified values. The examples:
# Update all customers with the given attributes
Customer.update_all wants_email: true
# Update all books with 'Rails' in their title
Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
# Update all books that match conditions, but limit it to 5 ordered by date
Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')
So in your case the call will update all the records with the same value of the name variable, which is passed to the function as an argument.
Model.update_all(name: name) # all columnt named 'name' will have value of variable `name`
As for prio:
prio = ...
change_of_prio = ...
Model.update_all(prio: "#{prio} #{change_of_prio}") # all columnt named 'prio' will have value of variable concat `prio` and `change_of_prio`

ActiveRecord - Update single column data of different records in single query

Is it possible to update following data in one single query?
ID Seq
2 2
1 4
4 1
3 3
It would be best if it could be done with rails active records, else with sql query would be perfectly fine too.
Reason for doing so is I have scenario where I need to update a group of ids frequently based on ajax request. I believe having it done in single query is much more optimized.
I guess you're asking whether you can complete set of updates in a single transaction.
In rails, you can use ActiveRecord::Base.transaction for this purpose. For example, for a Model, you can:
Model.transaction do
Model.update(2, {:seq => 2} )
Model.update(1, {:seq => 4} )
Model.update(4, {:seq => 1} )
Model.update(3, {:seq => 3} )
end
We can not directly call multiple updates in a single query using Active Record. As you said we can run it as a sql query in rails.
sql = ActiveRecord::Base.connection();
sql.execute("INSERT into products (id,name,description) VALUES (id-1,'name4','desc4'),(id-2,'name5','desc5'),(id-3,'name6','desc7') ON DUPLICATE KEY UPDATE name=VALUES(name),description=VALUES(description);")
Found the solution I want from here and here.
This is how I do it with active records.
_params = ["12", "9", "13", "14", "15", "16"]
db = ActiveRecord::Base.connection();
query = "UPDATE #{self.table_name} SET display_order = CASE id "
_params.each_with_index do |d,i|
query += sanitize_sql(["WHEN ? THEN ? ",d,i])
end
query += "END "
query += "WHERE id in ('#{_params.join("','")}') "