CakePHP4: объединенный, где на подключенной таблице не работает, как ожидалось - PullRequest
0 голосов
/ 10 ноября 2019

У меня есть следующий сценарий.

Программное обеспечение: Current CakePHP 4 Beta (Commit 48c3f80f8f)

SQL для таблиц

Позволяет изображению иметь 2 таблицы.

DROP TABLE IF EXISTS customers;
CREATE TABLE customers
(
    id             INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    company        VARCHAR(255),
    first_name     VARCHAR(255),
    last_name      VARCHAR(255),
    created        DATETIME DEFAULT CURRENT_TIMESTAMP,
    modified       DATETIME DEFAULT CURRENT_TIMESTAMP
) CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

DROP TABLE IF EXISTS customers_contacts;
CREATE TABLE customers_contacts
(
    id             INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    company        VARCHAR(255),
    first_name     VARCHAR(255),
    last_name      VARCHAR(255),
    customers_id   INT UNSIGNED NOT NULL,
    created        DATETIME DEFAULT CURRENT_TIMESTAMP,
    modified       DATETIME DEFAULT CURRENT_TIMESTAMP
) CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

И поэтому у нас есть связь один-ко-многим между клиентами и клиентами_контакты

// CustomersTable.php
$this->hasMany( 'CustomersContacts', [
  'foreignKey' => 'customers_id'
] );

// CustomersContactsTable.php
$this->belongsTo( 'Customers', [
  'foreignKey' => 'customers_id'
] );

Реализация поиска

Первым шагом было внедрение поиска, где я могуне только поиск значений одного столбца, но также и связанных столбцов, таких как «имя, фамилия» или «имя, фамилия»

Это работает

Я выполнил это с помощью следующего кода внутри моей индексной функции

$query = $this->Customers->find( 'all' )
                                  ->contain( ['CustomersContacts'] );
$current_request = $this->getRequest();

$search = $current_request->getQuery( 'search' );

// Default cake sort by clicking on table head
$sort_dir = $current_request->getQuery( 'direction' );
$sort_col = $current_request->getQuery( 'sort' );

if ( $current_request->is( 'get' ) ) {
  // Default sort by modified date
  if ( $sort_dir == '' && $sort_col == '' ) {
    $query->orderDesc( 'Customers.modified' );
  }

  // Change query if a search parameter is given in GET data
  if ( $search ) {

    // Search for concat fields in cakephp >=3
    // https://stackoverflow.com/questions/35405261/search-with-concat-fields-in-cakephp-3
    $query->where( function( $exp, $q ) use ( $search ) {
      /**
        * @var QueryExpression $exp
        * @var Query $q
        */

      // Concatenated Customers Name
      $conc1 = $q->func()
                  ->concat( [
                              'Customers.first_name' => 'literal',
                              ' ',
                              'Customers.last_name' => 'literal'
                            ] );
      $conc2 = $q->func()
                  ->concat( [
                              'Customers.last_name' => 'literal',
                              ' ',
                              'Customers.first_name' => 'literal'
                            ] );

      return $exp->or( [
                          'Customers.company LIKE' => "%$search%",
                          'Customers.first_name LIKE' => "%$search%",
                          'Customers.last_name LIKE' => "%$search%",
                        ] )
                  ->like( $conc1, "%$search%" )
                  ->like( $conc2, "%$search%" );
    } );

    $current_request = $current_request->withData( 'search', $search );
    $this->setRequest( $current_request );
  }
}

$Customers = $this->paginate( $query );

$this->set( compact( 'Customers' ) );

Это не работает

Тогда я подумал, что могу улучшить поиск, добавив также имя и фамилию таблицы контактов подключенных клиентов со следующим кодом:

$query = $this->Customers->find( 'all' )
                                  ->contain( ['CustomersContacts'] );
$current_request = $this->getRequest();

$search = $current_request->getQuery( 'search' );

// Default cake sort by clicking on table head
$sort_dir = $current_request->getQuery( 'direction' );
$sort_col = $current_request->getQuery( 'sort' );

