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

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

ただいまの
回答率

88.93%

FuelPHPでormのhas_many機能を用いたAND条件の検索を行いたいです。

解決済

回答 1

投稿

  • 評価
  • クリップ 1
  • VIEW 2,282

bugwbr

score 12

発生している問題・エラーメッセージ

ormモデルでhas_manyの機能を使用し検索ページを作っています。
or_whereを用いた複数条件の OR検索は実装出来たのですが
複数条件全てを満たす AND検索がうまく実装出来ません。

...わかりづらい質問になってしまい申し訳ありませんが、宜しくお願いいたします。

該当のソースコード

下記のような構造のテーブルがあります

class Model_Hoge extends \Model_Orm
{
  protected static $_properties = array(
    "id",
    "contents"
  );

  protected static $_has_many = array(
    'fuga_tables' => array(
      'key_from' => 'id',
      'model_to' => 'Model_Fuga',
      'key_to' => 'hoge_id',
      'cascade_save' => false,
      'cascade_delete' => false,
    )
  );
}

class Model_Fuga extends \Model_Orm
{
  protected static $_properties = array(
    "fuga_id",
    "hoge_id",
    "type"
  );
  protected static $_primary_key = array('fuga_id','hoge_id');

}

サンプルデータ

Model_Hoge

 id   Contents 
 1              AAA
 2               BBB
 3               CCC

Model_Fuga

 fuga_id   hoge_id   type 
 1                1 search_type_A
 2                1 search_type_B
 3                2 search_type_A
 4                3 search_type_B

試したこと

// OR検索
//関連するFugaモデルのtypeがsearch_type_Aまたはsearch_type_BのHogeモデルのデータを取得する
// [成功] Model_Hogeの id => 1,2,3に該当するデータが取得できる
Model_Hoge::query()
->related("fuga")
->where("type","search_type_A")
->or_where("type","search_type_B")
->get();

// AND検索
//関連するFugaモデルのtypeがsearch_type_Aとsearch_type_Bの両方のデータを持っているHogeモデルのデータを取得する
// [失敗] Model_Hogeの id => 1に該当するデータを取得したかったが、取得件数は0件だった
Model_Hoge::query()
->related("fuga")
->where("type","search_type_A")
->where("type","search_type_B")
->get();


AND検索の失敗例のコードは検索結果が0件なのは当然なのですが、他にいい方法がわかりませんでした。

補足情報

PHP 5.6.19
DATABASE psql (9.3.11)

↑MYSQLでも構いません

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

実装したことのない機能だったので学習がてら挑戦してみました。
手をつける前に想像してたよりは一手間必要な感じでした。
構成はFuelPHP1.8 + MySQL5.6.21です。

re:試したこと

先に、試したことへの回答を。実際に発行されたクエリを確認するには
CodeProfilerが便利ですが、最後に発行されたクエリだけなら
DB::last_query()でも取得できます。まずは失敗するというand検索のほうから。

->where("type","search_type_A")
->where("type","search_type_B")

このあたりのメソッドで生成されるクエリが以下の内容です。

WHERE `type` = 'search_type_A' AND `type` = 'search_type_B'

「typeがAかつB」のものとなっています。条件の照合対象はあくまでテーブル上の1レコードなので
この条件では絶対にヒットしないということでした。続いて一見うまくいっているor検索の
ほうですがこちらも少し問題があるようでした。

WHERE `type` = 'search_type_A' OR `type` = 'search_type_B'

抽出されるfugaレコードが「typeがAもしくはBのどちらか」に限定されてしまうため、
もし実際はA、B、C、Dを持つhogeがあったとしても、A、Bだけがリレーションされた状態で
取得されてしまいます。(表示や・計算にfugaを使わないならセーフかも)

目標とするクエリ

一旦fugaテーブルから条件に適合するhoge_idを集計して、そのhoge_idを元に
hogeテーブルを検索するとよさげです。目標とするクエリはこんな感じ。

SELECT * FROM `hoge`  LEFT JOIN `fuga` ON (`hoge`.`id` = `fuga`.`hoge_id`)
WHERE `hoge`.`id` IN (
    SELECT `hoge_id` FROM `fuga`
    WHERE `type` IN ('search_type_A', 'search_type_B')
    GROUP BY `hoge_id` HAVING count(`hoge_id`) = 2
)

サブクエリ部分の説明

  1. 検索typeと一致するfugaレコードのhoge_idをすべて抽出
  2. hoge_idでグループ分け
  3. count(hoge_id) = 検索type数なら一致とみなす

FuelPHPで組み立てる

サブクエリ部分はDBクラスのクエリビルダで組み立てて、
メインはORMモデルに任せるのが楽そうです。

$input = ['search_type_A', 'search_type_B'];
$count = count($input);

//compileメソッドでサブクエリを文字列として取得
//or検索ならhaving句はいらない
$sub_query = DB::select('hoge_id')
    ->from('fugas')
    ->where('type', 'in', $input)
    ->group_by('hoge_id')
    ->having(DB::expr('count(hoge_id) = ' . $count))
    ->compile();

//DB::exprメソッドでサブクエリをエスケープ無しで生のまま埋め込む
$data['hoges'] = Model_Hoge::query()
    ->related('fugas')
    ->where('id', 'in', DB::expr("({$sub_query})"))
    ->get();

試行環境ではテーブル名だけ変えていましたが
上記コードでリレーションを保持したまま検索が行えました。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/04/26 13:04

    回答ありがとうございました、ormでのサブクエリの方法など大変参考になりました

    キャンセル

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

  • ただいまの回答率 88.93%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る