select by nearest date - ruby-on-rails

i've a performance problem with this query:
#event = Event.find_by_sql("
SELECT * FROM `IdeProNew_development`.`events`
WHERE device_id = #{session[:selected_cam_id]}
AND data_type = 'image'
AND created_at BETWEEN CONVERT('#{params[:selected_date].to_time.beginning_of_day}', DATETIME)
AND CONVERT('#{params[:selected_date].to_time.end_of_day}', DATETIME)
ORDER BY abs(CONVERT('#{params[:selected_date]}', DATETIME)- created_at) LIMIT 1
").first
i use this to select the nearest event by the "selected_date"...
it's ok but it's very slow because scan all table (it's very big) and sort by the difference between the selected date and the creation date of the record.
i try to use DATEDIFF like this:
#event = Event.find_by_sql("
SELECT * FROM `IdeProNew_development`.`events`
WHERE device_id = #{session[:selected_cam_id]}
AND data_type = 'image' AND created_at
BETWEEN CONVERT('#{params[:selected_date].to_time.beginning_of_day}', DATETIME)
AND CONVERT('#{params[:selected_date].to_time.end_of_day}', DATETIME)
ORDER BY abs(DATEDIFF(CONVERT('#{params[:selected_date]}', DATETIME), created_at)) LIMIT 1
").first`
but it's not work very well (sometimes give me a wrong result) and it's slow too.
where is my mistake? could i use some type of indexing to make this query fast?

Why don't you use active record to do this rather than an SQL query?
Something like this :
`#event = Event.where(:device_id => session[:selected_cam_id]).
where(:data_type => 'image').
where("created_at >= ? AND created_at <= ?",
params[:selected_date].to_time.beginning_of_day,
params[:selected_date].to_time.end_of_day).
order("created_at DESC").first`
I think it's more efficient.

You can Also try this
#event = Event.where(:device_id => session[:selected_cam_id])
.where(:data_type => 'image').to_a
.select{|i| i.created_at.to_time >= params[:selected_date].to_time.beginning_of_day
&& i.created_at.to_time <= params[:selected_date].to_time.end_of_day}
.sort{ |x,y| y.created_at <=> x.created_at}.first

Related

Better solution for "one, other or both" cases

I was checking some code, and something similar to the following showed up:
def between_dates(date_1, date_2)
if date_1 && date_2
conditions "created_at >= date_1 AND created_at <= date_2"
elseif date_1
conditions "created_at >= date_1"
elseif date_2
conditions "created_at <= date_2"
end
end
It looked the kind of code that could be improved, but I couldn't find a more elegant solution for such a trivial and common conditional statement.
I'm looking for a better answer for this problem when we must return a value for one, other or both.
Rails lets you build a query dynamically. Here's an example using scopes and a class method. Since scopes always return an ActiveRecord::Relation object (even if the block returns nil), they are chainable:
class Event < ApplicationRecord
scope :created_before, -> (date) { where('created_at <= ?', date) if date }
scope :created_after, -> (date) { where('created_at >= ?', date) if date }
def self.created_between(date_1, date_2)
created_after(date_1).created_before(date_2)
end
end
Example usage:
Event.created_between(nil, Date.today)
# SELECT `events`.* FROM `events` WHERE (created_at <= '2018-05-15')
Event.created_between(Date.yesterday, nil)
# SELECT `events`.* FROM `events` WHERE (created_at >= '2018-05-14')
Event.created_between(Date.yesterday, Date.today)
# SELECT `events`.* FROM `events` WHERE (created_at >= '2018-05-14') AND (created_at <= '2018-05-15')
I'd use something like this:
def between_dates(date_1, date_2)
parts = []
if date_1
parts << "created_at >= date_1"
end
if date_2
parts << "created_at <= date_2"
end
full = parts.join(' AND ')
conditions(full)
end
This can be further prettified in many ways, but you get the idea.
def between_dates(date_1, date_2)
date_conditions = []
date_conditions << 'created_at >= date_1' if date_1
date_conditions << 'created_at <= date_2' if date_2
conditions date_conditions.join(' AND ') unless date_conditions.empty?
end
I am not sure if this is more elegant, but I always do reduce everything to avoid typos:
[[date_1, '>='], [date_2, '<=']].
select(&:first).
map { |date, sign| "created_at #{sign} #{date}" }.
join(' AND ')

how to fetch records whose date_of_leaving is nil or it should be in particular range of dates in rails?

I want to write a query on employee model, where the employee date_of_leaving is nil or it should be in the particular date range.
Here is my current query:
e = Employee.where.not('date_of_leaving >= ? and date_of_leaving <= ?', Date.today - 60, Date.today).where(:date_of_leaving => nil)
but its returning #<ActiveRecord::Relation []>
The following query will return all employees which have date_of_leaving either between specified dates or is nil:
Employee.where('date_of_leaving BETWEEN ? AND ? OR date_of_leaving IS NULL', Date.today - 60, Date.today)

How to combine ActiveRecord Relations? Merge not working?

Initially when I was trying to build a histogram of all Items that have an Order start between a given set of dates based on exactly what the item was (:name_id) and the frequency of that :name_id, I was using the following code:
dates = ["May 27, 2016", "May 30, 2016"]
items = Item.joins(:order).where("orders.start >= ?", dates.first).where("orders.start <= ?", dates.last)
histogram = {}
items.pluck(:name_id).uniq.each do |name_id|
histogram[name_id] = items.where(name_id:name_id).count
end
This code worked FINE.
Now, however, I'm trying to build a histogram that's more expansive. I still want to capture frequency of :name_id over a period of time, but now I want to bound that time by Order start and end. I'm having trouble however, combining the ActiveRecord Relations that follow the queries. Specifically, if my queries are as follows:
items_a = Item.joins(:order).where("orders.start >= ?", dates.first).where("orders.start <= ?", dates.last)
items_b = Item.joins(:order).where("orders.end >= ?", dates.first).where("orders.end <= ?", dates.last)
How do I join the 2 queries so that my code below that acts on query objects still works?
items.pluck(:name_id).each do |name_id|
histogram[name_id] = items.where(name_id:name_id).count
end
What I've tried:
+, but of course that doesn't work because it turns the result into an Array where methods like pluck don't work:
(items_a + items_b).pluck(:name_id)
=> error
merge, this is what all the SO answers seem to say... but it doesn't work for me because, as the docs say, merge figures out the intersection, so my result is like this:
items_a.count
=> 100
items_b.count
=> 30
items_a.merge(items_b)
=> 15
FYI currently, I've monkey-patched this with the below, but it's not very ideal. Thanks for the help!
name_ids = (items_a.pluck(:name_id) + items_b.pluck(:name_id)).uniq
name_ids.each do |name_id|
# from each query object, return the ids of the item objects that meet the name_id criterion
item_object_ids = items_a.where(name_id:name_id).pluck(:id) + items_b.where(name_id:name_id).pluck(:id) + items_c.where(name_id:name_id).pluck(:id)
# then check the item objects for duplicates and then count up. btw I realize that with the uniq here I'm SOMEWHAT doing an intersection of the objects, but it's nowhere near as severe... the above example where merge yielded a count of 15 is not that off from the truth, when the count should be maybe -5 from the addition of the 2 queries
histogram[name_id] = item_object_ids.uniq.count
end
You can combine your two queries into one:
items = Item.joins(:order).where(
"(orders.start >= ? AND orders.start <= ?) OR (orders.end >= ? AND orders.end <= ?)",
dates.first, dates.last, dates.first, dates.last
)
This might be a little more readable:
items = Item.joins(:order).where(
"(orders.start >= :first AND orders.start <= :last) OR (orders.end >= :first AND orders.end <= :last)",
{ first: dates.first, last: dates.last }
)
Rails 5 will support an or method that might make this a little nicer:
items_a = Item.joins(:order).where(
"orders.start >= :first AND orders.start <= :last",
{ first: dates.first, last: dates.last }
).or(
"orders.end >= :first AND orders.end <= :last",
{ first: dates.first, last: dates.last }
)
Or maybe not any nicer in this case
Maybe this will be a bit cleaner:
date_range = "May 27, 2016".to_date.."May 30, 2016".to_date
items = Item.joins(:order).where('orders.start' => date_range).or('orders.end' => date_range)

Activerecord where array with less than condition

I have an array of conditions i'm passing to where(), with the conditions being added one at a time such as
conditions[:key] = values[:key]
...
search = ModelName.where(conditions)
which works fine for all those that i want to compare with '=', however I want to add a '<=' condition to the array instead of '=' such as
conditions[:key <=] = values[:key]
which of course won't work. Is there a way to make this work so it i can combine '=' clauses with '<=' clauses in the same condition array?
One way of doing it:
You could use <= in a where clause like this:
User.where('`users`.`age` <= ?', 20)
This will generate the following SQL:
SELECT `users`.* FROM `users` WHERE (`users`.`age` <= 20)
Update_1:
For multiple conditions, you could do this:
User.where('`users`.`age` <= ?', 20).where('`users`.`name` = ?', 'Rakib')
Update_2:
Here is another way for multiple conditions in where clause:
User.where('(id >= ?) AND (name= ?)', 1, 'Rakib')
You can add any amount of AND OR conditions like this in your ActiveRecord where clause. I just showed with 2 to keep it simple.
See Ruby on Rails Official Documentation for Array Conditions for more information.
Update_3:
Another slight variation of how to use Array Conditions in where clause:
conditions_array = ["(id >= ?) AND (name = ?)", 1, "Rakib"]
User.where(conditions_array)
I think, this one will fit your exact requirement.
You could use arel.
conditions = {x: [:eq, 1], y: [:gt, 2]}
model_names = ModelName.where(nil)
conditions.each do |field, options|
condition = ModelName.arel_table[field].send(*options)
model_names = model_names.where(condition)
end
model_names.to_sql --> 'SELECT * FROM model_names WHERE x = 1 and y > 2'

plus 1 to integer record for each record in DB

I need plus some value to integer column for many records in db.
I'm trying to do it in "clean" way:
Transaction.where("account_id = ? AND date > ?", t.account_id, t.date).
update_all("account_state = account + ?", account_offset)
or
Transaction.where("account_id = ? AND date > ?", t.account_id, t.date).
update_all("account_state += ?", account_offset)
i get error:
QLite3::ConstraintException: constraint failed:
UPDATE "transactions" SET account_state = (account_state + ?)
WHERE (account_id = 1 AND date > '2012-03-01') AND (-10000)
But works "dirty" way:
Transaction.where("account_id = ? AND date > ?", t.account_id, t.date).
update_all("account_state = account + #{account_offset}")
Is there any "clean" way to do this?
The second parameter of update_all is not the value of the ?, but the conditions (optional) of the SQL request.
You may try with
Transaction.where("account_id = ? AND date > ?", t.account_id, t.date).update_all(["account_state = account + ?", account_offset])

Resources