if ( $current_request->is( 'get' ) ) {
  // Default sort by modified date
  if ( $sort_dir == '' && $sort_col == '' ) {
    $query->orderDesc( 'Customers.modified' );
  }

  // Change query if a search parameter is given in GET data
  if ( $search ) {

    // Search for concat fields in cakephp >=3
    // https://stackoverflow.com/questions/35405261/search-with-concat-fields-in-cakephp-3
    $query->where( function( $exp, $q ) use ( $search ) {
      /**
        * @var QueryExpression $exp
        * @var Query $q
        */

      // Concatenated Customers Name
      $conc1 = $q->func()
                  ->concat( [
                              'Customers.first_name' => 'literal',
                              ' ',
                              'Customers.last_name' => 'literal'
                            ] );
      $conc2 = $q->func()
                  ->concat( [
                              'Customers.last_name' => 'literal',
                              ' ',
                              'Customers.first_name' => 'literal'
                            ] );

      // Concatenated CustomersContacts Name
      $conc3 = $q->func()
                  ->concat( [
                              'CustomersContacts.first_name' => 'literal',
                              ' ',
                              'CustomersContacts.last_name' => 'literal'
                            ] );
      $conc4 = $q->func()
                  ->concat( [
                              'CustomersContacts.last_name' => 'literal',
                              ' ',
                              'CustomersContacts.first_name' => 'literal'
                            ] );

      return $exp->or( [
                          'Customers.company LIKE' => "%$search%",
                          'Customers.first_name LIKE' => "%$search%",
                          'Customers.last_name LIKE' => "%$search%",
                          'CustomersContacts.first_name LIKE' => "%$search%",
                          'CustomersContacts.last_name LIKE' => "%$search%"
                        ] )
                  ->like( $conc1, "%$search%" )
                  ->like( $conc2, "%$search%" )
                  ->like( $conc3, "%$search%" )
                  ->like( $conc4, "%$search%" );
    } );

    $current_request = $current_request->withData( 'search', $search );
    $this->setRequest( $current_request );
  }
}

$Customers = $this->paginate( $query );

$this->set( compact( 'Customers' ) );

Ошибка

Но теперь, когда я выполняю поиск, я получаю следующую ошибку:

Column not found: 1054 Unknown column 'CustomersContacts.first_name' in 'where clause'

Что я на самом деле не получаю, потому что соединение между клиентамии CustomersContacts должны были произойти с

->contain( ['CustomersContacts'] );

Но, похоже, это не так, потому что в Debug Kit я проверил SQL-запрос, и нет соединения между Contacts и ContactsCustomers.

Что я ожидаю

Таким образом, ожидаемый результат - показать всех клиентов, у которых есть связанные контакты с клиентами, где данный поисковый запрос имеет отношение к имени или фамилии этих контактов с клиентами.

Возможно ли это при таком подходе или мне нужно пойти другим путем?

Ответы [ 2 ]

0 голосов
/ 14 ноября 2019

Я наконец-то получил то, что искал с помощью этого кода:

// Had to remove ONLY_FULL_GROUP_BY from sql_mode so the distinct works
$query = $this->Customers->find( 'all' )
                         ->distinct( ['id'] )
                         ->contain( ['CustomersContacts'] );

// Check all the "matching" CustomersContacts if they have something in common with the given search parameter
// We have to use matching() instead of where() because we have a 1:N connection between Customers and CustomersContacts
$query->matching( 'CustomersContacts', function( $q ) use ( $search ) {
  /**
    * @var Query $q
    */
  return $q->where( function( $exp, $q ) use ( $search ) {
    /**
      * @var QueryExpression $exp
      * @var Query $q
      */

    $conc1 = $q->func()
                ->concat( [
                            'Customers.first_name' => 'literal',
                            ' ',
                            'Customers.last_name' => 'literal'
                          ] );
    $conc2 = $q->func()
                ->concat( [
                            'Customers.last_name' => 'literal',
                            ' ',
                            'Customers.first_name' => 'literal'
                          ] );

    // Concatenated CustomersContacts Name
    $conc3 = $q->func()
                ->concat( [
                            'CustomersContacts.first_name' => 'literal',
                            ' ',
                            'CustomersContacts.last_name' => 'literal'
                          ] );
    $conc4 = $q->func()
                ->concat( [
                            'CustomersContacts.last_name' => 'literal',
                            ' ',
                            'CustomersContacts.first_name' => 'literal'
                          ] );

    return $exp->or( [
                        'Customers.company LIKE' => "%$search%",
                        'Customers.first_name LIKE' => "%$search%",
                        'Customers.last_name LIKE' => "%$search%",
                        'Customers.city LIKE' => "%$search%",
                        'CustomersContacts.first_name LIKE' => "%$search%",
                        'CustomersContacts.last_name LIKE' => "%$search%"
                      ] )
                ->like( $conc1, "%$search%" )
                ->like( $conc2, "%$search%" )
                ->like( $conc3, "%$search%" )
                ->like( $conc4, "%$search%" );
  } );
} );

