【问题标题】:Laravel - Integrate big raw query in relationship eagerloadLaravel - 在关系 eagerload 中集成大型原始查询
【发布时间】:2019-09-12 13:58:49
【问题描述】:

我已经使用 laravel 大约 4 年了,但现在我面临着我最大的挑战。我正在重构和扩展一个旧的PHP 应用程序,用于映射我工作的办公室。我需要以某种方式将这个巨大的SQL 查询集成到 Laravel 的 QueryBuilder 中,以提供急切加载的关系。

流程是这样的

Building => hasMany: Floor => hasMany: Seat => hasMany: BookedSeat => belongsTo: User

其中BuildingFloorSeatBookedSeatEloquent 模型。

我的巨大查询从BookedSeatSeat 中选择基于许多其他条件的当前日期预订,例如预订座位的人是否在家办公、度假等(这些存储在其他一些表)并在BookedSeat 实例上设置一个名为Status 的属性,以了解Seat 是否在当天被占用

现在我正在尝试将此原始查询集成到构建 JSON 层次结构中,然后将其发送到在前端运行的 Vue.js 应用程序。

层次结构类似于:

 {
   "buildings": [
     {
        // properties
        "floors" : [
           {
              //properties
              "seats": [
                  {
                     //properties
                     "booked": [
                        {
                           "user": "some user model",
                           "Status": "some booked status"
                        }
                      ]
                  },
                  // other seats
              ] 
           }, 
           // other floors
        ]
     },
     //other buildings
   ]
}

巨大的查询返回一个对象数组,然后我可以用它们来水合BookedSeat 集合,但我不知道如何使用这个集合或直接使用巨大的查询来急切加载@987654338 @ for each Seat for each Floor for each Building 让框架为我完成繁重的工作。

我尝试的是构建如下方法:

public static function bookedSeatsForFloor(Relation $seatQuery, Relation $bookedQuery, Carbon $forDate)
    {
        $format = $forDate->format('Y-m-d H:m:i');
        $bindings = $seatQuery->getBindings();

        /** @var AvailableSeatsQuerySimple $availableSeats */
        $availableSeats = new AvailableSeatsQuerySimple($bindings, $format); // bindings are my floor id's and I'm feeding them to my big query in order to have control over which floors to load depending on the user's rights 

        return DB::raw($availableSeats->getRawQuery());
    }

然后这样称呼它:

Floor::where('id', $someId)->with(['seats' => static function ($seatQuery) use ($_that) {

    /**
     * Add to each seat the manager and the active booking
     */
    $seatQuery->with(['booked' => static function ($bookedQuery) use ($seatQuery, $_that) {
        return self::bookedSeatsForFloor($seatQuery, $bookedQuery, $_that->forDate);
    }, 'manager'])->orderBy('seat_cir');
}]);

但我需要使用$bookedQuery->select('something')$bookedQuery->setQuery('some query builder instance') 以某种方式修改bookedSeatsForFloor 方法中的$bookedQuery,但我不知道如何将巨大的查询转换为Builder 实例。

谢谢!

PS:由于复杂性,我最好跳过将庞大的查询重写为雄辩的语法

添加细节:

因此,根据要求,这是我的原始查询,我根据公司政策更改了一些数据库/表名称

    SET NOCOUNT ON;
DECLARE
    @the_date DATETIME;
SET @the_date = (SELECT CONVERT(DATETIME, ?, 120));

SELECT seat_identifier,
       user_id,
       FromDate,
       ToDate,
       Status
