【问题标题】:加入同一张表 - Laravel Query Builder 或 Eloquent 的原始 SQL?
【发布时间】:2022-01-20 01:58:27
【问题描述】:

我有下面的原始 sql 查询,它按预期工作

SELECT T1.category, T1.group, T1.series, T1.name, T2.cover
FROM (
    SELECT category, MAX(COALESCE(image)) as cover
    FROM stones
    GROUP BY `category`
    HAVING COUNT(DISTINCT category) = 1
    ) T2
    INNER JOIN stones T1 on T2.category = T1.category;

在尝试将其转换为 Laravel Query Builder 语法时,我尝试了以下方法

DB::table('stones', 't1')
    ->select('t1.category', 't1.group', 't1.series', 't1.name', 't2.cover')
    ->from(DB::raw('(SELECT category, MAX(COALESCE(image)) as cover) FROM stones GROUP BY `category` HAVING COUNT(DISTINCT category)=1) t2'))
    ->join('stones as t2', 't2.category', '=', 't1.category')
    ->get();

报错

Illuminate\Database\QueryException with message 'SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'FROM stones GROUP BY `category` HAVING COUNT(DISTINCT category)=1) t2 inner join' at line 1 (SQL: select `t1`.`category`, `t1`.`group`, `t1`.`series`, `t1`.`name`, `t2`.`cover` from (SELECT category, MAX(COALESCE(image)) as cover) FROM stones GROUP BY `category` HAVING COUNT(DISTINCT category)=1) t2 inner join `stones` as `t2` on `t2`.`category` = `t1`.`category`)'

当我尝试转储 toSql() 生成的 sql 时,我看不出与原始 sql 有任何区别,但是我无法发现错误。

# dump from toSql()

"select `t1`.`category`, `t1`.`group`, `t1`.`series`, `t1`.`name`, `t2`.`cover` from (SELECT category, MAX(COALESCE(image)) as cover) FROM stones GROUP BY `category` HAVING COUNT(DISTINCT category)=1) t2 inner join `stones` as `t2` on `t2`.`category` = `t1`.`category`"

【问题讨论】:

  • as cover) FROM 中的) 是否必要?因为我没有在您的第一个原始查询中看到它,但它在您的数据库查询构建器中
  • 我认为您不应该将此查询与 eloquent 一起使用,只需使用 DB::statement("query"); 直接运行它即可
  • @Faesal 你能否详细说明如何使用DB::statement('raw sql query here'),因为它只返回一个bool 如何让语句执行?
  • 您的原始 sql 查询是否成功获得结果?
  • 是的,它得到了想要的结果。正如@ChanYungKeat 和@adevel 所指出的,一旦我在cover 之后删除了额外的) 并在join() 中将stones as t2 替换为stones as t1 - 查询生成器语句也可以工作 - 已发布工作查询生成器语句以下供参考

标签: mysql laravel eloquent laravel-query-builder


【解决方案1】:

这可以通过直接选择来完成,如下所示:

$results = DB::select('SELECT T1.category, T1.group, T1.series, T1.name, T2.cover
FROM (
    SELECT category, MAX(COALESCE(image)) as cover
    FROM stones
    GROUP BY `category`
    HAVING COUNT(DISTINCT category) = 1
    ) T2
    INNER JOIN stones T1 on T2.category = T1.category');
dd($results);

这会容易得多。

更多信息请查看this

您可以使用以下 facade 方法来代替构建 eloquent 查询:

  • DB::select 用于选择复杂查询
  • DB::statement 用于 CRUD 查询
  • DB::insert 直接用于完整插入查询
  • DB::update 直接查询完整更新
  • DB::delete 直接用于完全删除查询

注意:如果需要,上述所有方法都应该接收参数绑定

如果您不想绑定参数,请使用DB::unprepared('query')

【讨论】:

  • 希望这可以帮助@donkarnash
  • 是的,您发现这两个语句都返回相同的结果。对于我的用例,我需要在应用 groupBy 之前映射结果并将 stdClass 对象转换为数组。
  • 感谢您的指点。已对您的回答表示赞赏,以感谢您的努力。
【解决方案2】:

您可能不需要其他答案,但出于个人喜好,我更愿意尽可能避免使用DB::raw()。考虑到这一点,我会为子查询使用fromSub() 方法:

Stone::query()
    ->select('t1.category', 't1.group', 't1.series', 't1.name', 't2.cover')
    ->fromSub(function ($query) {
        $query->from('stones')
            ->select('category')
            ->selectRaw('MAX(COALESCE(image)) AS cover')
            ->groupBy('category')
            ->havingRaw('COUNT(DISTINCT category) = 1');
    }, 't2')
    ->join('stones AS t1', 't2.category', '=', 't1.category')
    ->get()
    ->groupBy(['category', 'group', 'series'])
    ->toArray();

【讨论】:

  • 哇,谢谢。我完全忘记了子查询。我喜欢这种方法。另外,我不承担 SQL 注入的风险。
【解决方案3】:

感谢@ChanYungKeat 和@adevel 指出更正“额外)”。删除它并将stones as t2stones as t1join() 中交换使其工作。以下是供参考的工作查询

