简体   繁体   中英

Propel adds CROSS JOIN to query when using an alias to JOIN tables

Trying to do a fairly simple query in Propel 2. I have a Person table and a Possession table - persons can have many possessions but only one of each possession type. So a person can have 1 book, 1 car, etc. I'm trying to write a query in Propel that will return all persons along with their car name provided they have a car. Here's the code and resulting query:

$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;

This works as expected. However, I need to do multiple joins to the possession table (get the books for each person for example) so I need to use an alias. Here's what happens when I modify the previous query just to use an alias for the possession table (and to also get the book for each person):

$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;

As you can see, for some reason Propel adds "CROSS JOIN possession" to the query. This doesn't change the result of the query but it makes it extremely slow. Any ideas on how I can tell Propel not to use a CROSS JOIN while still using an alias for my joined table? (the CROSS JOIN also disappears if I take out the 'where' clause)

Found a workaround for this issue. The problem was that when I tried to do multiple left joins to the same table (by using aliases) Propel would also include a cross join to that table. However, I found that if the first join did not use an alias, Propel would not add the cross join.

So, to sum up, this was not working:

$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;

But this worked as expected:

$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;

Why are you left joining? If I understand your post correctly you only want results for persons that do actually have a car. So the following should work:

PersonQuery::create()
  ->withColumn('Possession.possession_name', 'CarName')
  ->usePossessionQuery()
  ->filterByPossessionType('car')
  ->endUse()
  ->find()
  ->toString();

useXxxQuery() can also be given an alias, so you should be able to accomplish the same, but more easily :)

EDIT: an example with multiple joins, as per your second example:

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();

This definitely looks like a bug. Did you report it?

In general, Propel will check what relation/aliases your where criterias are pointing to, and then make sure that all those tables are included in the query. If there is no existing join clause for that relation/alias, it will include it with a CROSS JOIN.

The ->where() method has no relation alias supplied via a method parameter (like filterByX has), and while propel sees that the 'p.' part of the where() condition maps to the possession table, it does not pick up on the fact that it got to that conclusion by using the relation alias (this is the bug), so propel thinks that the where() wants to check against the possession table (without alias), and thus the cross join is included.

Your workaround only thus works because of this bug, and it would be clearer to write:

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

... to clarify that there where() refers to the unaliased possession table and not a specific relation alias.

If I am correct, your workaround should not work if you referenced the second alias:

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

(it would still reference the first relation)

PS Imho, Propel should throw an exception instead of this automagic cross-join-"fixing" of the query, since most often, the cross join is an unwanted consequence of a typo or incorrect usage of the query methods.

Meanwhile, here is a gist with helper code that let's you check for these conditions and throw exceptions where necessary: https://gist.github.com/motin/2b00295ca71bb876f9873712544dd077

The solution introduced by zeke does not seem to work as expected in several cases.

Propel has not a good management of table aliases if there is more than one join instruction on the same table, at first sight. It does know how to keep the alias of a table when building the couple of table / column, both for select and where clauses, in most cases but not in all !

Effectively, there is a bug. But there is a native solution so that Propel manages related aliases as well as expected: using the "Phpname" format of columns (no camel case, no snake case, but the Phpname format of Propel).

Example with the table Possession joined two times, with alias p1 and p2 and a field nameOfCar :

For the SELECT, to get the right value according to the joined table, we have to create aliases:

$query->addAsColumn('p1.nameOfCar', 'p1.Nameofcar');
$query->addAsColumn('p2.nameOfCar', 'p2.Nameofcar');

For the WHERE clause, to get the right value according to the joined table:

// 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');

For users which use "Phpname" in a default configuration, there is no bug. For the others, with these workarounds it is possible to join the same table several times, with a dedicated alias for each table, and a way to SELECT fields and to add WHERE clauses (via ->where() or ->condition() ): Propel will keep TableAlias in the format TableAlias.Fieldname .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM