Laravel - 在关系 eagerload 中集成大型原始查询
Laravel - Integrate big raw query in relationship eagerload
我已经使用 laravel 大约 4 年了,但现在我面临着最大的挑战。我正在重构和扩展用于映射我工作办公室的旧 PHP
应用程序。我需要以某种方式将这个巨大的 SQL
查询集成到 Laravel 的 QueryBuilder 中,以提供一个急切加载的关系。
流程是这样的
Building => hasMany: Floor => hasMany: Seat => hasMany: BookedSeat => belongsTo: User
其中 Building
、Floor
、Seat
和 BookedSeat
是 Eloquent
个模型。
我的大型查询根据许多其他条件从 BookedSeat
中选择当前日期的 Seat
预订,例如预订座位的人是否在家办公、度假等(这些存储在其他一些表中)并在名为 Status
的 BookedSeat
实例上设置一个 属性 以了解当天 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
]
}
巨大的查询 returns 一个对象数组,我可以用它来组合 BookedSeat
集合,但我不知道如何使用这个集合或直接使用巨大的查询为了急切地为每个 Seat
为每个 Floor
为每个 Building
加载 BookedSeat
并让框架为我完成繁重的工作。
我尝试构建如下方法:
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
但我不知道如何转换巨大的查询生成器实例。
谢谢!
PS:由于复杂性
,我最好跳过将巨大的查询重写为 eloquent 语法
添加的详细信息:
因此,根据要求,这是我的原始查询,我根据公司政策
更改了一些 database/table 名称
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
我的models/relationships如下:
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);
}
}
这道题比较难。我总共坚持了一个多星期尝试不同的事情,但找不到任何好的方法。
我最终使用了@Jonas Staudenmeir 的建议,通过手动映射我所有的嵌套关系,然后在我的 Seat
模型实例上设置相应的 booked
关系,这些实例来自使用 [=13] 获得的集合=] 将原始查询的结果作为参数。
$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);
});
我已经使用 laravel 大约 4 年了,但现在我面临着最大的挑战。我正在重构和扩展用于映射我工作办公室的旧 PHP
应用程序。我需要以某种方式将这个巨大的 SQL
查询集成到 Laravel 的 QueryBuilder 中,以提供一个急切加载的关系。
流程是这样的
Building => hasMany: Floor => hasMany: Seat => hasMany: BookedSeat => belongsTo: User
其中 Building
、Floor
、Seat
和 BookedSeat
是 Eloquent
个模型。
我的大型查询根据许多其他条件从 BookedSeat
中选择当前日期的 Seat
预订,例如预订座位的人是否在家办公、度假等(这些存储在其他一些表中)并在名为 Status
的 BookedSeat
实例上设置一个 属性 以了解当天 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
]
}
巨大的查询 returns 一个对象数组,我可以用它来组合 BookedSeat
集合,但我不知道如何使用这个集合或直接使用巨大的查询为了急切地为每个 Seat
为每个 Floor
为每个 Building
加载 BookedSeat
并让框架为我完成繁重的工作。
我尝试构建如下方法:
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
但我不知道如何转换巨大的查询生成器实例。
谢谢!
PS:由于复杂性
,我最好跳过将巨大的查询重写为 eloquent 语法添加的详细信息:
因此,根据要求,这是我的原始查询,我根据公司政策
更改了一些 database/table 名称 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
我的models/relationships如下:
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);
}
}
这道题比较难。我总共坚持了一个多星期尝试不同的事情,但找不到任何好的方法。
我最终使用了@Jonas Staudenmeir 的建议,通过手动映射我所有的嵌套关系,然后在我的 Seat
模型实例上设置相应的 booked
关系,这些实例来自使用 [=13] 获得的集合=] 将原始查询的结果作为参数。
$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);
});