Как управлять порядком соединения sql в Yii CDbCriteria с помощью

У меня есть следующие критерии для оператора findAll

$with=array(
    'tumor'=>array(
    'select'=>false,
    'joinType'=>'INNER JOIN',
    ),
    'tumorLibraryType'=>array(
    'select'=>false,
    'joinType'=>'INNER JOIN',
    'condition'=>'tumorLibraryType.id = 1 OR tumorLibraryType.id = 6',
    ),  
    'tumorPatient'=>array(
    'select'=>false,
    'joinType'=>'INNER JOIN',
    )
);

$libPairs=LibraryPairs::model()->with($with)->findAll();

Это соответствующие модельные отношения:

    'tumor' => array(self::BELONGS_TO, 'Libraries', array('tumor_library'=>'id')),
    'normal' => array(self::BELONGS_TO, 'Libraries', array('normal_library'=>'id')),        
    // making separate AR routes for tumor and normal. only tumor used currently
    'tumorLibraryType'=>array(self::HAS_ONE,'LibraryTypes','','on'=>'tumor.library_type_id = tumorLibraryType.id'),
    'tumorLibrariesIsolates'=>array(self::HAS_MANY,'LibrariesIsolates',array('id'=>'library_id'),'through'=>'tumor'),
    'tumorSamplesIsolates'=>array(self::HAS_MANY,'SamplesIsolates',array('isolate_id'=>'isolate_id'),'through'=>'tumorLibrariesIsolates'),
    'tumorSamples'=>array(self::HAS_MANY,'Samples',array('sample_id'=>'id'),'through'=>'tumorSamplesIsolates'),
    'tumorPatient'=>array(self::HAS_ONE,'Patients',array('patient_id'=>'id'),'through'=>'tumorSamples'),

Этот код генерирует следующий sql:

SELECT `t`.`tumor_library` AS `t0_c0`, `t`.`normal_library` AS `t0_c1`, `t`.`created` AS `t0_c2`, `t`.`created_by` AS `t0_c3`, `t`.`last_modified` AS `t0_c4`, `t`.`last_modified_by` AS `t0_c5`, `tumor`.`library_type_id` AS `t1_c2`, `tumor`.`id` AS `t1_c0` 
FROM `library_tumor_normal_pairs` `t` 
INNER JOIN `library_types` `tumorLibraryType` ON (tumor.library_type_id = tumorLibraryType.id) 
INNER JOIN `libraries` `tumor` ON (`t`.`tumor_library`=`tumor`.`id`) 
LEFT OUTER JOIN `libraries_isolates` `tumorLibrariesIsolates` ON (`tumor`.`id`=`tumorLibrariesIsolates`.`library_id`) 
LEFT OUTER JOIN `samples_isolates` `tumorSamplesIsolates` ON (`tumorLibrariesIsolates`.`isolate_id`=`tumorSamplesIsolates`.`isolate_id`) 
LEFT OUTER JOIN `samples` `tumorSamples` ON (`tumorSamplesIsolates`.`sample_id`=`tumorSamples`.`id`) 
INNER JOIN `patients` `tumorPatient` ON (`tumorSamples`.`patient_id`=`tumorPatient`.`id`) 
WHERE (tumorLibraryType.id = 1 OR tumorLibraryType.id = 6) 

Но этот sql выдает ошибку:

"Столбец не найден: 1054 Неизвестный столбец "tumor.library_type_id" в предложении "on".

Однако, если я просто перемещаю строку опухоли в запросе sql, чтобы она была первым оператором соединения, и запускаю запрос вручную, тогда запрос работает.

