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

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

ただいまの
回答率

91.36%

  • PHP

    15137questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

  • Laravel 5

    924questions

    Laravel 5は、PHPフレームワークLaravelの最新バージョンで、2014年11月に発表予定です。ディレクトリ構造がが現行版より大幅に変更されるほか、メソッドインジェクションやFormRequestの利用が可能になります。

  • Laravel

    429questions

    LaravelとはTaylor Otwellによって開発された、オープンソースなPHPフレームワークです。Laravelはシンプルで表現的なシンタックスを持ち合わせており、ウェブアプリケーション開発の手助けをしてくれます。

モデルに実装したメソッドを利用してorderByしたい

解決済

回答 2

投稿 2017/11/20 17:30 ・編集 2017/11/20 18:07

  • 評価
  • クリップ 2
  • VIEW 76

sakura_hana

Unity総合1位

Laravel 5.4 を使用したWebアプリを作成中です。
基本的な仕組みは出来たのですが、並べ替えについて質問です。

class Item extends Model
{
    public function myPrice () {
        if (\Auth::User()->role == 'admin') {
            $result = $this->master_price;
        } else {
            $data = $this->prices()->where('user_id', \Auth::User()->id)->first();
            $result = (is_null($data) ? $this->master_price : $data->price);
        }
        return $result;
    }

    public function prices()
    {
        return $this->hasMany(Price::class);
    }
}

Controller内で、このモデルクラスのmyPrice()を元に「orderBy」をかけたく思います。
※追記ここから
サンプルにする為コードを単純化していたのですが、単純にしすぎていました。
実際は上記のようなリレーションを挟んでの処理になります。
「該当のリレーションデータが存在しない場合は自クラスのデータを使う」という処理が入ります。
(自クラスのデータは絶対に入っています)
後出し仕様になってしまって申し訳ありません。
※追記ここまで

$itemList = Item::where('aaa', 'bbb')->orderBy('updated_by', 'desc')->paginate(20);
とりあえず上記コードが動くことは確認しました。
が、orderBy('myPrice', 'asc')にすると(当然ながら)動きません。

Controller側でmyPriceと同じ条件分岐をすればいい話なのですが、出来れば同じ処理を2つ書きたくありません。
Item::where('aaa', 'bbb')->get()したデータを元に配列作成、それをソートする方法もあると思いますが、
そうした場合paginateが使えないのでは?と思っています。

何か良い方法があればお教え頂けると助かります。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • masaya_ohashi

    2017/11/20 18:30 編集

    疑問ですが、pricesがhasManyになっているのはどうしてでしょう?myPriceは1個しかデータがない想定で動作しているようですが、myPriceは「複数pricesレコードがあった場合、先頭1件を使う」という仕様なのでしょうか?

    キャンセル

  • masaya_ohashi

    2017/11/20 18:35

    もう一点、データベースはMySQLですか?

    キャンセル

  • sakura_hana

    2017/11/20 19:28

    1つのアイテムに対し、複数のユーザーが異なる価格を付ける為このような形としています(1ユーザーが1つのアイテムに付ける価格は1つだけです)。データベースはMySQLです。

    キャンセル

回答 2

+2

masaya_ohashiさんの回答を元に試行錯誤した結果、解決しました。
一部エラー等出ていたのでscopeメソッドの中身だけ変更して動作するようにしました。

//Itemクラス内
public function scopeOrderByMyPrice($query) {
    if (\Auth::User()->role == 'admin') {
        return $query->orderBy('master_price', 'asc');
    }
    else {
        $query
            ->selectRaw('COALESCE((SELECT price FROM prices WHERE prices.item_id = items.id and prices.user_id = ? limit 1), items.master_price) as myPrice', [\Auth::User()->id])
            ->addSelect('items.*')
            ->orderBy('myPrice', 'asc')
            ;
        return $query;
    }
}

変更した所

  • メソッド名を「scopeOfOrderByMyPrice」から「scopeOrderByMyPrice」へ(Ofが不要)
  • admin時は直接並べ替え
  • 「:」を使用するプリペアドステートメントだとエラーが出たので「?」を使うように変更
  • joinだとpricesのデータが無い場合アイテムデータも表示されない、leftJoinだとアイテムデータが重複表示される、groupByは何故かエラーが出る……ので、価格だけselectで追加取得を試みてCOALESCEで反映
  • COALESCEはraw内でしか使えないようなのでselectからselectRawへ変更

