mysql 查询:在多 table 选择中获取相关 table 的最后一行

mysql query : get the last row of related table on multi table selection

我有 2 个这样结构的表

products

plans

基本上,想法是为每个产品设置多个价格,每个产品的最后一个计划将是其当前价格,如果它被删除或过期,它将回退到以前的计划

因此,如果产品有 2 个计划,id 为 (1, 2),则计划 id = 2 将是其当前价格

我想展示他们最后计划的产品type = off

这是由 Laravel ORM Eloquent

生成的 SQL 查询
select * from `products` where exists
        (select * from `plans` where `products`.`id` = `plans`.`product_id`
                and `type` = 'off' 
                and `plans`.`deleted_at` is null) 
        and `products`.`deleted_at` is null

问题是它不检查 last/current 计划它会在所有计划中搜索...所以即使 id = 2 类型的计划没有关闭并且如果 plan.id = 1 类型已关闭我仍然会在查询中找到此产品
这是 php 代码:

$wonder_product = Product::whereHas('CurrentPlan', function ($q) {
    $q->where('type', 'off');
})->get();

您应该改用 whereDoesntHave

return Product::whereDoesntHave('plans', function ($q) {
    $q->where('type', 'off');
})->with('plans')->get();

工作示例:

products migration

Schema::create('products', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('title');
    $table->timestamps();
});

plans migration

Schema::create('plans', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->unsignedBigInteger('product_id');
    $table->foreign('product_id')->references('id')->on('products')
                                 ->onDelete('cascade');
    $table->decimal('price');
    $table->string('type');
    $table->timestamps();
});

Product Model Relationship

public function plans()
{
    return $this->hasMany(Plan::class);
}

Plan Model Relationship

public function product()
{
    return $this->belongsTo(Product::class);
}

Sample Data Seeder

$productWithOnePlanOff = Product::create([
    'title' => 'A product with one of its plans off'
]);
$productWithOnePlanOff->plans()->createMany([
    ['price' => rand(1, 50), 'type' => 'off'],
    ['price' => rand(50, 100), 'type' => 'on']
]);
$productWithNoPlanOff = Product::create([
    'title' => 'A product with none of its plans off'
]);
$productWithNoPlanOff->plans()->createMany([
    ['price' => rand(1, 50), 'type' => 'on'],
    ['price' => rand(50, 100), 'type' => 'on']
]);

查询部分和结果

WhereHas 查找具有 任何 个相关模型匹配查询条件

的模型
return Product::whereHas('plans', function ($q) {
    $q->where('type', 'off');
})->with('plans')->get();

结果

[
    {
        "id": 1,
        "title": "A product with one of its plans off",
        "created_at": "2019-09-03 16:30:30",
        "updated_at": "2019-09-03 16:30:30",
        "plans": [
            {
                "id": 1,
                "product_id": 1,
                "price": "46.00",
                "type": "off",
                "created_at": "2019-09-03 16:30:30",
                "updated_at": "2019-09-03 16:30:30"
            },
            {
                "id": 2,
                "product_id": 1,
                "price": "50.00",
                "type": "on",
                "created_at": "2019-09-03 16:30:30",
                "updated_at": "2019-09-03 16:30:30"
            }
        ]
    }
]

虽然带有 whereDoesntHave 的查询确保其相关模型的 NONE 符合查询的条件

return Product::whereDoesntHave('plans', function ($q) {
    $q->where('type', 'off');
})->with('plans')->get();

结果

[
    {
        "id": 2,
        "title": "A product with none of its plans off",
        "created_at": "2019-09-03 16:30:30",
        "updated_at": "2019-09-03 16:30:30",
        "plans": [
            {
                "id": 3,
                "product_id": 2,
                "price": "49.00",
                "type": "on",
                "created_at": "2019-09-03 16:30:30",
                "updated_at": "2019-09-03 16:30:30"
            },
            {
                "id": 4,
                "product_id": 2,
                "price": "93.00",
                "type": "on",
                "created_at": "2019-09-03 16:30:30",
                "updated_at": "2019-09-03 16:30:30"
            }
        ]
    }
]

希望对您有所帮助

尝试使用 GROUP BY 子查询:

$wonder_product = Product::whereHas('CurrentPlan', function ($q) {
    $q->where('type', 'off')
    ->whereIn('id', function ($subquery) {
        $subquery
        ->from(with(new CurrentPlan)->getTable())
        ->select(DB:raw('MAX(id)'))
        ->groupBy('product_id');
    });
})->get();

或者如果您可以使用原始子查询:

$wonder_product = Product::whereHas('CurrentPlan', function ($q) {
    $q->where('type', 'off')
      ->whereRaw('id in (select max(id) from plans group by product_id)')
})->get();

如果我没记错的话,这两种方法都应该生成如下查询:

select * from `products`
where exists (
        select * from `plans`
        where `products`.`id` = `plans`.`product_id`
          and `type` = 'off' 
          and `plans`.`deleted_at` is null
          and id in (select max(id) from plans group by product_id)
  ) 
  and `products`.`deleted_at` is null

但如果是我,我可能会像这样写一个原始查询:

$wonder_product = Product::hydrateRaw('
    select products.*
    from products
    where 'off' = (
      select plans.type
      from plans
      where plans.product_id = products.id
        and plans.deleted_at is null
      order by plans.id desc
      limit 1
    )
    and products.deleted_at is null
');