DB::table('stones', 't1')
    ->select('t1.category', 't1.group', 't1.series', 't1.name', 't2.cover')
    ->from(DB::raw('(SELECT category, MAX(COALESCE(image)) as cover FROM stones GROUP BY `category` HAVING COUNT(DISTINCT category)=1) t2'))
    ->join('stones as t1', 't2.category', '=', 't1.category')
    ->get();

编辑:

我的用例需要将查询结果按类别 > 组 > 系列进行分组,并准备数据进行序列化以供前端消费。所以我需要一个额外的步骤来映射每个结果条目并将其转换为数组(因为从查询生成器返回的 stdClass 对象可能无法正确序列化)

DB::table('stones', 't1')
    ->select('t1.category', 't1.group', 't1.series', 't1.name', 't2.cover')
    ->from(DB::raw('(SELECT category, MAX(COALESCE(image)) as cover FROM stones GROUP BY `category` HAVING COUNT(DISTINCT category)=1) t2'))
    ->join('stones as t1', 't2.category', '=', 't1.category')
    ->get()

    /* Here I need to map over and convert the stdClass objects 
     * returned by Query  Builder to array for proper serialization
     */
    ->map(function ($stone) {
        return ['category' => $stone->category, 'group' => $stone->group, 'series' => $stone->series, 'name' => $stone->name, 'ccover' => $stone->cover];
    })

    ->groupBy(['category', 'group', 'series'])
    ->toArray();

//OR using DB::select() as suggested by @Faesal

return collect(DB::select("SELECT t1.category, t1.group, t1.series, t1.name, t2.cover FROM stones as t1 JOIN (SELECT category, MAX(COALESCE(image)) as cover FROM stones GROUP BY `category` HAVING COUNT(DISTINCT category)=1) t2 ON t2.category = t1.category"))

    /* Here I need to map over and convert the stdClass objects 
     * returned by Query  Builder to array for proper serialization
     */
    ->map(function ($stone) {
        return ['category' => $stone->category, 'group' => $stone->group, 'series' => $stone->series, 'name' => $stone->name, 'ccover' => $stone->cover];
    })
    ->groupBy(['category', 'group', 'series'])
    ->toArray();

我还发现它可以使用 Eloquent(而不是 Query Builder)来完成。由于 Eloquent 返回 Illuminate\Database\Eloquent\Model 的对象,因此不需要额外的步骤来映射到数组,因为序列化由 Model 类负责。

Stone::query()
    ->select('t1.category', 't1.group', 't1.series', 't1.name', 't2.cover')
    ->from(DB::raw('(SELECT category, MAX(COALESCE(image)) as cover FROM stones GROUP BY `category` HAVING COUNT(DISTINCT category)=1) t2'))
    ->join('stones as t1', 't2.category', '=', 't1.category')
    ->get()
    ->groupBy(['category', 'group', 'series'])
    ->toArray();

希望这对处于类似情况的人有所帮助。

【讨论】:

    【解决方案4】:

    如果您比较输出查询和 toSql 溢出的查询,那么似乎在查询生成器中,“cover”之后的“)”不应该存在。

    【讨论】:

      猜你喜欢
      • 2016-07-25
      • 2014-03-17
      • 2019-08-11
      • 1970-01-01
      • 2015-06-15
      • 1970-01-01
      • 2020-04-26
      • 2017-10-08
      • 2021-02-26
      相关资源
      最近更新 更多