I've got a method like this:
def transfers(material, capacity, category, created_at)
transfers_range = Transfer.first.created_at..Transfer.last.created_at if created_at.nil?
Transfer.where(
sender_dispensary_id: id,
status: %i[dispatched released returned],
capacity: capacity,
material: material,
created_at: created_at.nil? ? transfers_range : Time.given_month_range(created_at)
).or(
Transfer.where(
receiver_dispensary_id: id,
status: %i[dispatched released returned],
capacity: capacity,
material_id: material,
created_at: created_at.nil? ? transfers_range : Time.given_month_range(created_at)
)
)
end
It's working, but is there a way to avoid query for transfer_range? I mean... If created_at == nil, then this function should skip created_at column, like it was not included in query
When created_at is nil then transfers_range basically covers all transfers and therefore the condition is pointless and I simple would query by created_at in that case.
def transfers(material, capacity, category, created_at)
transfers = Transfer
.where(status: %i[dispatched released returned], capacity: capacity, material_id: material)
.where('sender_dispensary_id = :id OR receiver_dispensary_id = :id', id: id)
transfer = transfer.where(created_at: Time.given_month_range(created_at)) if created_at
transfer
end
Yes, the transfers_range query can be avoided by breaking your query into multiple lines and adding a condition on created_at presence separately. You can also combine both the OR queries into a single query like this:
def transfers(material, capacity, category, created_at)
transfer_data = Transfer.where('sender_dispensary_id = ? OR receiver_dispensary_id = ?', id).where(status: %i[dispatched released returned], capacity: capacity, material_id: material)
transfer_data = transfer_data.where(created_at: Time.given_month_range(created_at)) if created_at.present?
transfer_data
end
You can consolidate the created_at query logic so it's in one place and you don't have to repeat it.
created_at = if created_at.nil?
Transfer.first.created_at..Transfer.last.created_at
else
Time.given_month_range(created_at)
end
You also don't need to repeat the whole where condition twice. You want the equivalent of...
where status in ('dispatched', 'released', 'returned')
and capacity = ?
and material = ?
and created_at = ?
and (
sender_dispensary_id = ?
or
receiver_dispensary_id = ?
)
You do that like so:
Transfer
.where(
status: %i[dispatched released returned],
capacity: capacity,
material: material,
created_at: created_at
)
.where(
Transfer
.where(receiver_dispensary_id: id)
.or(Transfer.where(sender_dispensary_id: id))
)
)
You can make this even more concise, and hide the details, by putting the logic into a scopes.
class Transfer < Application
scope :by_dispensary_id ->(id) {
where(receiver_dispensary_id: id)
.or(where(sender_dispensary_id: id))
}
scope :by_created_month ->(time) {
if time.nil?
where(created_at: first.created_at..last.created_at)
else
where(created_at: Time.given_month_range(time))
end
}
end
And now the query is much simpler.
Transfer
.by_dispensary_id(id)
.by_created_month(created_at)
.where(
status: %i[dispatched released returned],
capacity: capacity,
material: material
)
)
Related
I have an app where a User creates a Transaction to purchase an Item from a different User. I am suddenly having difficulty with the find_by in one method on Item. I want to find the very first Transaction involving the Item on which it is called, and I want to further limit that result by searching against a number of invalid states.
class Item < ApplicationRecord
def first_find
Transaction.find_by("item_id = ? AND recipient_id = ? AND state != ? OR state != ? OR state != ?", self.id, author.id, :ignored, :declined, :unapproved)
end
end
What this does, no matter what, is return the very first Transaction in my db. This is not expected behavior. So in console if I go like t = Transaction.last to cache Transaction id #5 (which has an item_id of 7 and a recipient_id of 4), and then call t.item.first_find, I would presumably get Transaction #5. The SQL output for this is query is Transaction Load (1.1ms) SELECT "transactions".* FROM "transactions" WHERE (item_id = 7 AND recipient_id = 4 AND state != 'ignored' OR state != 'declined' OR state != 'unapproved') LIMIT $1 [["LIMIT", 1]].
Which is great! That's what I want from the output. But to my confusion, it returns this:
#<Transaction id: 2, sender_id: 1, recipient_id: 2, item_id: 9 ..... >
Does anyone have any idea why? Thanks!
Edit 1
So I think I've solved it? I've had this problem before where putting too many search params into the where clause messes it up for some reason.
So while this does not work
Transaction.find_by("item_id = ? AND recipient_id = ? AND state != ? OR state != ? OR state != ?", self.id, author.id, :ignored, :declined, :unapproved)
This does
Transaction.where("item_id = ? AND recipient_id = ?", self.id, author.id).where("state != ? OR state != ? OR state != ?", :ignored, :declined, :unapproved).first
I'm not entirely sure why, though. Does anyone know?
Edit 2
The AND operators should be separate from the OR operators.
answering why.
that's how SQL operator precedence works. more explanation is here. so when you break it to another "where" clause that builds a new relation, which is the result of filtering the current relation according to the conditions in the arguments. the source code is here.
let me show other solutions.
1.
Transaction.where(item_id: self.id, recipient_id: author.id).where.not(state: [:ignored, :declined, :unapproved]).first
2.
recipient_transactions = Transaction.where(item_id: self.id, recipient_id: author.id)
active_transactions = Transaction.where.not(state: [:ignored, :declined, :unapproved])
result = recipient_transactions.merge(active_transactions).first # this buils a single query
I think you should use where clause instead of using find_by,
class Item < ApplicationRecord
def first_find
Transaction.where("item_id = ? AND recipient_id = ? AND state != ? OR state != ? OR state != ?", self.id, author.id, :ignored, :declined, :unapproved)
end
end
this will return ActiveRecord::Relation(record collections) instead of just one record if you using find statement
I have written so code to query lists of users who go jogging at a certain time and in certain locations, but I am getting the following error: ActiveRecord::StatementInvalid: SQLite3::SQLException: near "'jogging'"
Does this mean I cannot write a string into that variable? Are there any solutions to this?
users = User.where('ids in (:user_ids)', user_ids:
Activity.where('title :title AND created_at >= :created_at AND location_id: location_id',
{title: 'jogging', created_at: Time.zone.now.beginning_of_day, location_id:
Location.where('id in id', id: user.activities.where.not(location_id: nil)
.order('created_at DESC').first)}))
You can simplified your query this way
location_id = Location.where(
id: user.activities
.where.not(location_id: nil)
.order('created_at DESC').first
)
user_ids = Activity.where("title = ? AND created_at > ? AND location_id = ?",
"jogging", Time.zone.now.beginning_of_day, location_id)
users = User.where(id: user_ids)
But If you want to keep query one liner. You can use this
User.where('id IN(:user_ids)',
user_ids: Activity.where('title = :title AND created_at >= :created_at AND location_id = :location_id', title: 'jogging', created_at: Time.zone.now.beginning_of_day,
location_id: Location.where('id IN(:id)', id: user.activities.where.not(location_id: nil)
.order('created_at DESC').first).ids)
)
Note: I am assuming that you have user object to use above one liner query
Hope this will help you.
I think you forgot to add an = in the query:
Your query:
Activity.where('title :title ...
What you want:
Activity.where('title = :title ...
And if you don't need an operator like > or <, you can also use:
Activity.where(title: title)
If you then need to chain it, it's pretty simple:
Activity.where(title: title).where('foo < ?', 100)
Class Order < ActiveRecord::Base
scope :ordered, lambda { |user| where("orders.user_id = ?", user.id) }
scope :delivered, lambda { |user| where("orders.user_id = ? and orders.delivered = ?", user.id, true) }
def self.delivered_percentage (user)
((self.ordered(user).count/self.delivered(user).count) * 100)
end
end
I have an Order model, and I need to find out
The count of ordered items
The count of delivered items
The percentage of delivered items.
The ordered and delivered methods return an array. How do I have them return a count, and compute the percentage of delivered items.
In my controller I would like to
Order(current_user).delivered_percentage
Thanks!
ordered and delivered return ActiveRecord::Relation. If you call count on it, result of SQL count query will be returned. So your approach basically should work - except you have to convert your counts into floats to make it work:
def self.delivered_percentage(user)
(ordered(user).count.to_f / delivered(user).count.to_f) * 100
end
and you call it with:
Order.delivered_percentage(current_user)
Order.group('delivered').count
That will return a hash: {true => 10, false => 33} with a single SQL query.
So you should be able to do this:
def self.delivered_percent(user)
delivered_counts ||= Order.group('delivered').where(user: user).count
delivered_counts[true]/delivered_counts.values.sum.to_f
end
I have an Array of Contact objects...
[#<Contact id: 371, full_name: "Don Hofton", birthday: "2013-11-07">,...]
And I need to order them by birthdays nearest to the current time AND remove objects from the array that have birthdays greater than 4 months away. Here is what I've got so far, but it's not working....
#contacts_with_birthday_data = Contact.where(:user_id => current_user.id).where("birthday IS NOT NULL")
#current_time = Time.now
#contacts_with_birthday_data.each do |c|
c.birthday = c.birthday[0..4]
c.birthday = Date.parse(c.birthday)
end
#contacts_with_birthday_data = #contacts_with_birthday_data.sort! { |a,b| b[:birthday] <=> a[:birthday] }
#contacts_with_birthday_data = #contacts_with_birthday_data.sort! { |a| a.birthday < DateTime.now }
I think you can do this all with one query:
Contact \
.where(:user_id => current_user.id)
.where("birthday > ?", 4.months.ago)
.order("birthday desc")
If 4.months.ago is used in a scope, make sure to wrap it in a lambda or Proc, or it will be calculated when the class is loaded and not on subsequent calls. This has bit me more than once!
Alternatively, in a non-Rails world, you could use the reject and sort_by methods on Enumerable:
contacts = [#your array of contacts]
contacts.reject { |c| c.birthday < 4.months.ago }.sort_by(&:birthday).reverse
If you haven't seen the syntax used in sort_by, that's actually equivalent to sort_by { |c| c.birthday }. That syntax tells Ruby to convert the birthday method to a Proc object, then call the Proc against each instance in your array.
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