Come si usa "NON IN" in una query?


26

Qual è il modo corretto di scrivere una query contenente 'NOT IN' usando un'istruzione condition?

La mia query è la seguente:

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

Ho provato qualcosa di simile al seguente:

$query->condition('n.' . $key, $value, 'not in (select language from 
  languages where language = $value)');

Forse mi manca l'ovvio, ma qual è la differenza tra la tua domanda e SELECT nid FROM node WHERE language != 'ab'?
Елин Й.

Risposte:


38

Nell'esempio specifico, devi semplicemente scrivere la condizione come:

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

Nel caso generico, in cui è necessario selezionare le righe in un database in base ai valori restituiti da una query secondaria, è necessario considerare quanto segue:

  • "NOT IN" è accettato come operatore da SelectQuery::condition(). In effetti, verrebbe eseguita la seguente query:

    $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);
    }
    
  • Come riportato nelle clausole condizionali ("Sottoselezioni"), SelectQuery::condition()accetta anche un oggetto implementato SelectQueryInterfacecome valore per $value, come quello restituito da db_select(); il problema è che in realtà puoi semplicemente usarlo quando il valore di $operatorè uguale a "IN". Vedi Sottoselezioni non funzionano in condizioni DBTNG, tranne quando usato come valore per IN .

L'unico modo in cui riesco a vedere l'operatore "NOT IN" con una query secondaria in conditionè di:

  • Eseguire la subquery per ottenere un array
  • Esegui la query principale impostando la condizione come nel frammento seguente

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

    $subquery_result è l'array che contiene il risultato della query secondaria.

Altrimenti, potresti usare where()come altri hanno detto, che accetta una stringa per la parte della query che devi aggiungere.

Tieni presente che db_select()è più lento db_query(); dovresti usare il primo quando sai che la query potrebbe essere modificata da altri moduli. Altrimenti, se non si prevede che altri moduli utilizzino hook_query_alter()per modificare la query, è necessario utilizzare db_query().
Nel caso di accesso a nodi, se è necessario ottenere solo i nodi a cui un utente ha accesso, è necessario utilizzare db_select()e aggiungere 'node_access'come tag della query, con SelectQuery::addTag(). Ad esempio, blog_page_last()utilizza il seguente codice.

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

Codice simile è utilizzato da 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();

Ecco un esempio di una sottoquery per un filtro personalizzato delle viste che ho scritto: link
Roger

1
"Le sottoselezioni non funzionano nelle condizioni DBTNG, tranne quando utilizzate come valore per IN" è risolto in Drupal 8.3
Jonathan

3

Quando si scrivono query complesse, è necessario utilizzare al db_query()posto di db_select().

  1. Non è possibile scrivere una NOT INclausola con una sottoquery con l'attuale API del database Drupal (è un problema noto in fase di elaborazione).
  2. Se non hai bisogno che la tua query sia dinamica (quindi riscritta da altri moduli), non preoccuparti di provare a scriverne una così complessa db_select().
  3. Le subquery non sono ancora ben supportate (vedi una mia precedente risposta ) e se sei abituato a scrivere SQL è molto più facile da usare db_query().

Per quanto riguarda la tua query, non sono sicuro del motivo per cui desideri utilizzare una subquery (a meno che non abbia semplificato il tuo esempio)? Puoi scriverlo facilmente in questo modo:

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

DISTINCTnon è necessario in quanto nidè una chiave primaria, quindi non verrà duplicata.


2
Per quanto riguarda il n. 2, l'OP sta selezionando i nodi. AFAIK db_select () è l'unico modo per fornire qualsiasi tag 'node_access' richiesto, nel qual caso db_select () sarebbe l'unica scelta.
keithm

2

C'è anche where () che consente di aggiungere una condizione arbitraria alla query.

Esempio:

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

Come indicato da keithm, è necessario utilizzare db_select () e addTag ('node_access') quando si selezionano i nodi che vengono quindi visualizzati agli utenti.


1

Un modo più semplice di usare db_select con una sottoselezione NOT IN è solo quello di usare il poco conosciuto

$ Query-> dove

per aggiungere una condizione arbitraria dove.

per esempio:

  // 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);

0

Dove $ subquery_values ​​è un array di $ key => $ nid come risultato di una sottoquery

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

funziona benissimo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.