Usando OR com EntityFieldQuery

26

Nunca tive a necessidade de fazer isso antes de hoje, mas não parece que você possa fazer consultas OR EntityFieldQuery, pois db_oré usado para consultas selecionadas.

Um exemplo chegaria a todas as entidades que possuem um campo de data em que o valor é nulo ou depois de hoje.

Estou faltando alguma coisa ou algum truque ou isso simplesmente não é suportado?

googletorp
fonte
Você também pode dividir uma consulta em duas, executá-las e ingressar nos resultados.
Vadym Myrgorod
O impacto no desempenho disso é bastante horrível se as consultas ou a quantidade de dados forem remotamente maiores.
Tommi Forsström
11
Isso é antigo, mas está alto nos resultados do google - observe que você pode usar o orConditionGroup no Drupal 8.
ognockocaten

Respostas:

22

Eu já vi uma solução para esse problema . A idéia é usar addTag()na consulta e na implementação hook_query_TAG_alter(), onde você tem um bom SelectQueryobjeto antigo .

Michael
fonte
Eu proponho selecionar isso como a resposta certa. A postagem do blog fornece um método para adicionar condicionalidade OR ao EntityFieldQueries. O único problema é que você cria a dependência do SQL com esse método, o que é meio que contrário ao ponto principal dos EFQs, mas pelo menos realiza o trabalho. Obrigado pelo bom link @ Michael.
Tommi Forsström
2
Como essa é uma resposta da comunidade, e a maior parte consiste em um link externo, acho que o código, ou pelo menos parte do conteúdo do artigo, deve ser incluído nessa resposta. Porque os links morrem. Discussão sobre Meta StackExchange sobre este tópico
D. Visser
O artigo original é bastante longo e a ideia pode ser resumida como "use addTag () na consulta e implemente hook_query_TAG_alter ()". Depois disso, a pergunta foi reduzida para "Como usar o OR com o objeto SelectQuery", que é um assunto conhecido.
Michael Jackson
Eu recomendo fortemente converter o EFQ em uma consulta de seleção regular neste cenário. Ficar com o EFQ e usar alterações baseadas em tags para mexer com o que ele produz é horrível em comparação.
phils 9/09
12

Você pode sublassar EntityFieldQuerye substituir alguns métodos.

As condições adicionadas a um objeto da classe EntityFieldQuery(por exemplo, uma condição de propriedade) são adicionadas a uma matriz.

  public function propertyCondition($column, $value, $operator = NULL) {
    // The '!=' operator is deprecated in favour of the '<>' operator since the
    // latter is ANSI SQL compatible.
    if ($operator == '!=') {
      $operator = '<>';
    }
    $this->propertyConditions[] = array(
      'column' => $column, 
      'value' => $value, 
      'operator' => $operator,
    );
    return $this;
  }

Quando a consulta é criada, essa matriz é usada em um loop semelhante ao seguinte (o código está presente em EntityFieldQuery :: propertyQuery () ):

foreach ($this->propertyConditions as $property_condition) {
  $this->addCondition($select_query, "$base_table." . $property_condition['column'], $property_condition);
}

$select_querycontém o valor retornado de uma chamada para db_select().

kiamlaluno
fonte
5

Você não pode ter medo, as RUPs não são suportadas nativamente pelo EntityFieldQuery classe.

Uma maneira alternativa pode ser adicionar uma tag à consulta com ->addTag() , e implementarhook_query_TAG_alter() para alterar a estrutura interna da consulta manualmente para consultas que contenham essa tag.

Fazendo isso, você poderá percorrer as condições existentes e fazer as alterações necessárias para adicionar sua ORlógica. Não é uma maneira bonita de fazer isso; você pode encontrar um exemplo aqui .

Clive
fonte
5

Não há necessidade de dividir as consultas em 2 e mesclar ou algo assim. Só preciso alterar a consulta

Considere o cenário: eu tinha 2 tipos de entidade com nomes de máquinas: instruções tincan e tincan_agents

5 campos de referência da entidade na entidade

4 deles são campos regulares de referência a entidades e o quinto (tincan_object) é um campo de referência com várias entidades, cada campo de referência faz referência a entidades do tipo 'Agente'.

O campo de referência tincan_object pode fazer referência a agentes e atividades (um terceiro tipo de entidade). Um agente tem uma propriedade object_type, que pode ser agente ou grupo.

Quero encontrar qualquer declaração que faça referência a um dos vários agentes possíveis, em qualquer um dos campos de referência. Precisamos de um operador OR entre as fieldConditions, mas também precisamos verificar o object_type do campo de referência do tipo multi-entidade e garantir que seja uma das duas possibilidades.

