当对JOIN表使用别名时,Propel将CROSS JOIN添加到查询中



尝试在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连接两次,别名p1p2,字段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

相关内容

  • 没有找到相关文章

最新更新