条件仅在查询中加入

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句号。