当使用JOIN表

时间:2016-10-18 19:22:01

标签: php mysql sql propel propel2

尝试在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也会消失

3 个答案:

答案 0 :(得分: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();

答案 1 :(得分:2)

找到此问题的解决方法。问题是,当我尝试对同一个表执行多个左连接时(通过使用别名),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;

答案 2 :(得分:0)

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

通常,Propel会检查标准所指向的关系/别名,然后确保所有这些表都包含在查询中。如果该关系/别名没有现有的连接子句,则它将包含CROSS JOIN。

->where()方法没有通过方法参数提供的关系别名(比如filterByX有),而推进时看到'p'。 where()条件的一部分映射到拥有表,它没有意识到它通过使用关系别名得到了这个结论(这是错误),所以propel认为where()想要检查对possession表(没有别名),因此包括交叉连接。

因为这个错误,你的解决方法才有效,写起来会更清楚:

 ->where('possession.possession_name IS NOT NULL')

...澄清那里where()指的是不公平的占有表而不是特定的关系别名。

如果我是正确的,如果您引用了第二个别名,则您的解决方法不起作用:

 ->where('p2.possession_name IS NOT NULL')

(它仍会引用第一个关系)

PS Imho,Propel应该抛出异常而不是这种自动交叉连接 - “修复”查询,因为大多数情况下,交叉连接是拼写错误或查询方法使用不正确的不必要结果。

与此同时,这里有一个带有帮助代码的要点,让您检查这些条件并在必要时抛出异常:https://gist.github.com/motin/2b00295ca71bb876f9873712544dd077