質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
SQLite

SQLiteはリレーショナルデータベース管理システムの1つで、サーバーではなくライブラリとして使用されている。

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

検索

検索は、あるデータの集まりの中から 目的のデータを見つけ出すことです。

Q&A

1回答

1161閲覧

[Rails] N+1問題と子要素条件検索の両立ができない

Zray

総合スコア0

SQLite

SQLiteはリレーショナルデータベース管理システムの1つで、サーバーではなくライブラリとして使用されている。

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

検索

検索は、あるデータの集まりの中から 目的のデータを見つけ出すことです。

0グッド

0クリップ

投稿2021/07/19 03:29

編集2021/07/20 23:16

前提・実現したいこと

Ruby On Rails 5で社内のみで使う業務管理システムを作成中。
プロジェクトごとにタグが複数ついていて、タグと案件番号と顧客会社名によるプロジェクト検索機能を作りたい。
######テーブルは下記(質問に関係ない情報は省略)
1←多(矢印の向きがbelongs_to)
projects → clients
projects ← project_tags → tags(Active Hash)

[projects]
id(PK) integer
code string (「案件番号」と呼ぶ、「302-5」みたいな数字とハイフンの文字列)
client_id(FK) integer

[clients]
id(PK) integer
company_name integer (株式会社○○など)

project_tags
id(PK) integer
project_id(FK) integer
tag_id(FK) integer

[tags](Active Hash)11個ある
id(PK)
name

######検索機能の詳細
案件番号は前方一致、顧客会社名は部分一致、タグは1,2,3を選択した場合タグIDとして1,2,3のいずれかを含む案件がヒット

発生している問題の例

ベン図
赤は検索フォームで選択されたtag_id
Project:1(青)とProject: 2(緑)のみヒット

eager_loadでJOINする

projects.idproject_tags.tag_id
11
12
13
14
15
22
23
24
34
35
36


tag_id: [1,2,3]で絞り込み

projects.idproject_tags.tag_id
11
12
13
22
23

→ Project:1は4番・5番、Project: 2は4番のタグが消えているためビューで全てタグが表示されない

該当のソースコード

※注意:なぜindexではなくallなのか
メモリの問題もあるので文字通りallを取り出すわけではない。
しかし resourcesのindexを使うと
検索前のURLが/projects
検索後のURLは/projects/search....(←クエリ文字列)
検索フォームの自動補完機能をjQueryで実装するときautocompleteアクションへの相対パスを書くわけだが、検索前と検索後でURLの階層が異なるとそれが使えなくなるため、
検索前のURLを/projects/allとして階層をそろえている。

######views/projects/all.html.erb (検索フォーム部分)

Ruby

1<%= form_with url: search_projects_path, method: :get, min: 1, local: true do |f| %> 2 <%= f.label :code, '案件番号' %> 3 <%= f.search_field :code, value: @code, id: 'code_autocomplete' %> 4 5 <%= f.label :client, '顧客' %> 6 <%= f.search_field :client, value: @company_name, id: 'client_autocomplete' %> 7 8 <%= f.collection_check_boxes(:tag_ids, Tag.all, :id, :name, include_hidden: false) do |tag| %> 9 <%= tag.label do %> 10 <%= tag.check_box %> 11 <%= tag.text %> 12 <% end %> 13 <% end %> 14 <%= f.submit '検索' %> 15<% end %>
models/project.rb

Ruby

1 def self.search(code, client_company_name, tag_ids) 2 if tag_ids 3 Project.eager_load(:project_tags, :client) 4 .where('projects.code LIKE ? AND clients.company_name LIKE ?',"#{code}%", "%#{client_company_name}%") 5 .where(project_tags: {tag_id: tag_ids}) 6 else # タグ選択がない場合は検索条件に入れない 7 Project.eager_load(:project_tags, :client) 8 .where('projects.code LIKE ? AND clients.company_name LIKE ?', "#{code}%", "%#{client_company_name}%") 9 end 10 end

######controllers/projects_controller.rb

Ruby

1class ProjectsController < ApplicationController 2 def all 3 @projects = Project.limit(100).preload(:client, :project_tags) 4 end 5 6 def search 7 @code = params[:code] 8 @company_name = params[:company_name] 9 @projects = Project.limit(100).eager_load(:client, :project_tags).search(@code, @company_name, params[:tag_ids]) 10 render action: :all 11 end 12end

######views/projects/all.html.erb(検索結果表示部分)

erb

1<% @projects&.each do |pj| %> 2 <% pj.project_tags&.each do |pt| %> 3 <%= pt.tag.name %> 4 <% end %> 5<% end %>

試したこと

######・案件を絞ったあとでタグは別々にロードする(N+1問題が残る)
一応ヒットした案件のタグはすべて表示される

erb

1<% @projects&.each do |pj| %> 2 <% ProjectTag.where(project_id: pj.id)&.each do |pt| %> 3 <%= pt.tag.name %> 4 <% end %> 5<% end %>

補足情報(FW/ツールのバージョンなど)

FW: AWSのcloud9
rails: 5.2.3
DB: sqlite3
OS: Windows10

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

クエリ一発で解決するのは複雑そうなので、

  • 該当するProjectのIDsを返すクエリ
  • 上記クエリ結果から、該当するProjectの全タグをeager_loadするクエリ

の2回に分けるのはどうでしょうか?

2回目のクエリについては、サブセット条件を使えばINで効率よく検索してくれそうです。

投稿2021/07/22 17:19

takahashim

総合スコア1877

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zray

2021/07/23 09:51

なるほど!!回答ありがとうございます!!「条件に合致するIDsを作ってから」という発想にとても納得しました!そうなると顧客条件に合致するClientもIDsを配列に格納してしまえば完全にProjectテーブルのカラムでの絞り込みにできるのでeager_loadではなくpreloadで済みますね。美しい!! project_ids = ProjectTag.where(tag_id: params[:tag_ids]).group(:project_id)         .select(:project_id) client_ids = Client.where("company_name LIKE ?", "%#{@company_name}%").select(:id) @projects = Project.where("code LIKE ?", "#{params[:code]}%")             .where(id: project_ids, client_id: client_ids)             .preload(:project_tags, :client)
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問