尝试在Propel 2中做一个相当简单的查询。我有一个人表和一个占有表-人可以有许多财产,但只有一个每个占有类型。一个人可以有一本书,一辆车,等等。我试图写一个查询在推进,将返回所有人与他们的汽车名称提供他们有一辆车。下面是代码和结果查询:
$x = PersonQuery::create()
->groupById()
->leftJoinPossession()
->addJoinCondition('Possession','Possession.possession_type = ?', 'car')
->withColumn('Possession.possession_name', 'CarName')
->where('Possession.possession_name IS NOT NULL')
->find()
->toString();
//resulting sql
SELECT person.id, possession.possession_name as CarName
FROM person
LEFT JOIN possession ON (person.id=possession.person_id AND possession.possession_type = 'car')
WHERE possession.possession_name IS NOT NULL;
按预期工作。但是,我需要对占有表执行多个连接(例如,获取每个人的图书),因此我需要使用别名。下面是当我修改前面的查询只是为占有表使用别名(并且还为每个人获取图书)时发生的情况:
$x = PersonQuery::create()
->groupById()
->leftJoinPossession('p')
->addJoinCondition('p','p.possession_type = ?', 'car')
->leftJoinPossession('p2')
->addJoinCondition('p2','p2.possession_type = ?', 'book')
->withColumn('p.possession_name', 'CarName')
->withColumn('p2.possession_name', 'BookName')
->where('p.possession_name IS NOT NULL')
->find()
->toString();
//resulting sql
SELECT person.id, p.possession_name as CarName, p2.possession_name as BookName
FROM person
CROSS JOIN possession
LEFT JOIN possession p ON (person.id=p.person_id AND p.possession_type = 'car')
LEFT JOIN possession p2 ON (person.id=p2.person_id AND p2.possession_type = 'book')
WHERE p.possession_name IS NOT NULL;
可以看到,出于某种原因,Propel将"CROSS JOIN占有"添加到查询中。这不会改变查询的结果,但会使查询变得非常慢。关于我如何告诉Propel不使用CROSS JOIN而仍然使用别名我的连接表的任何想法?(CROSS JOIN也消失,如果我拿出'where'子句)
找到了解决此问题的方法。问题是,当我试图对同一个表进行多个左连接(通过使用别名)时,Propel还将包括对该表的交叉连接。但是,我发现如果第一个连接没有使用别名,那么Propel将不会添加交叉连接。
所以,总结一下,这是行不通的:
$x = PersonQuery::create()
->groupById()
->leftJoinPossession('p')
->addJoinCondition('p','p.possession_type = ?', 'car')
->leftJoinPossession('p2')
->addJoinCondition('p2','p2.possession_type = ?', 'book')
->withColumn('p.possession_name', 'CarName')
->withColumn('p2.possession_name', 'BookName')
->where('p.possession_name IS NOT NULL')
->find()
->toString();
//resulting sql
SELECT person.id, p.possession_name as CarName, p2.possession_name as BookName
FROM person
CROSS JOIN possession
LEFT JOIN possession p ON (person.id=p.person_id AND p.possession_type = 'car')
LEFT JOIN possession p2 ON (person.id=p2.person_id AND p2.possession_type = 'book')
WHERE p.possession_name IS NOT NULL;
但这工作如预期的:
$x = PersonQuery::create()
->groupById()
->leftJoinPossession() //changed
->addJoinCondition('Possession','Possession.possession_type = ?', 'car') //changed
->leftJoinPossession('p2')
->addJoinCondition('p2','p2.possession_type = ?', 'book')
->withColumn('p.possession_name', 'CarName')
->withColumn('p2.possession_name', 'BookName')
->where('p.possession_name IS NOT NULL')
->find()
->toString();
//resulting sql
SELECT person.id, p.possession_name as CarName, p2.possession_name as BookName
FROM person
LEFT JOIN possession ON (person.id=posession.person_id AND posession.possession_type = 'car')
LEFT JOIN possession p2 ON (person.id=p2.person_id AND p2.possession_type = 'book')
WHERE p.possession_name IS NOT NULL;
你为什么不加入?如果我理解你的帖子正确的话,你只想要那些确实有车的人的结果。所以下面的代码应该可以工作:
PersonQuery::create()
->withColumn('Possession.possession_name', 'CarName')
->usePossessionQuery()
->filterByPossessionType('car')
->endUse()
->find()
->toString();
useXxxQuery()也可以被赋予一个别名,所以你应该能够完成相同的,但更容易:)
编辑:一个有多个连接的例子,正如你的第二个例子:
PersonQuery::create()
->withColumn('possession_car.possession_name', 'CarName')
->withColumn('possession_book.possession_name', 'BookName')
->usePossessionQuery('possession_car')
->filterByPossessionType('car')
->endUse()
->usePossessionQuery('possession_book')
->filterByPossessionType('book')
->endUse()
->find()
->toString();
这看起来肯定是一个bug。你报告了吗?
一般情况下,Propel将检查where条件指向的关系/别名,然后确保所有这些表都包含在查询中。如果该关系/别名没有现有的连接子句,它将用CROSS join包含它。
->where()
方法没有通过方法参数提供的关系别名(如filterByX),并且当propel看到'p. x '时。' where()条件的一部分映射到占有表,它没有通过使用关系别名(这是错误)得出结论的事实,因此propel认为where()想要检查possession
表(没有别名),因此包括交叉连接。
由于这个错误,您的解决方法只能这样工作,这样写会更清楚:
->where('possession.possession_name IS NOT NULL')
…澄清一下,where()指的是未使用别名的占有表,而不是特定的关系别名。
如果我是正确的,你的解决方案不应该工作,如果你引用第二个别名:
->where('p2.possession_name IS NOT NULL')
(它仍然引用第一个关系)
PS我的意思是,Propel应该抛出一个异常,而不是自动交叉连接——"修复"查询,因为大多数情况下,交叉连接是查询方法的拼写错误或不正确使用的不希望的结果。
同时,这里是帮助代码的要点,让您检查这些条件并在必要时抛出异常:https://gist.github.com/motin/2b00295ca71bb876f9873712544dd077
zeke引入的解决方案在一些情况下似乎没有像预期的那样工作。
如果在同一个表上有多个连接指令,那么Propel对表别名的管理不是很好。它知道如何在为select和where子句构建一对表/列时保持表的别名,在大多数情况下,但不是全部!
实际上,这里有一个bug。但是有一个原生的解决方案,使Propel能够像预期的那样管理相关的别名:使用"phpname";列的格式(没有驼峰大小写,没有蛇形大小写,而是使用Propel的Phpname格式)。
示例,表Possession
连接两次,别名p1
和p2
,字段nameOfCar
:
对于SELECT,为了根据所连接的表获得正确的值,我们必须创建别名:
$query->addAsColumn('p1.nameOfCar', 'p1.Nameofcar');
$query->addAsColumn('p2.nameOfCar', 'p2.Nameofcar');
对于WHERE子句,根据所连接的表获得正确的值:
// we will **not use** :
$query->where('p1.nameOfCar = ?', 'Coccinelle');
$query->where('p2.nameOfCar = ?', 'Dacia');
// But we will **use** :
$query->where('p1.Nameofcar = ?', 'Coccinelle');
$query->where('p2.Nameofcar = ?', 'Dacia');
对于使用"Phpname"在默认配置中,没有错误。对于其他人来说,使用这些解决方案可以多次加入同一个表,为每个表使用专用别名,以及选择字段和添加WHERE子句的方法(通过->where()
或->condition()
): Propel将以TableAlias.Fieldname
的格式保留TableAlias。