【问题标题】:How to use a subquery in a join clause in Laravel's query builder?如何在 Laravel 查询生成器的连接子句中使用子查询?
【发布时间】:2020-07-24 14:10:09
【问题描述】:

我想在 Eloquent 中构建以下查询:

SELECT products.*
FROM products
LEFT JOIN product_translations ON product_translations.id = (
    SELECT id
    FROM product_translations
    WHERE product_id = products.id
      AND title IS NOT NULL
      AND language IN (?, ?)
    ORDER BY FIELD(language, ?, ?)
    LIMIT 1
)
WHERE is_published = ?
ORDER BY product_translations.title ASC;

这是我的尝试:

$languages = ['de', 'en'];
$placeholders = collect($languages)->map(fn() => '?')->join(', ');

$subquery = ProductTranslation::query()
    ->select('id')
    ->whereRaw('product_id = products.id')
    ->whereNotNull('title')
    ->whereIn('language', $languages)
    ->orderByRaw("FIELD(language, $placeholders)", $languages)
    ->limit(1);

$query = Product::query()
    ->addSelect('products.*')
    ->leftJoin('product_translations', 'product_translations.id', '=', DB::raw("({$subquery->toSql()})"))
    ->mergeBindings($subquery->toBase())
    ->where('is_published', false)
    ->orderBy('product_translations.title');

$query->get();

但是这样绑定顺序会不正确。我期待这个:'de''en''de''en'1。但实际产生的查询如下:

SELECT products.*
FROM products
LEFT JOIN product_translations ON product_translations.id = (
    SELECT id
    FROM product_translations
    WHERE product_id = products.id
      AND title IS NOT NULL
      AND language IN ('de', 'en')
    ORDER BY FIELD(language, 1, 'de')
    LIMIT 1
)
WHERE is_published = 'en'
ORDER BY product_translations.title ASC;

我该如何做到这一点?我没有看到在连接子句中使用子查询的方法。

更新:
这是带有测试记录的迁移:

Schema::create('products', function (Blueprint $table) {
    $table->id();
    $table->boolean('is_published');
});
Schema::create('product_translations', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('product_id');
    $table->string('language', 2);
    $table->string('title');
});

DB::table('products')->insert(['is_published' => 1]);
DB::table('product_translations')->insert([
    ['product_id' => 1, 'language' => 'en', 'title' => 'Test EN'],
    ['product_id' => 1, 'language' => 'de', 'title' => 'Test DE'],
    ['product_id' => 1, 'language' => 'es', 'title' => 'Test ES'],
]);

【问题讨论】:

  • 尝试使用leftJoinSublaravel.com/docs/7.x/queries#joins在 Advanced Joins 下的 subquery joins 下检查
  • 谢谢,但这看起来不对。您可以只使用子查询而不是表来连接。但我想加入一个表并在“ON”子句中使用子查询。
  • 我想知道您是否可以共享迁移和数据转储或 sql 转储。我想试一试..
  • 当然,我都添加了。谢谢!
  • 哦,我希望我早点注意到这一点,但实际上您需要使用addBindings 而不是合并绑定。我会发布答案。

标签: mysql laravel


【解决方案1】:

你需要addBinding,而不是mergeBindings

$query = Product::query()
    ->addSelect('products.*')
    ->leftJoin('product_translations', 'product_translations.id', '=', DB::raw("({$subquery->toSql()})"))
    ->addBinding($subquery->getBindings(), 'join') //use add bindings.
    ->where('is_published', false)
    ->orderBy('product_translations.title');

mergeBingings 按类型构造整个查询的绑定。如,顺序,在哪里,拥有......等等。需要addBinding 来合并各个连接级别上的绑定。


我已经用您提供的数据对此进行了测试,这里是使用addBinding 时的绑定数组。

"bindings" => array:5 [▼
      0 => "de"
      1 => "en"
      2 => "de"
      3 => "en"
      4 => false
    ]

【讨论】:

  • 非常感谢!如果 on 子句中的子查询在 Laravel 中实现为原生函数,那就太好了。
【解决方案2】:

为了记录,这段代码对我有用:

->join(
    DB::raw($subqueryString), 
    function ($join) { 
        $join->on('table1.id', '=', 'table2.id'); 
    }
)

例子:

$results = Translation::selectRaw('t1.*')
            ->from('translations AS t1')
            ->join(DB::raw("
                (SELECT MAX(id) AS id, release_id
                FROM translations
                WHERE release_id = 3
                "), function ($join) {
                $join->on('t1.id', '=', 't2.id');
            })
            ->orderBy('t1.release_id', 'DESC')
            ->get();

【讨论】:

    猜你喜欢
    • 2020-03-12
    • 2019-10-02
    • 2017-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-03
    • 2013-12-09
    相关资源
    最近更新 更多