FROM (
         SELECT d.seat_identifier,
                d.user_id,
                d.FromDate,
                d.ToDate,
                CASE
                    WHEN d.Status IS NULL
                        THEN 0
                    WHEN d.Status = 2
                        THEN 2
                    WHEN d.Status != 0
                        THEN CASE
                                 WHEN -- New, OnGoing Request in Main_DB_Name
                                             ho.status = 1 -- New StatusType in Main_DB_Name
                                         OR
                                             ho.status = 4 -- Pending StatusType in Main_DB_Name
                                         OR
                                             twl.status = 1 -- New StatusType in Main_DB_Name
                                         OR
                                             twl.status = 4 -- Pending StatusType in Main_DB_Name
                                         OR
                                             li.status = 1 -- New StatusType in Main_DB_Name
                                         OR
                                             li.status = 4 -- Pending StatusType in Main_DB_Name
                                         OR
                                             ctaf.status = 1 -- New StatusType2 in Main_DB_Name
                                         OR
                                             ctaf.status = 2 -- Ongoing StatusType2 in Main_DB_Name
                                     THEN
                                     2 --> Pending seat in MyApplication


                                 WHEN -- Approved Request in Main_DB_Name
                                             ho.status = 2
                                         OR
                                             twl.status = 2
                                         OR
                                             li.status = 1
                                         OR
                                             li.status = 2
                                         OR
                                             ctaf.status = 1
                                         OR
                                             ctaf.status = 2
                                     THEN 0 -- Free Seat MyApplication

                                 ELSE 1 -- Taken  Seat MyApplication
                        END
                    END as 'Status'
         FROM (
                  SELECT seats.seat_identifier as seat_identifier,
                         c.user_id,
                         c.FromDate,
                         c.ToDate,
                         c.Status
                  FROM (
                           SELECT fo_bs.seat_identifier,
                                  fo_bs.user_id,
                                  fo_bs.FromDate,
                                  fo_bs.ToDate,
                                  fo_bs.Status
                           FROM MyApplication.another_schema.BookedSeats fo_bs
                                    INNER JOIN MyApplication.another_schema.seats AS seats ON fo_bs.seat_identifier = seats.seat_identifier
                               WHERE fo_bs.FromDate <= @the_date
                                    AND fo_bs.ToDate >= @the_date
                                    AND fo_bs.Status IN (1, 2)
                                    AND seats.floor_id IN (###FLOOR_IDS###) -- will replace this from php with a list of "?,?,?" depending on how many floor_ids are in the query bindings
                       ) c
                           INNER JOIN MyApplication.another_schema.seats AS seats ON c.seat_identifier = seats.seat_identifier) d
                  LEFT JOIN (SELECT requester, status
                             from Main_DB_Name.schema.HOME_OFFICE
                                 WHERE Main_DB_Name.schema.HOME_OFFICE.from_date <= @the_date
                                      and Main_DB_Name.schema.HOME_OFFICE.to_date >= @the_date) ho ON d.user_id = ho.requester
                  LEFT JOIN (SELECT requester, status
                             from Main_DB_Name.schema.TEMPORARY_WORK_LOCATION
                                 WHERE Main_DB_Name.schema.TEMPORARY_WORK_LOCATION.from_date <= @the_date
                                      and Main_DB_Name.schema.TEMPORARY_WORK_LOCATION.to_date >= @the_date) twl
                            ON d.user_id = twl.requester
                  LEFT JOIN (SELECT employee, status
                             from Main_DB_Name.schema.LEAVE_INVOIRE
                                 WHERE Main_DB_Name.schema.LEAVE_INVOIRE.leave_date = @the_date) li ON d.user_id = li.employee
                  LEFT JOIN (SELECT requester, status
                             from Main_DB_Name.schema.TRAVEL
                                 WHERE Main_DB_Name.schema.TRAVEL.from_date <= @the_date
                                      and Main_DB_Name.schema.TRAVEL.until_date >= @the_date) ctaf
                            ON d.user_id = ctaf.requester
     ) y

我的模型/关系如下:

class Building extends Model {
  /* Properties */

   public function floors()
    {
        return $this->hasMany(Floor::class);
    }
}

class Floor extends Model {
  /* Properties */
   public function building()
    {
        return $this->belongsTo(Building::class);
    }

   public function seats()
    {
        return $this->hasMany(Seat::class);
    }
}

class Seat extends Model {
  /* Properties */
   public function floor()
    {
        return $this->belongsTo(Floor::class);
    }

   public function booked()
    {
        return $this->hasMany(BookedSeat::class);
    }
}

class BookedSeat extends Model {
  /* Properties */
   public function user()
    {
        return $this->belongsTo(User::class);
    }

   public function seat()
    {
        return $this->belongsTo(Seat::class);
    }
}

【问题讨论】:

  • Buildings::with('floors.seats.bookedseats.users')-&gt;get() 这样的东西不会给你所有的东西吗? (假设with 部分是您关系的正确名称)
  • 是的,但它会为我提供数据库中的所有内容。我需要以某种方式使用这个复杂的查询,因为它有一系列基于许多约束的 CASE-WHEN (T-SQL) 并设置了 @ 987654353@ 为每个 BookedSeat 实例动态
  • 然后我在前端使用该状态来允许用户预订座位或不预订
  • 您能否提供一个包含尽可能少的模型/关系和实际原始查询的简单示例?
  • 将原始查询集成到您的急切加载设置中非常复杂。我会建议一种不同的方法:分别执行主查询和原始查询,然后使用Model::setRelation() 手动将原始结果插入层次结构。你可以查看关系的match() 方法来了解 Laravel 是如何处理这个问题的。

标签: php laravel eloquent eager-loading laravel-query-builder


【解决方案1】:

这是一个相当困难的问题。我坚持尝试不同的事情总共超过一个星期,但找不到任何好的方法。

我最终使用了@Jonas Staudenmeir 的建议,方法是手动映射我所有的嵌套关系,然后在我的Seat 模型实例上设置相应的booked 关系,该关系来自使用BookedSeat::hydrate() 获得的集合以及原始结果查询作为参数。

$availableSeats = new AvailableSeatsQuerySimple($format);

        // Map through all the Buildings
        $this->template['buildings'] = $this->template['buildings']
            ->map(static function (Building $building) use ($availableSeats) {

                // Map through all the Floors in a Building
                $floors = $building->floors->map(static function (Floor $floor) use ($availableSeats) {
                    /** @var BookedSeat|Collection $booked */
                    $booked = $availableSeats->execute($floor->id); // execute the raw query and get the results

                    if(count($booked) > 0) {

                        // Map through all the Seats in a Floor
                        $seats = $floor->seats->map(static function (Seat $seat) use ($booked) {

                            // Select the BookedSeat for the corresponding Seat
                            /** @var BookedSeat $bookedSeatForRelation */
                            $bookedSeatForRelation = $booked->filter(static function (BookedSeat $bookedSeat) use ($seat) {
                                return $bookedSeat->seat_identifier === $seat->id;
                            })->first();

                            // Attach the BookedSeat to the Seat only if the Status IS NOT 0
                            if($bookedSeatForRelation !== null && $bookedSeatForRelation->Status !== 0) {
                                return $seat->setRelation('booked', $bookedSeatForRelation);
                            }

                            return $seat->setRelation('booked', null);
                        });

                        return $floor->setRelation('seats', $seats);
                    }

                    return $floor;
                });

                return $building->setRelation('floors', $floors);
            });

【讨论】:

猜你喜欢
  • 2017-10-01
  • 2015-04-09
  • 2021-09-18
  • 2019-09-08
  • 2014-01-18
  • 2021-03-22
  • 1970-01-01
  • 2012-10-29
相关资源
最近更新 更多