O código abaixo representa o mais simples possível, em nossa solução a consulta tinha muitas outras condições, campos, etc ... então o código precisava não contar com a ordem das condições, ou mesmo se todos esses campos estivessem sendo consultados.

    $query = new EntityFieldQuery();
    $query->entityCondition('entity_type', 'tincan_statement');

    $all_agents = array(4,10); //entity_ids to search for
    $query->addTag('tincan_statement_get_agents');
    $query->fieldCondition('tincan_actor', 'target_id', $all_agents, 'IN'); 
    //need OR between fields conditions
    $query->fieldCondition('tincan_authority', 'target_id', $all_agents, 'IN');
//need OR between fields conditions
    $query->fieldCondition('tincan_instructor', 'target_id', $all_agents, 'IN');
//need OR between fields conditions
    $query->fieldCondition('tincan_team', 'target_id', $all_agents, 'IN');
//need OR between fields conditions
//but then nested in the OR structure we need an AND for two columns of the multientity type reference field tincan_object
    $query->fieldCondition('tincan_object', 'target_id', $all_agents, 'IN');
    $query->fieldCondition('tincan_object', 'object_type', array('Agent', 'Group'), 'IN');
    $results = $query->$execute();

Solução: Aviso no EntityFieldQuery acima

 $query->addTag('tincan_statement_get_agents');

Isso marca a consulta, permitindo a implementação de hook_query_TAG_alter ()

/**
 * Implements hook_query_TAG_alter()
 * alters the query for finding agents with or without the related_agents flag
 * used for Statement API Get processor EntityFieldQuery
 */
function tincan_lrs_query_tincan_statement_get_agents_alter(QueryAlterableInterface $query) {
  //need to or the search for all the fields (actor, object, authority, instructor, team)
  // the object_type of the object field needs to be Agent OR Group

  $conditions =& $query->conditions();
  // dsm($conditions);  //dsm() is your friend! comes with devel module
  $agent_grouping_condition = db_or(); 
  $object_parameters = array();
  $x = 0;
  foreach ($conditions as $key => $condition) {
    if (is_numeric($key) && isset($condition['field']) && is_scalar($condition['field'])) {
      if ( (strpos($condition['field'], 'tincan_object_object_type') !== FALSE  ||
          strpos($condition['field'], 'tincan_object_target_id') !== FALSE ) && $condition['operator'] == 'IN') {
  //u
            unset($conditions[$key]);
            $object_parameters[$x]['field'] = $condition['field'];
            $object_parameters[$x]['value'] = $condition['value'];
            $object_parameters[$x]['operator'] = $condition['operator'];
            $x += 1;
          }

       if(strpos($condition['field'], 'tincan_actor_target_id') !== FALSE ||
          strpos($condition['field'], 'tincan_instructor_target_id') !== FALSE ||
          strpos($condition['field'], 'tincan_team_target_id') !== FALSE ||
          strpos($condition['field'], 'tincan_authority_target_id') !== FALSE ) {
            unset($conditions[$key]);
            $agent_grouping_condition->condition($condition['field'], $condition['value'], $condition['operator']);

      } 
    }
  }

  // create new AND condition to nest in our OR condition set for the object parameters
  $object_condition = db_and();
  foreach($object_parameters as $key => $param) {
    $object_condition->condition($param['field'], $param['value'], $param['operator']);
  }

  $agent_grouping_condition->condition($object_condition);

  $query->condition($agent_grouping_condition);

  //By default EntityFieldQuery uses inner joins, change to left
  $tables =& $query->getTables();

  foreach($tables as $key => $table) {
    if (strpos($key, 'field_data_tincan_object') !== FALSE ||
        strpos($key, 'field_data_tincan_actor') !== FALSE ||
        strpos($key, 'field_data_tincan_authority') !== FALSE ||
        strpos($key, 'field_data_tincan_instructor') !== FALSE ||
        strpos($key, 'field_data_tincan_team') !== FALSE ) {
          if(!is_null($table['join type'])) {
            $tables[$key]['join type'] = 'LEFT';
          }
    }
  }

}
jackrabbithanna
fonte
2

O OP deseja consultar entidades com data nula OU maior que x, eu queria consultar nós sem idioma definido OU o idioma do usuário. addTag()é a melhor solução para adicionar uma instrução OR real, mas seria um exagero no meu caso. Meu OR muito simples pode ser realizado consultando a propriedade language em uma matriz usando:

$query->propertyCondition('language', array($GLOBALS['language']->language, LANGUAGE_NONE), 'IN');
lmeurs
fonte