条件仅在查询中加入
Condition only inside join in query
我正在尝试在 Symfony 中进行查询,我想在其中查找特定员工一天的午餐。我真的无法想象它会那么难,所以我希望有一个简单的答案。
数据库中的不同表是 single_day、午餐表和员工表。一天可以有许多来自不同员工的午餐与之相关。在其余 API 中,这将是 url 完成此操作:https://example.org/api/day?employee=728
在 SQL 这个简单的查询中解决了它:
SELECT * FROM hs_morning.single_day as single_day
LEFT JOIN (SELECT id as lunch_id, vegetarian, employee_id, single_day_id FROM hs_morning.lunch WHERE lunch.employee_id = 727) as lunch
ON single_day.id = lunch.single_day_id
我的存储库中有这段代码,它 returns 整天都是我想要的,但它不会过滤掉其他员工的午餐:
public function findDayAndLunchesByEmployee($employee, $limit = 10)
{
return $this->createQueryBuilder('d')
->leftJoin('d.lunches', 'l', Expr\Join::WITH, 'l.employee = :val') // conditions here have no effect
// ->andWhere('l.employee = :val') //this will prevent all days from being included (which I need) - so it does not work
->setParameter('val', $employee)
->orderBy('d.id', 'ASC')
->setMaxResults($limit)
->getQuery()
->getResult()
;
}
这就是现在的结果。我对 https://example.org/api/day?employee=728 的要求几乎是完美的,但我不想要员工 nr 的午餐。 727 出现,只有员工 nr 的午餐。 728:
[
{
"id": 118,
"date": "2021-10-05T09:00:00+00:00",
"lunches": [
{
"id": 2202,
"vegetarian": false,
"employee": {
"id": 728
}
}
]
},
{
"id": 119,
"date": "2021-10-06T09:00:00+00:00",
"lunches": [
{
"id": 2199,
"vegetarian": false,
"employee": {
"id": 727
}
}
]
},
{
"id": 120,
"date": "2021-10-07T09:00:00+00:00",
"lunches": [
{
"id": 2200,
"vegetarian": false,
"employee": {
"id": 727
}
},
{
"id": 2201,
"vegetarian": false,
"employee": {
"id": 728
}
}
]
},
{
"id": 121,
"date": "2021-10-08T09:00:00+00:00",
"lunches": []
},
{
"id": 122,
"date": "2021-10-09T09:00:00+00:00",
"lunches": []
}
]
换句话说,这不应该出现在响应中:
{
"id": 2201,
"vegetarian": false,
"employee": {
"id": 728
}
}
编辑:
非常感谢@V-Light 提供了可行的解决方案。仅供参考:我也设法用这段代码实现了相同的结果(使用 ResultSetMappingBuilder 和 createNativeQuery):
public function findRecentDaysAndLunchesByEmployee($employee_id, $limit = 10)
{
$sql = "SELECT * FROM single_day as single_day
LEFT JOIN (
SELECT single_day_id, employee_id, vegetarian, id as lunch_id
FROM lunch WHERE employee_id = $employee_id
) as lunch
ON single_day.id = lunch.single_day_id
WHERE
date BETWEEN '"
. date("Y-m-d h:i:s", strtotime('monday this week 1am'))
. "' AND '"
. date("Y-m-d h:i:s", strtotime('friday this week'))
. "'
LIMIT $limit";
$entityManager = $this->getEntityManager();
$rsm = new ResultSetMappingBuilder($entityManager);
//map query to SingleDay entity
$rsm->addRootEntityFromClassMetadata('App\Entity\SingleDay', 'single_day');
/**
* map subquery to Lunch entity
* 2. param: lunch alias
* 3. param: parent query
* 4. param: name of ORM relation
* 5. param: new name for id of lunch in order to prevent conflict with id from single_day
*/
$rsm->addJoinedEntityFromClassMetadata('App\Entity\Lunch', 'lunch', 'single_day', 'lunches', array('id' => 'lunch_id'));
$query = $entityManager->createNativeQuery($sql, $rsm);
return $query->getResult();
}
没有 ORM-Relations 会更难,但我还是会尝试的。
首先,正如我在评论中所说,没有简单的方法可以将您的 SQL 转移到 DQL。 Doctrine 不知道如何使用子查询或临时表。
但是,我看到 两个 可能的解决方案:
首先 - 尝试拆分您的查询。将你的“pre-selection”移出
SELECT id FROM lunch WHERE employee_id = 727
然后将LEFT JOIN中的结果作为条件
SELECT *
FROM single_day d
LEFT JOIN lunch l ON (d.id = l.single_day_id AND l.id IN (:lunch_ids_from_prev_selection) )
使用 QueryBuilder 它看起来像这样。
public function findDayAndLunchesByEmployee($employee, $limit = 10) {
// first, get all distinct lunch_ids by given employee
$distinctLunchesQuery = $this->_em->getRepository(Lunch::class)->createQueryBuilder('l')
->select('l.id AS lunch_id')->distinct()
->where('l.employee = :given_employee')->setParameter('given_employee', $employee)
->getQuery();
$preSelected = $distinctLunchesQuery->getScalarResult();
// distinct ID as flat-array
$distinctIds = array_column($preSelected, 'lunch_id'); // see ALIAS in $distinctLunchesQuery
// check if atleast one was found
if(empty($distinctIds[0]))
{
// IN() condition doesn't work with empty lists.
// since your second query depends on this list, you just provide some dummy (or not existing) data which will 100% NOT MATCH!
$distinctIds = [-1];
}
// your "regular" query with LEFT JOIN
$allSingleDayQuery = $this->createQueryBuilder('d')
->select('d')
->leftJoin('d.lunches', 'l', 'WITH', 'l.id IN (:given_lunched_ids_list)')
->addSelect('l') // important!
->setParameter('given_lunched_ids_list', $distinctIds)
->orderBy('d.id', 'ASC')
->setMaxResults($limit)
->getQuery()
->getResult()
;
return $allSingleDayQuery;
}
但根据您的 orm 关系,尤其是其中之一配置为 fetch="EAGER"
,结果可能与您的预期不符。
其次 - 自己编写想要的结果
当我看到最终输出时,我看到 JSON。我也看到越来越多的日子。因此,您可以通过 selecting 所有 single_day
仅一个月(或其他一段时间)
来“缩短”您的 selection
因此,您可以再次 select 所需的 single_day
个条目,然后遍历所有条目并检查是否有任何 lunches
由您还预select句号。
我正在尝试在 Symfony 中进行查询,我想在其中查找特定员工一天的午餐。我真的无法想象它会那么难,所以我希望有一个简单的答案。
数据库中的不同表是 single_day、午餐表和员工表。一天可以有许多来自不同员工的午餐与之相关。在其余 API 中,这将是 url 完成此操作:https://example.org/api/day?employee=728
在 SQL 这个简单的查询中解决了它:
SELECT * FROM hs_morning.single_day as single_day
LEFT JOIN (SELECT id as lunch_id, vegetarian, employee_id, single_day_id FROM hs_morning.lunch WHERE lunch.employee_id = 727) as lunch
ON single_day.id = lunch.single_day_id
我的存储库中有这段代码,它 returns 整天都是我想要的,但它不会过滤掉其他员工的午餐:
public function findDayAndLunchesByEmployee($employee, $limit = 10)
{
return $this->createQueryBuilder('d')
->leftJoin('d.lunches', 'l', Expr\Join::WITH, 'l.employee = :val') // conditions here have no effect
// ->andWhere('l.employee = :val') //this will prevent all days from being included (which I need) - so it does not work
->setParameter('val', $employee)
->orderBy('d.id', 'ASC')
->setMaxResults($limit)
->getQuery()
->getResult()
;
}
这就是现在的结果。我对 https://example.org/api/day?employee=728 的要求几乎是完美的,但我不想要员工 nr 的午餐。 727 出现,只有员工 nr 的午餐。 728:
[
{
"id": 118,
"date": "2021-10-05T09:00:00+00:00",
"lunches": [
{
"id": 2202,
"vegetarian": false,
"employee": {
"id": 728
}
}
]
},
{
"id": 119,
"date": "2021-10-06T09:00:00+00:00",
"lunches": [
{
"id": 2199,
"vegetarian": false,
"employee": {
"id": 727
}
}
]
},
{
"id": 120,
"date": "2021-10-07T09:00:00+00:00",
"lunches": [
{
"id": 2200,
"vegetarian": false,
"employee": {
"id": 727
}
},
{
"id": 2201,
"vegetarian": false,
"employee": {
"id": 728
}
}
]
},
{
"id": 121,
"date": "2021-10-08T09:00:00+00:00",
"lunches": []
},
{
"id": 122,
"date": "2021-10-09T09:00:00+00:00",
"lunches": []
}
]
换句话说,这不应该出现在响应中:
{
"id": 2201,
"vegetarian": false,
"employee": {
"id": 728
}
}
编辑: 非常感谢@V-Light 提供了可行的解决方案。仅供参考:我也设法用这段代码实现了相同的结果(使用 ResultSetMappingBuilder 和 createNativeQuery):
public function findRecentDaysAndLunchesByEmployee($employee_id, $limit = 10)
{
$sql = "SELECT * FROM single_day as single_day
LEFT JOIN (
SELECT single_day_id, employee_id, vegetarian, id as lunch_id
FROM lunch WHERE employee_id = $employee_id
) as lunch
ON single_day.id = lunch.single_day_id
WHERE
date BETWEEN '"
. date("Y-m-d h:i:s", strtotime('monday this week 1am'))
. "' AND '"
. date("Y-m-d h:i:s", strtotime('friday this week'))
. "'
LIMIT $limit";
$entityManager = $this->getEntityManager();
$rsm = new ResultSetMappingBuilder($entityManager);
//map query to SingleDay entity
$rsm->addRootEntityFromClassMetadata('App\Entity\SingleDay', 'single_day');
/**
* map subquery to Lunch entity
* 2. param: lunch alias
* 3. param: parent query
* 4. param: name of ORM relation
* 5. param: new name for id of lunch in order to prevent conflict with id from single_day
*/
$rsm->addJoinedEntityFromClassMetadata('App\Entity\Lunch', 'lunch', 'single_day', 'lunches', array('id' => 'lunch_id'));
$query = $entityManager->createNativeQuery($sql, $rsm);
return $query->getResult();
}
没有 ORM-Relations 会更难,但我还是会尝试的。
首先,正如我在评论中所说,没有简单的方法可以将您的 SQL 转移到 DQL。 Doctrine 不知道如何使用子查询或临时表。
但是,我看到 两个 可能的解决方案:
首先 - 尝试拆分您的查询。将你的“pre-selection”移出
SELECT id FROM lunch WHERE employee_id = 727
然后将LEFT JOIN中的结果作为条件
SELECT *
FROM single_day d
LEFT JOIN lunch l ON (d.id = l.single_day_id AND l.id IN (:lunch_ids_from_prev_selection) )
使用 QueryBuilder 它看起来像这样。
public function findDayAndLunchesByEmployee($employee, $limit = 10) {
// first, get all distinct lunch_ids by given employee
$distinctLunchesQuery = $this->_em->getRepository(Lunch::class)->createQueryBuilder('l')
->select('l.id AS lunch_id')->distinct()
->where('l.employee = :given_employee')->setParameter('given_employee', $employee)
->getQuery();
$preSelected = $distinctLunchesQuery->getScalarResult();
// distinct ID as flat-array
$distinctIds = array_column($preSelected, 'lunch_id'); // see ALIAS in $distinctLunchesQuery
// check if atleast one was found
if(empty($distinctIds[0]))
{
// IN() condition doesn't work with empty lists.
// since your second query depends on this list, you just provide some dummy (or not existing) data which will 100% NOT MATCH!
$distinctIds = [-1];
}
// your "regular" query with LEFT JOIN
$allSingleDayQuery = $this->createQueryBuilder('d')
->select('d')
->leftJoin('d.lunches', 'l', 'WITH', 'l.id IN (:given_lunched_ids_list)')
->addSelect('l') // important!
->setParameter('given_lunched_ids_list', $distinctIds)
->orderBy('d.id', 'ASC')
->setMaxResults($limit)
->getQuery()
->getResult()
;
return $allSingleDayQuery;
}
但根据您的 orm 关系,尤其是其中之一配置为 fetch="EAGER"
,结果可能与您的预期不符。
其次 - 自己编写想要的结果
当我看到最终输出时,我看到 JSON。我也看到越来越多的日子。因此,您可以通过 selecting 所有 single_day
仅一个月(或其他一段时间)
因此,您可以再次 select 所需的 single_day
个条目,然后遍历所有条目并检查是否有任何 lunches
由您还预select句号。