Как изменить ассоциации модели CakePHP 2 на глубокую связь с bindModel и пользовательским поиском?

Я пытаюсь воспроизвести этот результат SQL-запроса, который работает:

SELECT r.id, r.day, s.ddbroute_id, s.delivery_id, d.id, d.laststatusid, t.id, t.delivery_id, t.statusstage_id, st.id, st.stage
FROM ddbroutes r
LEFT JOIN ddbrouteslots s on r.id = s.ddbroute_id
LEFT JOIN deliveries d on s.delivery_id = d.id
LEFT JOIN trackingstatuses t on d.laststatusid = t.id 
LEFT JOIN statusstages st on t.statusstage_id = st.id

Я использую модели CakePHP 2 с

  1. bindModel для изменения ассоциаций модели на лету
  2. custom Find поместите логику в модель

Общего поля из нижней таблицы за пределами второго уровня нет. Сообщение об ошибке: "Модель "Ddbroute" не связана с моделью "Доставка"." Поэтому я пробовал его с доставкой и без нее в массиве «содержать», и никоим образом не вводил поля «Доставка». Я был бы рад использовать соединения, если это уместно. Я прочитал самые релевантные сообщения на StackOverflow, которые смог найти.

Мой код с дополнительной информацией приведен ниже. Будем признательны за любую помощь.


У меня есть пять таблиц (включая следующие поля):

ddbroutes (id, day)
ddbrouteslots (id, ddbroute_id, delivery_id)
deliveries (id, laststatusid)
trackingstatuses (id, statusstage_id)
statusstages (id, stage)

В моделях установлены следующие отношения:

Ddbroute hasMany Ddbrouteslot (Ddbrouteslot belongsTo Ddbroute)
Delivery hasOne Ddbrouteslot (Ddbrouteslot belongsTo Delivery)
Delivery hasMany Trackingstatus (Trackingstatus belongsTo Delivery)
Statusstage hasMany Trackingstatus (Trackingstatus belongsTo Statusstage)

Несмотря на то, что у доставки есть один Ddbrouteslot (и это будет hasMany — исправлено — теперь остается hasOne), для каждого отдельного Ddbroute существует только одна доставка, связанная с каждым Ddbrouteslot. Контейнер настроен во всех моделях. Я не знал, нужно ли мне сначала использовать unbindModel (это не изменило сообщение об ошибке).

Мой код в файле модели Ddbroute.php (только до таблицы доставки)

public $findMethods = array('ddbstatuses' => true);

  protected function _findDdbstatuses($state, $query, $results = array()) {
      if ($state === 'before') {
        $ddbrouteslotmodel = ClassRegistry::init('Ddbrouteslot');
        $ddbrouteslotmodel->unbindModel(
          array('belongsTo' => array('Delivery'))
        );
        $ddbrouteslotmodel->bindModel(
          array('hasOne' => array(
            'Delivery' => array(
              'className' => 'Delivery',
              'foreignKey' => 'id',
              'dependent' => false,
              'fields' => array(
                'id', 'laststatusid'
              )
              )
            ))
          );

          $deliverymodel = ClassRegistry::init('Delivery');
          $deliverymodel->unbindModel(
            array('hasOne' => array('Ddbrouteslot'))
          );
          $deliverymodel->bindModel(
            array('belongsTo' => array(
              'Delivery' => array(
                'className' => 'Delivery',
                'foreignKey' => 'delivery_id'
                )
              )
            )
          );

          $query['contain'] = array(
            'Ddbrouteslot', 'Delivery'
          );
        return $query;
      }
        return $results;
    }

В другом контроллере для запуска операции поиска:

$this->LoadModel('Ddbroute');
$ddbstatuses = $this->Ddbroute->find('ddbstatuses');
$this->set(compact('ddbstatuses')); // to make available in a view

Я также предпринял еще одну попытку с длинным массивом соединения, но запрос не принес никакой информации о доставке, статусе отслеживания или статусе, хотя запрос, похоже, был выполнен.

  public $findMethods = array('ddbstatuses' => true);

  protected function _findDdbstatuses($state, $query, $results = array()) {
  if ($state === 'before') {

    ClassRegistry::init('Delivery'); // not sure these three lines were needed so I tried with and without them
    ClassRegistry::init('Trackingstatus');
    ClassRegistry::init('Statusstage');



    $query['joins'] = array(
      array(
        'table' => 'ddbrouteslots',
        'alias' => 'Ddbrouteslot',
        'type' => 'LEFT',
        'conditions' => array(
            'Ddbroute.id = Ddbrouteslot.ddbroute_id'
      )),
      array(
        'table' => 'deliveries',
        'alias' => 'Delivery',
        'type' => 'LEFT',
        'conditions' => array(
          'Ddbrouteslot.id = Delivery.id'
      )),
      array(
        'table' => 'trackingstatuses',
        'alias' => 'Trackingstatus',
        'type' => 'LEFT',
        'conditions' => array(
          'Delivery.laststatusid = Trackingstatus.id'
      )),
      array(
        'table' => 'statusstages',
        'alias' => 'Statusstage',
        'type' => 'LEFT',
        'conditions' => array(
          'Trackingstatus.statusstage_id = Statusstage.id'
      ))
  );


    $query['contain'] = array(
        'Ddbrouteslot',
          'Delivery',  // Not sure I should be adding these other models, so I tried with and without them
          'Trackingstatus',
          'Statusstage'
      );
    return $query;
  }
  return $results;
}

