Como uso "NOT IN" em uma consulta?

26

Qual é a maneira correta de escrever uma consulta contendo 'NOT IN' usando uma instrução de condição?

Minha consulta é a seguinte:

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

Eu tentei algo como o seguinte:

$query->condition('n.' . $key, $value, 'not in (select language from 
  languages where language = $value)');
JurgenR
fonte
Talvez esteja faltando o óbvio, mas qual é a diferença entre sua consulta e SELECT nid FROM node WHERE language != 'ab'?
Елин Й.

Respostas:

38

No exemplo específico, você deve simplesmente escrever a condição como:

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

No caso genérico, em que você precisa selecionar as linhas em um banco de dados com base nos valores retornados de uma subconsulta, considere o seguinte:

  • "NOT IN" é aceito como operador de SelectQuery::condition(). De fato, a seguinte consulta seria executada:

    $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);
    }
  • Conforme relatado nas cláusulas condicionais ("Subseleciona"), SelectQuery::condition()também aceita um objeto implementado SelectQueryInterfacecomo valor para $value, como o retornado por db_select(); o problema é que, na verdade, você pode usá-lo apenas quando o valor de $operatorfor igual a "IN". Consulte Subselecionamentos não funcionam em condições DBTNG, exceto quando usado como valor para IN .

A única maneira de ver o uso do operador "NOT IN" com uma subconsulta conditioné:

  • Execute a subconsulta para obter uma matriz
  • Execute a consulta principal definindo a condição como no seguinte trecho

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

    $subquery_result é a matriz que contém o resultado da subconsulta.

Caso contrário, você pode usar where()como outros disseram, que aceita uma string para a parte da consulta que você precisa adicionar.

Tenha em mente que db_select()é mais lento isso db_query(); você deve usar o primeiro quando souber que a consulta pode ser alterada por outros módulos. Caso contrário, se outros módulos não devem ser usados hook_query_alter()para alterar sua consulta, você deve usá-lo db_query().
No caso de acessar nós, se você precisar obter apenas os nós aos quais um usuário tem acesso, precisará usar db_select()e adicionar 'node_access'como tag da consulta, com SelectQuery::addTag(). Por exemplo, blog_page_last()usa o seguinte código.

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

Código semelhante é usado por 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
fonte
Aqui está um exemplo de uma subconsulta para um filtro de vista personalizado que eu escrevi: ligação
Roger
11
"Subselecionações não funcionam em condições DBTNG, exceto quando usadas como valor para IN" é corrigido no Drupal 8.3
Jonathan
3

Ao escrever consultas complexas, você definitivamente deve usar em db_query()vez de db_select().

  1. Você não pode escrever uma NOT INcláusula com uma subconsulta com a API atual do banco de dados Drupal (é um problema conhecido que está sendo resolvido).
  2. Se você não precisa que sua consulta seja dinâmica (portanto, reescrita por outros módulos), não se preocupe em tentar escrever uma pergunta tão complexa db_select().
  3. As subconsultas ainda não são bem suportadas (veja uma resposta anterior ) e, se você está acostumado a escrever SQL, é muito mais fácil de usar db_query().

Em relação à sua consulta, não sei por que você deseja usar uma subconsulta (a menos que tenha simplificado seu exemplo)? Você pode escrever facilmente assim:

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

DISTINCTnão é necessário, pois nidé uma chave primária, para que não seja duplicada.

tostinni
fonte
2
Em relação ao item 2, o OP está selecionando nós. AFAIK db_select () é a única maneira de fornecer qualquer tag 'node_access' necessária; nesse caso, db_select () seria a única opção.
KeithM
2

Também há where () que permite adicionar uma condição where arbitrária à consulta.

Exemplo:

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

Como o keithm mencionado, você deve usar db_select () e addTag ('node_access') ao selecionar nós que serão exibidos aos usuários.

Berdir
fonte
1

Uma maneira mais fácil de usar o db_select com uma sub-seleção NOT IN é usar o pouco conhecido

$ query-> where

para adicionar uma condição where arbitrária.

por exemplo:

  // 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
fonte
0

Onde $ subquery_values ​​é uma matriz do formato $ key => $ nid como resultado de uma subconsulta

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

Funciona bem.

Riccardo Ravaro
fonte