Comment utiliser «NOT IN» dans une requête?

26

Quelle est la bonne façon d'écrire une requête contenant «NOT IN» à l'aide d'une déclaration de condition?

Ma requête est la suivante:

SELECT DISTINCT nid FROM node WHERE language NOT IN 
  (SELECT language 
    FROM languages WHERE language = 'ab');

J'ai essayé quelque chose comme ceci:

$query->condition('n.' . $key, $value, 'not in (select language from 
  languages where language = $value)');
JurgenR
la source
Peut-être que je manque l'évidence, mais quelle est la différence entre votre requête et SELECT nid FROM node WHERE language != 'ab'?
Елин Й.

Réponses:

38

Dans l'exemple spécifique, vous devez simplement écrire la condition comme suit:

$query->condition('n.language', 'ab', '<>');

Dans le cas générique, où vous devez sélectionner les lignes d'une base de données en fonction des valeurs renvoyées par une sous-requête, vous devez considérer ce qui suit:

  • "NOT IN" est accepté comme opérateur à partir de SelectQuery::condition(). En fait, la requête suivante serait exécutée:

    $query = db_select('node', 'n')->fields('n');
    $query->condition('n.nid', array(1, 2, 3), 'NOT IN');
    $nodes = $query->execute();
    
    foreach ($nodes as $node) {
      dsm($node->nid);
    }
    
  • Comme indiqué dans les clauses conditionnelles ("Sous-sélections"), SelectQuery::condition()accepte également un objet implémentant SelectQueryInterfacecomme valeur pour $value, tel que celui renvoyé par db_select(); le problème est qu'en fait, vous pouvez simplement l'utiliser lorsque la valeur de $operatorest égale à "IN". Voir Les sous-sélections ne fonctionnent pas dans des conditions DBTNG, sauf lorsqu'elles sont utilisées comme valeur pour IN .

La seule façon que je peux voir d'utiliser l'opérateur "NOT IN" avec une sous-requête dans conditionest de:

  • Exécutez la sous-requête pour obtenir un tableau
  • Exécutez la requête principale en définissant la condition comme dans l'extrait de code suivant

    $query->condition($key, $subquery_result, 'NOT IN');

    $subquery_result est le tableau contenant le résultat de la sous-requête.

Sinon, vous pourriez utiliser where()comme d'autres l'ont dit, qui accepte une chaîne pour la partie de la requête que vous devez ajouter.

Gardez à l'esprit que db_select()c'est plus lent que cela db_query(); vous devez utiliser le premier lorsque vous savez que la requête peut être modifiée par d'autres modules. Sinon, si d'autres modules ne sont pas censés utiliser hook_query_alter()pour modifier votre requête, vous devez utiliser db_query().
Dans le cas de l'accès aux nœuds, si vous devez obtenir uniquement les nœuds auxquels un utilisateur a accès, vous devez utiliser db_select()et ajouter 'node_access'comme balise de la requête, avec SelectQuery::addTag(). Par exemple, blog_page_last()utilise le code suivant.

  $query = db_select('node', 'n')->extend('PagerDefault');
  $nids = $query
  ->fields('n', array('nid', 'sticky', 'created'))
    ->condition('type', 'blog')
    ->condition('status', 1)
    ->orderBy('sticky', 'DESC')
    ->orderBy('created', 'DESC')
    ->limit(variable_get('default_nodes_main', 10))
    ->addTag('node_access')
    ->execute()
    ->fetchCol();

Un code similaire est utilisé par book_block_view().

$select = db_select('node', 'n')
  ->fields('n', array('title'))
  ->condition('n.nid', $node->book['bid'])
  ->addTag('node_access');
$title = $select->execute()->fetchField();
kiamlaluno
la source
Voici un exemple de sous-requête pour un filtre personnalisé de vues que j'ai écrit: lien
Roger
1
«Les sous-sélections ne fonctionnent pas dans des conditions DBTNG, sauf lorsqu'elles sont utilisées comme valeur pour IN» est corrigé dans Drupal 8.3
Jonathan
3

Lors de l'écriture de requêtes complexes, vous devez absolument utiliser à la db_query()place de db_select().

  1. Vous ne pouvez pas écrire une NOT INclause avec une sous-requête avec l'API de base de données Drupal actuelle (c'est un problème connu en cours d'élaboration).
  2. Si vous n'avez pas besoin que votre requête soit dynamique (donc réécrite par d'autres modules), ne vous embêtez pas à écrire une requête aussi complexe avec db_select().
  3. Les sous-requêtes ne sont pas encore bien prises en charge (voir une réponse précédente ) et si vous êtes habitué à écrire SQL, c'est beaucoup plus facile à utiliser db_query().

Concernant votre requête, je ne sais pas pourquoi vous souhaitez utiliser une sous-requête (sauf si vous avez simplifié votre exemple)? Vous pouvez l'écrire facilement comme ceci:

SELECT nid 
FROM node n INNER JOIN languages l ON n.language = l.language
WHERE language NOT IN ('ab')

DISTINCTn'est pas nécessaire, tout comme nidune clé primaire, elle ne sera donc pas dupliquée.

tostinni
la source
2
Concernant # 2, l'OP sélectionne des nœuds. AFAIK db_select () est le seul moyen de fournir une balise 'node_access' requise, auquel cas db_select () serait le seul choix.
keithm
2

Il y a aussi where () qui permet d'ajouter une condition arbitraire where à la requête.

Exemple:

$query->where('n.language NOT IN (SELECT language FROMlanguages WHERE language = :lang)', array(':lang' => $value));

Comme Keithm l'a mentionné, vous devez utiliser db_select () et addTag ('node_access') lors de la sélection des nœuds qui sont ensuite affichés aux utilisateurs.

Berdir
la source
1

Un moyen plus simple d'utiliser db_select avec une sous-sélection NOT IN consiste simplement à utiliser le peu connu

$ query-> où

pour ajouter une condition arbitraire where.

par exemple:

  // Count query for users without rid 3
  $query = db_select('users', 'u');
  $query->fields('u', array('uid'));
  $query->where('u.uid NOT IN(select uid from {users_roles} where rid = :rid)', array(':rid' => 3));  
  $count = $query->countQuery()->execute()->fetchField();
  drupal_set_message($count);
David Thomas
la source
0

Où $ subquery_values ​​est un tableau de format $ key => $ nid résultant d'une sous-requête

$query->condition('node.nid', array_values($subquery_values), "NOT IN");

ça fonctionne bien.

Riccardo Ravaro
la source