Utilisation de OR avec EntityFieldQuery

26

Je n'ai jamais eu besoin de le faire avant aujourd'hui, mais il ne semble pas que vous puissiez faire des requêtes OR avec EntityFieldQuery, car il db_orest utilisé pour certaines requêtes.

Un exemple serait d'accéder à toutes les entités qui ont un champ de date où la valeur est nulle ou après aujourd'hui.

Suis-je en train de manquer quelque chose ou une astuce ou est-ce simplement non pris en charge?

googletorp
la source
Vous pouvez également diviser une requête en deux, les exécuter, puis joindre les résultats.
Vadym Myrgorod
L'impact sur les performances est assez horrible si les requêtes ou la quantité de données sont encore plus grandes à distance.
Tommi Forsström
1
Ceci est ancien mais en haut de mes résultats Google - il convient de noter que vous pouvez utiliser le orConditionGroup pour cela dans Drupal 8.
ognockocaten

Réponses:

22

J'ai vu une solution à ce problème . L'idée est d'utiliser addTag()dans la requête et l'implémentation hook_query_TAG_alter(), où vous avez un bon vieil SelectQueryobjet.

Michael
la source
Je proposerais de sélectionner ceci comme la bonne réponse. Le billet de blog fournit une méthode pour ajouter la conditionnalité OR à EntityFieldQueries. Le seul problème est que vous construisez réellement une dépendance SQL avec cette méthode, ce qui est en quelque sorte contre le point de vue des EFQ, mais au moins fait le travail. Merci pour le bon lien @Michael.
Tommi Forsström
2
Comme il s'agit d'une réponse de la communauté, et la plupart consiste en un lien externe, je pense que le code, ou au moins une partie du contenu de l'article, devrait être inclus dans cette réponse. Parce que les liens meurent. Discussion Meta StackExchange sur ce sujet
D. Visser
L'article d'origine est assez long et l'idée peut être résumée comme "utiliser addTag () dans la requête et implémenter hook_query_TAG_alter ()". Après cela, la question a été réduite à "Comment utiliser OR avec l'objet SelectQuery" qui est un sujet connu.
Michael
Je recommanderais fortement de convertir l'EFQ en une requête de sélection régulière dans ce scénario. Rester fidèle à l'EFQ et utiliser des modifications basées sur des balises pour jouer avec ce qu'il produit est horrible en comparaison.
phils
12

Vous pouvez sous-classer EntityFieldQueryet remplacer certaines méthodes.

Les conditions qui sont ajoutées à un objet de classe EntityFieldQuery(par exemple une condition de propriété) sont ajoutées à un tableau.

  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;
  }

Lorsque la requête est générée, ce tableau est ensuite utilisé dans une boucle similaire à la suivante (le code est présent dans EntityFieldQuery :: propertyQuery () ):

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

$select_querycontient la valeur renvoyée par un appel à db_select().

kiamlaluno
la source
5

Vous ne pouvez pas, je le crains, les salles d'opération ne sont pas prises en charge nativement par la EntityFieldQueryclasse.

Une solution consiste à ajouter une balise à la requête avec avec ->addTag(), puis à implémenter hook_query_TAG_alter()pour modifier manuellement la structure interne de la requête pour les requêtes contenant cette balise.

En faisant cela, vous pourrez parcourir les conditions existantes et apporter les modifications nécessaires pour ajouter votre ORlogique. Ce n'est pas une jolie façon de le faire cependant; vous pouvez trouver un exemple ici .

Clive
la source
5

Pas besoin de diviser les requêtes en 2 et de fusionner ou quelque chose comme ça. Il suffit de modifier la requête

Considérez le scénario: j'avais 2 types d'entité avec des noms de machine: les instructions tincan et tincan_agents

5 champs de référence d'entité sur l'entité

4 d'entre eux sont des champs de référence d'entité réguliers et le 5ème (tincan_object) est un champ de référence de type multi-entités, chaque champ de référence fait référence à des entités de type 'Agent'.

Le champ de référence tincan_object peut référencer des agents et des activités (un troisième type d'entité). Un agent possède une propriété object_type, qui peut être un agent ou un groupe.

Je souhaite trouver toute déclaration faisant référence à l'un des agents possibles, dans l'un des champs de référence. Nous avons besoin d'un opérateur OU entre les conditions de champ, mais nous devons également vérifier le type d'objet du champ de référence de type multi-entités et nous assurer qu'il s'agit de l'une des deux possibilités.

Le code ci-dessous représente le plus simple possible, dans notre solution la requête avait beaucoup d'autres conditions, champs, etc ... donc le code devait ne pas compter sur l'ordre des conditions, ou même si tous ces champs étaient interrogés.

    $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();

Solution: remarquez dans l'EntityFieldQuery ci-dessus

 $query->addTag('tincan_statement_get_agents');

Cette balise la requête, permettant l'implémentation 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
la source
2

L'OP veut interroger des entités avec une date nulle OU supérieure à x, je voulais interroger des nœuds sans langue définie OU la langue de l'utilisateur. addTag()est la meilleure solution pour ajouter une instruction OR réelle, mais serait exagéré dans mon cas. Mon OU très simple peut être accompli en recherchant la propriété du langage dans un tableau en utilisant:

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