

2つのテーブルがあります。  
**① Articles（記事）**  
ユーザが投稿した記事一覧  
**② Favorites（お気に入り）**  
ユーザがお気に入りに登録した記事一覧

① には記事とその情報が登録されています。  
② はお気に入りに登録された記事のid （① の主キー）とユーザIDを列に持ちます。

お気に入り一覧を抽出するとき、以下の2つの方法のうちどちらを選択するか迷っています。

**A案**  
②から該当ユーザが登録した記事ID を取得。  
その後、そのID を条件に①から記事一覧を取得する。

[code]

    $article_ids = Favorite::where('user_id', $user_id)->pluck('article_id');
    $articles = Article::whereIn('id', $article_ids)->get();
    
[/code]

**B案**  
ユーザIDを条件に、①と②を記事ID で結合して記事一覧を取得する。

[code]

    $articles = Favorite::leftJoin('articles', 'favorites.article_id', '=', 'articles.id')
    ->where('favorites.user_id', $user_id)->get();
    
[/code]

**気にしているのは以下です。**  
(1) このような場合、A、Bどちらの方法がオーソドックスか？  
(2) パフォーマンスに優れるのはどちらか？  
Aは内部的にSQLを2つ発行している。  
Bはコストの高い結合という処理を行っている。  
(3) 総合的にどちらの方法が優れているか。

以上、よろしくお願いいたします。


CC BY-SA 4.0
2021-12-23T05:42:36.280
85295
2021-12-23T08:30:20.643
2021-12-23T06:03:44.550
3060
50616
question
sql laravel
Laravel 8 どちらのSQLクエリのほうが良いですか？


勝手に微調整を加えます。また IDE と相性がいい・インデントが揃えやすいように `::query()` から入ります。

[code]

    // A: WHERE IN (1, 2, 3, ...) で連続的に SQL を 2 つ発行
    $article_ids = Favorite::query()
        ->where('user_id', $user_id)
        ->pluck('article_id');
    $articles = Article::query()
        ->find($article_ids);
    
    // B: JOIN を使う
    $articles = Article::query()
        ->leftJoin('favorites', 'favorites.article_id', '=', 'articles.id')
        ->where('favorites.user_id', $user_id)
        ->select('articles.*')
        ->get();
    
    // C: WHERE EXISTS (SELECT ...) を使う相関サブクエリ
    $articles = Article::query()
        ->whereHas('favorites', function (Builder $query) use ($user_id){
            $query->where('user_id' => $user_id);
        })
        ->get();
    
    // D: WHERE IN (SELECT ...) を使う非相関サブクエリ
    // https://github.com/mpyw/eloquent-has-by-non-dependent-subquery
    $articles = Article::query()
        ->hasByNonDependentSubquery('favorites', function (Builder $query) use ($user_id) {
            $query->where('user_id' => $user_id);
        })
        ->get();
    
[/code]

### A

最も素朴なコードですが，ページネーションを入れようとすると少し考えることが増えます。また `articles`
テーブルにしかない情報でページネーションは困難です。それゆえ，使用できるケースは限られます。

### B

**パフォーマンス的に最も優れる** コードですが，地雷が非常に多い使い方です。以下の点に注意してください。熟練者以外はあまり使わないほうがいい方法です。

  * 適用できるケースが限られます。`HasMany` リレーションに対して使うと， **同じIDのレコードが重複して出現する** リスクがあります。「同じユーザは1個しか同じ記事をいいねできない」という制約があり，かつ単一の `user_id` で絞り込む場合は問題ありません。また `HasOne` `BelongsTo` に対して使う場合は全く問題ありません。
  * **`->select('articles.*')`** を付与するのを忘れないように。これがないと `favorites` のフィールドが紛れ込んで悲惨なバグを生むことに繋がります。
  * `Article::query()` から始めてください。`Favorite::query()` からだと，取得結果が `Favorite` のインスタンスになってしまいます。

### C

おそらく **最もオーソドックスな** な方法です。いろいろな場合において使え，安全性の高い方法ですが， **クエリオプティマイザが賢くない MySQL
だと露骨にパフォーマンスが落ちます** 。PostgreSQL の場合は問題ありません。

### D

MySQL で C
の問題点を解消するために自作したライブラリを使ったコードです。オーソドックスという観点ではマイナスですが，使いやすさと高パフォーマンスを両立しています。

  * [mpyw/eloquent-has-by-non-dependent-subquery: Convert has() and whereHas() constraints to non-dependent subqueries.](https://github.com/mpyw/eloquent-has-by-non-dependent-subquery)


CC BY-SA 4.0
2021-12-23T07:59:39.493
85297
2021-12-23T08:30:20.643
2021-12-23T08:30:20.643
940
940
85295
answer