SELECT `t`.`tumor_library` AS `t0_c0`, `t`.`normal_library` AS `t0_c1`, `t`.`created` AS `t0_c2`, `t`.`created_by` AS `t0_c3`, `t`.`last_modified` AS `t0_c4`, `t`.`last_modified_by` AS `t0_c5`, `tumor`.`library_type_id` AS `t1_c2`, `tumor`.`id` AS `t1_c0` 
FROM `library_tumor_normal_pairs` `t` 
INNER JOIN `libraries` `tumor` ON (`t`.`tumor_library`=`tumor`.`id`) 
INNER JOIN `library_types` `tumorLibraryType` ON (tumor.library_type_id = tumorLibraryType.id) 
LEFT OUTER JOIN `libraries_isolates` `tumorLibrariesIsolates` ON (`tumor`.`id`=`tumorLibrariesIsolates`.`library_id`) 
LEFT OUTER JOIN `samples_isolates` `tumorSamplesIsolates` ON (`tumorLibrariesIsolates`.`isolate_id`=`tumorSamplesIsolates`.`isolate_id`) 
LEFT OUTER JOIN `samples` `tumorSamples` ON (`tumorSamplesIsolates`.`sample_id`=`tumorSamples`.`id`) 
INNER JOIN `patients` `tumorPatient` ON (`tumorSamples`.`patient_id`=`tumorPatient`.`id`) 
WHERE (tumorLibraryType.id = 1 OR tumorLibraryType.id = 6) 

Итак, мой вопрос: как я могу контролировать порядок соединения sql с критериями «с» в Yii? Является ли это возможным? Как вы можете видеть, мой массив with и отношения имеют «опухоль» раньше других, но порядок соединения не сохраняется.


person 111    schedule 09.07.2013    source источник
comment
В итоге я решил эту проблему по-другому, но все же хотел бы знать, как контролировать порядок соединения с помощью with, если кто-нибудь знает, как это сделать.   -  person 111    schedule 11.07.2013


Ответы (3)


Я столкнулся с похожей проблемой: Yii генерирует соединения в таком порядке, что оператор SQL становится недействительным. Такая ситуация возникает, например, когда вы пытаетесь написать собственный $CDBCriteria->join, который опирается на таблицы, указанные в отношениях, переданных $CDBCriteria->with. Это происходит потому, что join обрабатывается в CJoinQuery::__constructor, тогда как все "стандартные" соединения (из with) генерируются Yii в CJoinQuery::join, то есть после конструктора.

К сожалению, другого решения, кроме патча, нет. Вот пример того, как я это сделал на своей копии Yii (код предоставляется "как есть" - пожалуйста, проверьте, применимо ли это для вашего случая).

Во-первых, нам нужно добавить поле в CDbCriteria, которое будет включать/выключать новую опцию.

CDbCriteria.php

class CDbCriteria extends CComponent
{
  ...

  /**
   * @var string how to join with other tables. This refers to the JOIN clause in an SQL statement.
   * For example, <code>'LEFT JOIN users ON users.id=authorID'</code>.
   */
  public $join='';

  /**
   * Patch begins: 
   */
  public $joinreorder = false; // new option
  ...

Во-вторых, нам нужно расширить CJoinQuery (обратите внимание, это в CActiveFinder.php):

CActiveFinder.php

class CJoinQuery
{
  ...

  /**
   * @var array list of join element IDs (id=>true)
   */
  public $elements=array();

