Propel добавляет CROSS JOIN к запросу при использовании псевдонима к таблицам JOIN

Попытка сделать довольно простой запрос в 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 при использовании псевдонима для моей объединенной таблицы? (CROSS JOIN также исчезает, если я уберу предложение 'where')

4 ответа

Решение

Нашел обходной путь для этой проблемы. Проблема заключалась в том, что когда я пытался сделать несколько левых соединений с одной и той же таблицей (используя псевдонимы), 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();

Это определенно выглядит как ошибка. Вы сообщили об этом?

В общем, Propel проверит, на какие отношения / псевдонимы указывают ваши критерии местоположения, а затем удостоверится, что все эти таблицы включены в запрос. Если для этого отношения / псевдонима нет существующего предложения соединения, оно будет включено в CROSS JOIN.

->where() Метод не имеет псевдонима отношения, предоставленного через параметр метода (как у filterByX), и в то время как propel видит, что 'p.' часть условия where() отображается на таблицу владения, она не учитывает тот факт, что он пришел к такому выводу, используя псевдоним отношения (это ошибка), поэтому propel считает, что where() хочет проверить против possession таблица (без псевдонима), и, следовательно, перекрестное соединение включено.

Ваш обходной путь только таким образом работает из-за этой ошибки, и было бы яснее написать:

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

... чтобы уточнить, что там, где () относится к непроверенной таблице владения, а не к псевдониму конкретного отношения.

Если я прав, ваш обходной путь не должен работать, если вы указали второй псевдоним:

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

(это все равно будет ссылаться на первое отношение)

PS Имхо, Propel должен выдать исключение вместо этого автоматического "исправления" перекрестного объединения, поскольку чаще всего перекрестное объединение является нежелательным следствием опечатки или неправильного использования методов запроса.

Между тем, вот список с вспомогательным кодом, который позволяет вам проверять эти условия и генерировать исключения при необходимости: https://gist.github.com/motin/2b00295ca71bb876f9873712544dd077

Решение, представленное zeke, в некоторых случаях не работает должным образом.

На первый взгляд, Propel плохо управляет псевдонимами таблиц, если в одной и той же таблице имеется более одной инструкции соединения. Он знает, как сохранить псевдоним таблицы при построении пары таблица/столбец, как для предложений select, так и для предложений where, в большинстве случаев, но не во всех!

Фактически, есть ошибка. Но есть собственное решение, так что Propel управляет связанными псевдонимами так же, как и ожидалось: используя формат столбцов «Phpname» (без верблюжьего регистра, без змеиного регистра, но формат Phpname Propel).

Пример с таблицей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 ​​в форматеTableAlias.Fieldname.

Другие вопросы по тегам