1

尝试在 Propel 2 中做一个相当简单的查询。我有一个 Person 表和一个 Possession 表 - 人们可以拥有许多财产,但每种财产类型只有一个。因此,一个人可以拥有 1 本书、1 辆汽车等。我正在尝试在 Propel 中编写一个查询,如果他们有车,它将返回所有人及其车名。这是代码和结果查询:

$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 的任何想法?(如果我去掉 'where' 子句,CROSS JOIN 也会消失)

4

3 回答 3

3

找到了解决此问题的方法。问题是,当我尝试对同一个表进行多个左连接(通过使用别名)时,Propel 还会包含对该表的交叉连接。但是,我发现如果第一个join没有使用别名,Propel就不会添加cross join。

所以,总而言之,这是行不通的:

$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;
于 2016-12-10T23:38:11.810 回答
2

你为什么离开加入?如果我正确理解您的帖子,您只需要实际拥有汽车的人的结果。所以以下应该工作:

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();
于 2016-10-25T14:46:45.727 回答
1

这绝对看起来像一个错误。你举报了吗?

一般来说,Propel 会检查你的 where 条件指向的关系/别名,然后确保所有这些表都包含在查询中。如果该关系/别名没有现有的连接子句,它将使用 CROSS JOIN 包含它。

->where()方法没有通过方法参数提供的关系别名(如 filterByX 有),而 propel 看到“p”。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

于 2017-06-29T09:31:41.957 回答