问题
I have a query in my Controller that works perfectly:
@klasses_mon = Klass.order(:start).where(day: 'MON').find_each
my result is (shown by <%= @klasses_mon.inspect %>
in my view):
#<Enumerator: #<ActiveRecord::Relation
[#<Klass id: 9, name: "Cycling", teacher: "Tomek", day: "MON", start: 510, duration: 45>,
#<Klass id: 8, name: "LBT", teacher: "Monia", day: "MON", start: 600, duration: 60>,
#<Klass id: 11, name: "HIIT", teacher: "Aga", day: "MON", start: 930, duration: 45>]>
:find_each({:start=>nil, :finish=>nil, :batch_size=>1000, :error_on_ignore=>nil})>
But I am trying to display it in each loop. For some reason it is not ordered anymore. Looks like each loop does not keep the order from my query result:
<% @klasses_mon.each do |k| %>
<p><%= k.teacher %>,
<%= k.name %>
START: <%= k.start/60 %>:<%= k.start%60 %>
<% end %>
result:
Monia, LBT START: 10:0
Tomek, Cycling START: 8:30
Aga, HIIT START: 15:30
How should I do that?
回答1:
From the fine manual:
find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
[...]
NOTE: It's not possible to set the order. That is automatically set to ascending on the primary key (“id ASC”) to make the batch ordering work. This also means that this method only works when the primary key is orderable (e.g. an integer or string).
So find_each
is explicitly documented to ignore any ordering that you try to use.
find_each
doesn't use LIMIT and OFFSET to move the batch window through the result set as that tends to be very expensive as the OFFSET increases, instead it orders by the primary key and includes a id > last_one
condition in the WHERE clause to set the start of the batch and a LIMIT clause to set the batch size. Ordering by the PK and querying on the PK are both generally inexpensive as is a LIMIT clause.
find_each
is the wrong tool for this job, find_each
is for batch work but you're just displaying a short list of records so you want a simple:
@klasses_mon = Klass.order(:start).where(day: 'MON')
回答2:
The method #find_each
ignores any scoped order
and forces a sort by the primary key (usually id
). This is stated in the documentation and is because #find_each
needs to make sure that it doesn't repeat any records during iteration.
You can see this in your console if you try:
> @klasses_mon = Klass.order(:start).where(day: 'MON').find_each
> @klasses_mon.map(&:start) # force the relation to execute and return rows.
Scoped order is ignored, it's forced to be batch order.
Klass Load (0.ms) SELECT "klasses".* FROM "klasses" WHERE "klasses"."day" = 'MON' ORDER BY "klasses"."id"
=> [600, 510, 930]
If you're not expecting to run through thousands of rows, you can drop the find_each
:
@klasses_mon = Klass.where(day: "MON").order(:start)
来源:https://stackoverflow.com/questions/48725199/ruby-each-loop-in-order-on-sql-order-query-result