Еще раз спасибо Грегу Шмидту за уведомление о matching

0 голосов
/ 11 ноября 2019

Спасибо Грегу Шмидту в комментарии выше!

Я определенно забыл о разнице между contain и matching

Так что «решение» таково:

// Search for concat fields in cakephp >=3
// https://stackoverflow.com/questions/35405261/search-with-concat-fields-in-cakephp-3
$query->where( function( $exp, $q ) use ( $search ) {
  /**
    * @var QueryExpression $exp
    * @var Query $q
    */

  // Concatenated Customers Name
  $conc1 = $q->func()
              ->concat( [
                          'Customers.first_name' => 'literal',
                          ' ',
                          'Customers.last_name' => 'literal'
                        ] );
  $conc2 = $q->func()
              ->concat( [
                          'Customers.last_name' => 'literal',
                          ' ',
                          'Customers.first_name' => 'literal'
                        ] );

  return $exp->or( [
                      'Customers.company LIKE' => "%$search%",
                      'Customers.first_name LIKE' => "%$search%",
                      'Customers.last_name LIKE' => "%$search%",
                      'Customers.city LIKE' => "%$search%",
                    ] )
              ->like( $conc1, "%$search%" )
              ->like( $conc2, "%$search%" );
} );

// Redo the query since if we cant find anything in the Customers table
// See https://stackoverflow.com/questions/58783591/cakephp4-concatenated-where-on-connected-table-not-working-as-expected
// TODO: Remove this check and merge above query results with below query results (and probably mark them with something?)
if ( sizeof( $query->toArray() ) == 0 ) {

  $query = $this->Customers->find( 'all' )
                                    ->contain( ['CustomersEmails', 'CustomersContacts'] );

  if ( $sort_dir == '' && $sort_col == '' ) {
    $query->orderDesc( 'Customers.modified' );
  }

  // Check all the "matching" CustomersContacts if they have something in common with the given search parameter
  $query->matching( 'CustomersContacts', function( $q ) use ( $search ) {
    /**
      * @var Query $q
      */
    return $q->where( function( $exp, $q ) use ( $search ) {
      /**
        * @var QueryExpression $exp
        * @var Query $q
        */

      // Concatenated CustomersContacts Name
      $conc3 = $q->func()
                  ->concat( [
                              'CustomersContacts.first_name' => 'literal',
                              ' ',
                              'CustomersContacts.last_name' => 'literal'
                            ] );
      $conc4 = $q->func()
                  ->concat( [
                              'CustomersContacts.last_name' => 'literal',
                              ' ',
                              'CustomersContacts.first_name' => 'literal'
                            ] );

      return $exp->or( [
                          'CustomersContacts.first_name LIKE' => "%$search%",
                          'CustomersContacts.last_name LIKE' => "%$search%"
                        ] )
                  ->like( $conc3, "%$search%" )
                  ->like( $conc4, "%$search%" );
    } );
  } );

}

Как видите, я выполняю запрос matching, только если у меня нет результатов в базовом запросе.

Но что мне действительно нужно, так это сочетание обоих результатов запроса.

Я определенно могу сделать это, просто выполняя оба запроса один за другим и объединяя оба результата запроса с array_merge, но это мешает мне использовать функцию paginate из CakePHP, поскольку она требует объект Query.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...