person nicholasvad    schedule 01.01.2017    source источник
comment
Теперь у меня есть решение, но запрос с использованием моделей CakePHP выполняется значительно медленнее, чем решение SQL (2.) ниже. Поле «Delivery.laststatusid» может ссылаться на «Trackingstatus.id», тогда ограничение заказа не потребуется. Я пробовал это с unbindModel и bindModel, пытаясь установить ассоциации hasOne и ownTo. Поскольку первичный ключ Delivery.id не использовался, мой синтаксис включал 'foreignKey' => false, 'conditions' => array(' Delivery.laststatusid` = Trackingstatus.id ' )`. Я получил сообщение об ошибке, что laststatusid был неизвестным столбцом. Есть идеи, как ускорить работу?   -  person nicholasvad    schedule 12.02.2017


Ответы (1)


После некоторой помощи у меня теперь есть четыре решения для получения моих данных, хотя на самом деле три из них являются вариантами первого. Я относительно неопытен, и были некоторые основные вещи, которые я не ценил.

<сильный>1. В КОНТРОЛЛЕРЕ

$this->LoadModel("Ddbrouteslot");
$res = $this->Ddbrouteslot->find("all", array(
  "conditions" => array(
    "Ddbrouteslot.delivery_id > 0",
    "Ddbrouteslot.ddbroute_id" => 45
),
"contain" => array(
    "Ddbroute",
    "Delivery" => array(
"Trackingstatus" => array(
   "order" => array(
   "Trackingstatus.id" => "desc"
    ),
    "limit" => 1,
    "Statusstage"
   )
  )
 )
);

Тайминги от DebugKit: основной запрос был 20 мс; Trackingstatus и Statusstage были дополнительными запросами по 18 мс каждый x 4 для четырех связанных доставок; общее время составило 164 мс. Это довольно медленно, что не идеально.

Это началось со второй модели, Ddbrouteslot, потому что она имела прямое отношение как к Ddbroute, так и к Delivery. Никаких изменений ни в одной из ассоциаций не произошло. Взаимосвязь ownTo между Ddbrouteslot и Delivery работала нормально. Между Delivery и Trackingstatus для delivery_id уже существовала связь hasMany.


<сильный>2. ИСПОЛЬЗОВАНИЕ SQL

$this->LoadModel("Ddbroute");
$qres = $this->Ddbroute->query(
    "SELECT *
    FROM 
    ddbroutes AS r
    LEFT JOIN ddbrouteslots s on r.id = s.ddbroute_id
    LEFT JOIN deliveries d on s.delivery_id = d.id
    LEFT JOIN trackingstatuses t on d.laststatusid = t.id 
    LEFT JOIN statusstages st on t.statusstage_id = st.id
    WHERE s.delivery_id > 0 AND s.ddbroute_id = 45
;"
debug($qres);

Тайминги: это заняло 19 мс. Это означает, что это было намного быстрее. Это не рекомендуется в документации Cake, и очевидно, что он не так переносим между базами данных, как чистый поиск Cake.


<сильный>3. ИЗМЕНЕНИЕ БАЗОВОЙ МОДЕЛИ

$rres = $this->Ddbroute->find("all", array(
    "conditions" => array(
    "Ddbroute.id" => 45
),
"recursive" => -1,
"contain" => array(

        "Ddbrouteslot" => array(
            "conditions" => array(
                "Ddbrouteslot.delivery_id > 0"
            ),
            "Delivery" => array(
                "Trackingstatus" => array(
                    "order" => array(
                        "Trackingstatus.id" => "desc"
                    ),
                    "limit" => 1,
                    "Statusstage"
                )
            )
        )
    )
));
debug($rres);

Тайминги: основной запрос был 18 мс; Доставка, Статус отслеживания и Статус статуса составляли 18 мс каждый x 4 для четырех связанных доставок; общее время составило 234 мс. Это было медленнее, потому что Delivery нужно было запускать для каждой отправки, потому что это не входило в модель Ddbroute. Изменение рекурсии не имело значения.


<сильный>4. ИСПОЛЬЗОВАНИЕ ПОЛЬЗОВАТЕЛЬСКОГО ПОИСКА Это был тот же запрос, что и 1.) выше, но только с пользовательским методом поиска.

public $findMethods = array('ddbstatuses' => true);   
protected function _findDdbstatuses($state, $query, $results = array()) {
    if ($state === 'before') {       
        $query['conditions'] = array(
          "Ddbrouteslot.delivery_id > 0",
          "Ddbrouteslot.ddbroute_id" => 45
        );
        $query['contain'] = array(
          "Ddbroute",
          "Delivery"=> array(
             "Trackingstatus" => array(
                "order" => array(
                "Trackingstatus.id" => "desc"
             ),
             "limit" => 1,
                "Statusstage"
              )
           )
          );
         return $query;
      }   
   return $results;
}  
person nicholasvad    schedule 11.02.2017