ruby each loop in order on sql order query result

匆匆过客 提交于 2019-12-13 20:19:19

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!