Jak użyć „NOT IN” w zapytaniu?

26

Jaki jest właściwy sposób napisania zapytania zawierającego „NOT IN” przy użyciu instrukcji warunku?

Moje zapytanie jest następujące:

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

Próbowałem czegoś takiego:

$query->condition('n.' . $key, $value, 'not in (select language from 
  languages where language = $value)');
JurgenR
źródło
Może brakuje mi oczywistości, ale jaka jest różnica między twoim zapytaniem a SELECT nid FROM node WHERE language != 'ab'?
Елин Й.

Odpowiedzi:

38

W konkretnym przykładzie powinieneś po prostu napisać warunek jako:

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

W ogólnym przypadku, w którym należy wybrać wiersze w bazie danych na podstawie wartości zwróconych z zapytania częściowego, należy rozważyć następujące kwestie:

  • „NOT IN” jest akceptowane jako operator od SelectQuery::condition(). W rzeczywistości zostanie wykonane następujące zapytanie:

    $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);
    }
    
  • Jak podano w klauzulach warunkowych („Subselects”), SelectQuery::condition()akceptuje również obiekt implementujący SelectQueryInterfacejako wartość $value, taki jak ten zwracany przez db_select(); problem polega na tym, że właściwie można go użyć, gdy wartość $operatorjest równa "IN". Zobacz: Podselekty nie działają w warunkach DBTNG, z wyjątkiem przypadków użycia jako wartości IN .

Jedyny sposób, w jaki widzę użycie operatora „NOT IN” w zapytaniu podrzędnym conditionto:

  • Wykonaj podzapytanie, aby uzyskać tablicę
  • Wykonaj główne zapytanie, ustawiając warunek jak w poniższym fragmencie

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

    $subquery_result to tablica zawierająca wynik zapytania podrzędnego.

W przeciwnym razie możesz użyć, where()jak powiedzieli inni, który akceptuje ciąg dla części zapytania, którą musisz dodać.

Pamiętaj, że db_select()jest to wolniejsze db_query(); powinieneś użyć pierwszego, gdy wiesz, że zapytanie może zostać zmienione przez inne moduły. W przeciwnym razie, jeśli inne moduły nie powinny używać hook_query_alter()do zmiany zapytania, należy użyć db_query().
W przypadku uzyskiwania dostępu do węzłów, jeśli potrzebujesz uzyskać tylko te węzły, do których użytkownik ma dostęp, musisz użyć db_select()i dodać 'node_access'jako znacznik zapytania za pomocą SelectQuery::addTag(). Na przykład blog_page_last()używa następującego kodu.

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

Podobny kod jest używany przez 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
źródło
Oto przykład podzapytania na niestandardowy filtr widoków, który napisałem: link
Roger
1
„Podselekty nie działają w warunkach DBTNG, z wyjątkiem sytuacji, gdy są używane jako wartość dla IN” jest naprawione w Drupal 8.3
Jonathan
3

Pisząc złożone zapytania, zdecydowanie powinieneś użyć db_query()zamiast db_select().

  1. Nie można napisać NOT INklauzuli z podzapytaniem z bieżącym interfejsem API bazy danych Drupal (jest to znany problem w trakcie opracowywania).
  2. Jeśli nie potrzebujesz dynamicznego zapytania (stąd przepisanego przez inne moduły), nie zawracaj sobie głowy pisaniem tak złożonego db_select().
  3. Podkwerendy nie są jeszcze dobrze obsługiwane (zobacz moją poprzednią odpowiedź ), a jeśli jesteś przyzwyczajony do pisania SQL, jest o wiele łatwiejszy w użyciu db_query().

Jeśli chodzi o zapytanie, nie jestem pewien, dlaczego chcesz użyć podzapytania (chyba że uprościłeś swój przykład)? Możesz to łatwo napisać w ten sposób:

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

DISTINCTnie jest konieczne, ponieważ nidjest kluczem podstawowym, więc nie będzie duplikowane.

tostinni
źródło
2
Odnośnie # 2, OP wybiera węzły. AFAIK db_select () jest jedynym sposobem na dostarczenie dowolnego wymaganego znacznika „node_access”, w którym to przypadku db_select () byłby jedynym wyborem.
keithm
2

Istnieje również where (), które pozwala dodać dowolny warunek where do zapytania.

Przykład:

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

Jak wspomniano w keithm, musisz wybrać db_select () i addTag ('node_access') podczas wybierania węzłów, które są następnie wyświetlane użytkownikom.

Berdir
źródło
1

Łatwiejszym sposobem użycia db_select z podseleksem NOT IN jest po prostu użycie mało znanego

$ zapytanie-> gdzie

dodać dowolny warunek where.

na przykład:

  // 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
źródło
0

Gdzie $ subquery_values ​​jest tablicą formatu $ key => $ nid w wyniku podzapytania

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

to działa dobrze.

Riccardo Ravaro
źródło