  /**
   * Patch begins: 
   */
  private $joinreorder = false; // the same new option
  private $postjoins; // the variable to store our custom joins
  ...

Теперь мы можем изменить конструктор CJoinQuery:

CActiveFinder.php (продолжение)

public function __construct($joinElement,$criteria=null)
{
  if($criteria!==null)
  {
    $this->joinreorder = $criteria->joinreorder;
    $this->selects[]=$joinElement->getColumnSelect($criteria->select);
    $this->joins[]=$joinElement->getTableNameWithAlias();
    if($this->joinreorder)                 //
    {                                      //
      $this->postjoins=$criteria->join;    // new lines
    }                                      //
    else                                   //
    {                                      //
      $this->joins[]=$criteria->join;
    }                                      //
    $this->conditions[]=$criteria->condition;
    $this->orders[]=$criteria->order;

Если joinreorder равно true, мы сохраняем пользовательские соединения в нашей новой переменной-члене postjoins. В остальном все должно работать как прежде.

А теперь собственно исправление в CJoinQuery::createCommand:

CActiveFinder.php (продолжение)

public function createCommand($builder)
{
  $sql=($this->distinct ? 'SELECT DISTINCT ':'SELECT ') . implode(', ',$this->selects);
  $sql.=' FROM ' . implode(' ',$this->joins);
  if($this->joinreorder)      //
  {                           //
    $sql .= $this->postjoins; // new lines
  }                           //
  ...

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

Теперь мы можем использовать его так:

$criteria = new CDbCriteria;
$criteria->joinreorder = true;
$criteria->with = array('product', 'shop');
$criteria->join = 'LEFT OUTER JOIN `shop2address` `s2a` ON (`shop`.`id` =  `s2a`.`shop_id`)';

Без joinreorder = true это генерирует ошибку, указывающую, что shop.id является неизвестным столбцом в предложении ON, потому что таблица 'shop' еще не добавлена ​​в оператор SQL. С joinreorder = true все работает как положено.

Что же касается случаев, когда используется только with и генерируется неверная последовательность соединений, то следует применять более сложный патч. Он включает в себя метод CJoinQuery::join. Возможно, он должен иметь необязательный параметр $priority, который можно снова заполнить через соответствующий элемент, добавленный в CDbCriteria. Затем в CJoinQuery::join измените эти строки:

$this->joins[$element->priority]=$element->getJoinCondition();
$this->joins[$element->priority]=$element->relation->join;

Это позволяет изменять порядок соединений произвольным образом в соответствии с заданными приоритетами.

person Stan    schedule 23.09.2013
comment
Замечательно. Я не тестировал ваше решение, но вы разместили его на форуме Yii? Это может быть включено в будущий выпуск. - person 111; 24.09.2013
comment
@glyph Возможно, я опубликую решение, когда оно будет завершено, что означает изменение порядка в пределах withs. - person Stan; 24.09.2013

Эта строка выглядит неправильно:

'tumorLibraryType'=>array(self::HAS_ONE,'LibraryTypes','','on'=>'tumor.library_type_id = tumorLibraryType.id'),

Может быть, это должно быть

'tumorLibraryType'=>array(self::HAS_ONE,'LibraryTypes',array('id'=>'library_type_id'),'through'=>'tumor'),
person jborch    schedule 09.07.2013
comment
Это мой обходной путь для неуникальной ошибки псевдонима таблицы, когда несколько отношений проходят через одно и то же отношение. - person 111; 11.07.2013
comment
Ok. Но вы можете указать псевдоним: yiiframework.com /doc/guide/1.1/ru/ - person jborch; 18.07.2013
comment
Это не решает проблему неуникального псевдонима таблицы, потому что создает дополнительное соединение с тем же псевдонимом или требует избыточных соединений и «сквозных» отношений, что замедляет работу. Это задокументированная проблема, и приведенное выше решение является наиболее эффективным. - person 111; 18.07.2013

ребята, кажется, я опаздываю на вечеринку

у меня была похожая проблема

У меня есть критерии слияния:

    $criteria = new CDbCriteria();

    $criteria->with = [
       'codebaseName' => [
          'alias' => 'cn'
       ],
       'codebaseProducer' => [
          'alias' => 'cp'
       ],
       'registrationDocumentLast' => [
          'alias' =>'rdl'
       ]

    ];

Это закончилось в таком порядке по утверждению:

ORDER BY COALESCE(cn.name_our,cn.name_supplier), id DESC LIMIT 50

Я не указал явно порядок по id DESC!

Поигравшись, я обнаружил, что это произошло из отношения RegistrationDocumentLast , которое было определено как

      'registrationDocumentLast' => [
          self::HAS_ONE, RegistrationDocument::class, 'codebase_product_pharm_id',
          'joinType' => 'LEFT JOIN',
          'order' => 'id DESC'
       ],

Посмотрите ключ заказа. Изменение его с

          'order' => 'id DESC'

to

          'order' => 't.id DESC'

решил проблему

person Tebe    schedule 03.10.2017