ActiveRecord의 order를 복수 연결한 경우의 움직임을 조사해 보았다

7111 단어 SQLActiveRecordRails5
ActiveRecord의 order 메소드를 사용한 정렬 기능으로 예상한 움직임을 하지 않았으므로 조사해 보았습니다.

추가 (6/15)
올바른 결론을 알았으므로 수정했습니다.

환경



Ruby: 2.6.3
Rails: 5.2.3

이전 준비



간단한 posts 테이블을 가정해 봅시다.
postsテーブル

id
title


1
첫글

2
두 번째 게시물

3
세 번째 게시물


컨트롤러의 인덱스 동작에서 id의 내림차순(값이 큰 순서)으로 설정합니다.

app/controllers/posts_controller.rb
  def index
    @posts= Post.order(id: :desc)
  end

이렇게하면 posts 테이블의 내용이 id의 내림차순으로 표시됩니다.


SQL도 다음과 같습니다.


의문 1



여기에 다음 요구 사항이 부여되었습니다.
- 통상은 id의 내림차순으로 표시시키지만, title의 오름차순으로 소트시키는 버튼도 붙이는 것

그래서 다음과 같이 했습니다.

app/views/posts/index.html.erb
<%= link_to 'Titleでソート', posts_path(title_sort: :true) %>

app/controllers/posts_controller.rb
  def index
    @posts = Post.order(id: :desc)
    if params[:title_sort]
      @posts = @posts.order(title: :asc)
    end
  end

앱을 시작하고 Titleでソート를 클릭했지만 제목 오름차순으로 정렬되지 않습니다! 왜? ? ?


검증&&가설



그래서 Titleでソート를 클릭했을 때 SQL을 로그에서 확인했습니다.


SQL을 보면
① 우선 id의 내림차순(DESC)으로 정렬
②이어서 title의 오름차순(ASC)으로 정렬
하고 있습니다.
언뜻 보면 문제 없을 것 같습니다만, ORDER BY구로 복수의 조건을 지정했을 경우, ①을 실행해, 같은 것이 있었을 경우에 ②로 한층 더 정렬합니다. 그러나 ①은 id이며 같은 것이 존재하지 않기 때문에 ①에서 정렬한 결과를 묶어 ②의 결과를 반영시킬 수는 없습니다.

해결 방법



그래서 이것을 해결하려면 다음과 같이 합니다.

app/controllers/posts_controller.rb
  def index
    if params[:title_sort]
      @posts = Post.order(title: :asc)
    else
      @posts = Post.order(id: :desc)
    end
  end

title_sort의 매개 변수가 존재하는 경우에만 제목으로 정렬하고, 그렇지 않으면 id로 정렬합니다.
이제 title의 오름차순으로 정렬되었습니다!


의문 2



단지, 여기서 의문이.
①의 정렬을하고있는 것은 컨트롤러의
@posts = Post.order(id: :desc)

의 장소이며, ②의 소트를 하고 있는 것은, 컨트롤러의
@posts = @posts.order(title: :asc)

입니다. 각각 독립한 코드인데, 왜 ①과 ②를 정리한 SQL로 실행하고 있는 것일까요?
(테카, 정리해 실행하지 않으면, 타이틀에서도 정렬할 수 있지?)
 

검증&&가설



SQL의 실행 타이밍에 원인이 있는 것이 아닌가 하는 것으로, 이하와 같은 것을 시도했습니다.

app/controllers/posts_controller.rb
  def index
    @posts = Post.order(id: :desc)
    if params[:title_sort]
      @posts = Post.order(title: :asc)
    end
    binding.pry #これを追記
  end

이제 실제로 제목으로 정렬을 수행했는데 콘솔이 시작된 화면은 다음과 같습니다.

어라?
order 메소드가 있는 7행째와 9행째가 끝나도, SQL이 발행되어 있지 않다!

그럼 과감히 views 파일에 이런 코드를 추가했습니다.

views/posts/index.html.erb
// 省略
  <tbody>
    <% puts "今からeachメソッドを実行します" %> # これを追記
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.id %></td>
        <td><%= post.title %></td>
        <td><%= link_to 'Show', post %></td>
        <td><%= link_to 'Edit', edit_post_path(post) %></td>
        <td><%= link_to 'Titleでソート', posts_path(title_sort: :true) %></td>
        <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
// 省略

즉, each 메소드가 실행되는 타이밍을 알 수 있도록 한 것입니다.
이것으로 타이틀로의 소트를 실행했을 때의 로그가 이것.

무려!
each 메소드가 실행되는 타이밍에, 드디어 SQL이 실행되고 있었습니다!

결론



order나 where등은 복수 연결해 정리해 실행할 수 있는 성질을 가지고 있습니다. 정리하지 않고, 코드 마다 매번 실행해 버리면 DB와의 교환이 증가해 버립니다.
그래서 Rails에서는 SQL을 컨트롤러에서는 굳이 실행하지 않고, 실제로 필요하게 된 타이밍(이번으로 말하면 each 메소드의 타이밍)으로 정리해 실행하고 있다는 것이 이번의 이굴이었습니다.

매우 공부가 되었습니다!

좋은 웹페이지 즐겨찾기