投稿 2017/11/21 23:49

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/11/22 10:42

    おー、お見事です。Ofはすいませんでした。なにを見て私はOfを追加したのか…

    キャンセル

checkベストアンサー

0

scopeを使ってはどうでしょう?本来は独自のwhere句を書くための構文ですが、$queryを受け取れるのでこういった使い方もできるかもしれません(未検証)。

class Item extends Model
{
    // roleによって使用するカラム名を切り分ける
    private function myPriceColumnName() {
        if (\Auth::User()->role == 'admin') {
            return 'master_price';
        } else {
            return 'my_price';
        }
    }

    public function myPrice () {
        $column = $this->myPriceColumnName();
        return $this->$column; // カラム名を変数を使って指定
    }

    public function scopeOrderByMyPrice($query) {
        return $query->orderBy($this->myPriceColumnName(), 'asc'); // scope処理でorderByを付ける
    }
}
# 使い方
$itemList = Item::where('aaa', 'bbb')
    ->orderByMyPrice() // scopeXXXという名前のメソッドはクエリビルダでこうやって呼べる
    ->paginate(20);

 仕様変更後

正直仕様が複雑すぎて、PHP側の処理とSQL側の処理を共通化して書くとか無理です…同じ動作をするように力技でSQLを書くしかないかと思います。

全然動作確認してないので、このまま正しく動くかどうかはわかりませんが…joinとかDB::rawのサブクエリを使えばどうにかできるかと思います。

class Item extends Model
{
    public function myPrice () {
        if (\Auth::User()->role == 'admin') {
            $result = $this->master_price;
        } else {
            $data = $this->prices()->where('user_id', \Auth::User()->id)->first();
            $result = (is_null($data) ? $this->master_price : $data->price);
        }
        return $result;
    }

    public function prices()
    {
        return $this->hasMany(Price::class);
    }

    public function scopeOrderByMyPrice($query) {
        if (\Auth::User()->role == 'admin') {
            $query
                ->select(
                    'items.*', // itemsテーブルのカラム全てと
                    'items.master_price AS my_price' // master_priceをmy_priceとして追加
                    );
        }
        else {
            $query
                ->join(\DB::raw('(SELECT item_id, price FROM prices WHERE user_id = :user_id) AS prices', ['user_id' => \Auth::User()->id]), 'prices.item_id', 'items.id') // pricesからuser_idの一致するレコードを引き出し、itemsのidと紐付ける
                ->groupBy('prices.item_id') // pricesのitem_idでグルーピングすることでpricesの結合を1件に絞り込む
                ->select(
                    'items.*', // itemsテーブルのカラム全てと
                    'COALESCE(prices.price, items.master_price) AS my_price' // joinしたpricesのpriceがあればpriceを、なければmaster_priceをmy_priceとして追加
                    );
        }
        return $query->orderBy('my_price', 'asc'); // my_price順でソート
    }
}


使い方は仕様変更前と同じです。Model内の実装だけが変わります。

投稿 2017/11/20 17:39

編集 2017/11/22 10:33

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/11/20 18:09

    回答ありがとうございます。scopeは知らなかったのでとても参考になります。
    ただ、申し訳無いのですが実際には単純にカラム名を指定する訳ではない状況でして、この場合どうしたらいいのか現在こちらでも調べています。
    後出し仕様で申し訳ありませんが、ご確認頂ければ幸いです。

    キャンセル

  • 2017/11/20 18:15

    うーん、この仕様ですとどうあがいても複雑なクエリ生成をせざるを得ないかと思います。とりあえず書いてみます。

    キャンセル

  • 2017/11/21 23:53

    ご協力ありがとうございました! お手数をお掛けして申し訳ありません。お陰様で解決出来ました。
    Laravelで複雑なクエリを書いたのは初めてだったので苦労はしましたが、使い方が分かってきたように思います。
    scope、COALESCEは初めて知った要素だったので大変ありがたかったです。また何かありましたら宜しくお願い致します。

    キャンセル

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

ただいまの回答率

91.36%

関連した質問

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

  • PHP

    15137questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

  • Laravel 5

    924questions

    Laravel 5は、PHPフレームワークLaravelの最新バージョンで、2014年11月に発表予定です。ディレクトリ構造がが現行版より大幅に変更されるほか、メソッドインジェクションやFormRequestの利用が可能になります。

  • Laravel

    429questions

    LaravelとはTaylor Otwellによって開発された、オープンソースなPHPフレームワークです。Laravelはシンプルで表現的なシンタックスを持ち合わせており、ウェブアプリケーション開発の手助けをしてくれます。