使用带有 N 查询的“USING”子句的雄辩连接

Posted

技术标签:

【中文标题】使用带有 N 查询的“USING”子句的雄辩连接【英文标题】:Eloquent join using "USING" clause with N query 【发布时间】:2018-06-29 02:57:54 【问题描述】:

我正在使用Slim Framework with Illuminate Database。

我想用USING 子句进行JOIN 查询。假设给定Sakila 数据库。图:

如何在 eloquent 模型中加入 USING 子句(不是 ON)?

SELECT film_id,title,first_name,last_name 
FROM film_actor 
INNER join film USING(film_id) -- notice 
INNER join actor USING(actor_id) -- notice 

我想要的是使用 EXACT 1 查询的急切加载。 API 中描述的雄辩关系的使用不符合我的期望,因为任何急切的关系都使用 N+1 查询。我想减少对数据库的 IO。

电影演员模型:

class FilmActor extends Model

    protected $table = 'film_actor';
    protected $primaryKey = ["actor_id", "film_id"];
    protected $increamenting = false;
    protected $appends = ['full_name'];
    
    // i need to make it in Eloquent model way, so it easier to manipulate
    public function getFullNameAttribute()  
    
        $fn = "";
        $fn .= isset($this->first_name) ? $this->first_name ." ": "";
        $fn .= isset($this->last_name) ? $this->last_name ." ": "";
        return $fn; 
    

    public function allJoin()
    
        // how to join with "USING" clause ?
        return self::select(["film.film_id","title","first_name","last_name"])
            ->join("film", "film_actor.film_id", '=', 'film.film_id')  
            ->join("actor", "film_actor.actor_id", '=', 'actor.actor_id');  

        //something like
        //return self::select("*")->joinUsing("film",["film_id"]);
        //or
        //return self::select("*")->join("film",function($join)
        //    $join->using("film_id");
        //);
    

所以,在控制器中我可以得到类似的数据

$data = FilmActor::allJoin()  
        ->limit(100)  
        ->get();`  

但有一个缺点,如果我需要添加额外的行为(如 whereorder)。

$data = FilmActor::allJoin()
        ->where("film.film_id","1")   
        ->orderBy("film_actor.actor_id")  
        ->limit(100)  
        ->get();`  

我需要传递表名以避免不明确的字段。 不好。所以我想进一步使用,我可以做

$kat = $request->getParam("kat","first_name");  
// ["film_id", "title", "first_name", "last_name"]  
// from combobox html  
// adding "film.film_id" to combo is not an option  
// passing table name to html ?? big NO

$search = $request->getParam("search","");
$order = $request->getParam("order","");
$data = FilmActor::allJoin()
        ->where($kat,"like","%$search%")   
        ->orderBy($order)  
        ->limit(100)  
        ->get();`  

【问题讨论】:

如果你只想要 1 个查询,不要使用 eloquent,而是使用查询生成器。 @FelippeDuarte 你可以发布一个答案吗?我找不到类似$join->using('users.id') laravel.com/docs/5.4/queries#joins laravel.com/api/5.0/Illuminate/Database/Query/JoinClause.html @bishop :感谢您的努力。好吧,我会保持这个问题的开放性,以防 iluminate 未来版本有一个实际的答案。 “任何急切的关系都使用 N+1 查询” - 什么是“N”?你在哪里读到/听到的? @PaulSpiegel : laravel.com/docs/5.4/eloquent-relationships#eager-loading 最后,它将操作减少到只有 2 个查询 【参考方案1】:

在 Eloquent 中(我认为这在 2018 年已经可用)该功能没有命名为 using 而是 with 并且应该给出如下内容:

    ForumActor::with(['film', 'actor'])->get();

当然这必须适应你的情况,你甚至可以嵌套关系:

   ForumActor::with('actor.contacts')->get();

例如。

看看:https://laravel.com/docs/8.x/eloquent-relationships#eager-loading 即使它被标记为“急切加载”(顺便说一句,这很棒),它也可以在没有急切加载的情况下工作,而且,当正确设置外键(例如使用迁移)时,它只使用一个查询,这样就可以避开 @ 987654326@.

【讨论】:

【参考方案2】:

您可以尝试在代码中查找是否可以制作USING JOIN,或者添加一些代理字典:

$kat_dict =  ["film_id" => "film.film_id", "title"=> 'title', "first_name" => 'first_name', "last_name" => 'last_name'];

$kat = $kat_dict[$request->getParam("kat","first_name")];

顺便说一句:更好的方法是使用像 Arr::get($arr, $index, $default) 这样的函数(参见代码示例)

【讨论】:

【参考方案3】:

您可以通过select 简单地调用原始查询

$query = "SELECT 
    film_id,title,first_name,last_name 
FROM 
    film_actor 
INNER join film USING(film_id)
INNER join actor USING(actor_id)
WHERE 
    film.film_id = :filmId 
ORDER BY film_actor.actor_id
LIMIT 0, 100";

$data = DB::select($query, [
    'filmId' => 1
]);
// or like this, if not using default connection
/**
$data = DB::connection('test')->select($query, [
    'filmId' => 1
]);
*/

【讨论】:

以上是关于使用带有 N 查询的“USING”子句的雄辩连接的主要内容,如果未能解决你的问题,请参考以下文章

无法在具有聚合值的 HAVING 子句中使用来自子查询表连接的单值列

在laravel中雄辩的where子句中连接两列

我雄辩的查询构建器实例返回空,而 sql 子句返回结果。会欣赏第二只眼睛

oracle 之 using 使用

from 子句中的 JPA/hibernate 子查询

具有多个带有 AND 类型逻辑连接的 where IN 子句的配置单元查询