Last updated February 27, 2013. Created by solotandem on September 18, 2008.
Edited by sulav, Elijah Lynn, drupalshrek, xjm. Log in to edit this page.
A "conditional clause" is a portion of a query that restricts the rows matched by certain conditions. In SQL, that means the WHERE or HAVING portions of a SELECT, UPDATE, or DELETE query. In all dynamic queries in Drupal those are implemented using the same mechanism. Except where noted, everything that follows applies to all three of those query types.
Concepts
- Conditional fragment
- A conditional fragment is a self-contained portion of a conditional clause.
- Conjunction
- Every conditional clause consists of one or more conditional fragments joined by a conjunction. A conjunction is a term such as
ANDorORthat joins the two statements together. - Conditional object
- Drupal represents each conditional fragment as an instance of class
QueryConditional. A conditional object is an instance of that class.
As an example, the following query breaks down this way:
- Query:
SELECT FROM {mytable} WHERE (a = 1 AND b = 'foo' OR (c = 'bar'))- Conditional clause:
WHERE (a = 1 AND b = 'foo' OR (c = 'bar'))- Conditional fragments:
(a = 1 AND b = 'foo' OR (c = 'bar'))(c = 'bar')- Conjunctions:
AND,OR
The Select, Update, and Delete query objects implement the QueryConditionalInterface, which provides them all with the same interface. Internally they wrap a QueryConditional object. The QueryConditional class can also be instantiated directly.
Each conditional fragment in a conditional statement is joined by a conjunction. A conditional object consists of one or more conditional fragments that will all be joined by a specified conjunction. By default, that conjunction is AND. Each conditional fragment may optionally be a conditional object with a different conjunction, allowing for a conditional statement to be built up by nesting conditional fragments inside each other. In that way, arbitrarily complex conditional statements may be built.
API
There are two main methods that apply for all conditional objects:
$query->condition($field, $value = NULL, $operator = '=')- The
condition()method allows for adding a standard$field $value $operatorformat of conditional fragment. That includes any case where the condition is a binary comparison such as=,<,>=,LIKE, etc. If no operator is specified,=is assumed. That means the most common case would becondition('myfield', $value), which results in a conditional fragment ofmyfield = :value, where:valuewill be replaced with$valuewhen the query is run. $query->where($snippet, $args = array())- The
where()method allows for the addition of arbitrary SQL as a conditional fragment.$snippetmay contain any legal SQL fragment, and if it has variable content it must be added using a named placeholder. The$argsarray is an array of placeholders and values that will be substituted into the snippet. It is up to the developer to ensure that the snippet is valid SQL. No database-specific modifications are made to the snippet.
The condition() method is preferred in most cases, unless the $field $value $operator format is not appropriate, such as happens when you have more complex things like expressions, or a condition on two fields instead of a field and a value. Both methods return the corresponding conditional object, so they may be chained indefinitely.
condition() also handles several other special cases.
Unlike Update and Delete queries, Select queries have two types of conditionals: The WHERE clause and the HAVING clause. The Having clause behaves identically to the WHERE clause, except that it uses methods havingCondition() and having() instead of condition() and where().
Array operators
Some operators are intended to work on an array for the value parameter. The most common of these are IN and BETWEEN. If the operator is IN, then the $value is assumed to be an array of values that the field may equal. Thus, the following call will evaluate this way:
<?php
$query->condition('myfield', array(1, 2, 3), 'IN');
// Becomes: myfield IN (:db_placeholder_1, :db_placeholder_2, :db_placeholder_3)
?>If the operator is BETWEEN, then $value is assumed to be a 2-element array of the values the field must be between. For example:
<?php
$query->condition('myfield', array(5, 10), 'BETWEEN');
// Becomes: myfield BETWEEN :db_placeholder_1 AND :db_placeholder_2
?>Nested conditionals
The first parameter of condition() can also accept another conditional object. That inner conditional object will be incorporated into the outer conditional, surrounded by parentheses. The inner object may also use a different conjunction than the outer object. That way, one can build complex nested conditional structures by creating and building up conditional objects "bottom up".
The db_condition() helper function will return a new conditional object. It takes a single parameter that is the conjunction that object will use. In general, the helper methods db_and(), db_or(), and db_xor() will cover almost any expected case. That allows conditionals to be inserted inline in a query for a very compact syntax.
For example, consider the following construct:
<?php
$query
->condition('field1', array(1, 2), 'IN')
->condition(db_or()->condition('field2', 5)->condition('field3', 6))
// Results in:
// (field1 IN (:db_placeholder_1, :db_placeholder_2) AND (field2 = :db_placeholder3 OR field3 = :db_placeholder_4))
?>Null values
To filter a database field on whether the value is or is not NULL, use the following methods:
<?php
$query->isNull('myfield');
// Results in (myfield IS NULL)
$query->isNotNull('myfield');
// Results in (myfield IS NOT NULL)
?>Both methods may be chained and combined with condition() and where() as desired.
Note: Although it is possible in Drupal 7 to check for NULL values using condition('field', NULL), that usage is deprecated and should not be used. Use the methods above instead. See #813540: Comparisons involving NULL must never return true for more information.
Subselects
condition() also supports subselects as the $value. To use a subselect, first construct a SelectQuery object created by db_select(). Then, instead of executing the Select query pass it into the value parameter of the condition() method of another query. It will automatically get integrated into the main query when it is executed.
Subselects are generally useful only in two cases: Where the subselect results in only a single row and value returned and the operator is =, <, >, <=, or >=; or when the subselect returns a single column of information and the operator is IN. Most other combination would result in a syntax error.
Note: Currently it is only possible to use subselect conditions with the IN operator because with the other operators the sub-query is not wrapped in parentheses and so results in a syntax error. See #1267508: Subselects don't work in DBTNG conditions, except when used as value for IN.
Note that on some databases, particularly MySQL, subselects in a conditional clause are not particularly fast. If possible, use joins, subselects in the FROM clause, or multiple flat conditional fragments instead of a subselect.
Examples
The following examples should hopefully make the use of conditionals clearer. For clarity, the equivalent query string is shown even though in practice placeholders and prepared statements would be used instead.
<?php
db_delete('sessions')
->condition('timestamp', REQUEST_TIME - $lifetime, '<')
->execute();
// DELETE FROM {sessions} WHERE (timestamp < 1228713473)
?><?php
db_update('sessions')
->fields(array(
'sid' => session_id()
))
->condition('sid', $old_session_id)
->execute();
// UPDATE {sessions} SET sid = 'abcde' WHERE (sid = 'fghij');
?><?php
// From taxonomy_term_save():
$or = db_or()->condition('tid1', 5)->condition('tid2', 6);
db_delete('term_relation')->condition($or)->execute();
// DELETE FROM {term_relation} WHERE ((tid1 = 5 OR tid2 = 6))
?>
Comments
Wildcards for LIKE conditions
To do LIKE conditions using wildcards you would do something like the following:
<?php$query->condition('my_field', '%superstring%', 'LIKE');
?>
Is the equivalent of searching for
*superstring*and will find results like
mysuperstringrocksYou may also use the underscore as a wildcard for a single character.
<?php$query->condition('my_field', 'super_tring', 'LIKE');
?>
will find results like
superstringBut not
superduperstringTo use a LIKE condition that uses the "real" percent and underscore characters you need to escape them.
<?php$query->condition('my_field', '\_foo\%bar\_', 'LIKE');
?>
will find results like
_foo%bar_But not
_foo_bar_________________________
dave hansen-lange
Technical Lead
Advomatic.com
Great White North office
Canada
Use db_like() for escaping
You may use db_like() instead of manually escaping the value:
<?php$query->condition('my_field', db_like('_foo%bar_'), 'LIKE');
?>
NOT IN also supported
Just for completeness, NOT IN is also supported.
Using the <> operator will result in an error:
<?php$query->condition('myfield', array(1, 2, 3), '<>');
// myfield <>
?>
Instead, use the NOT IN operator:
<?php$query->condition('myfield', array(1, 2, 3), 'NOT IN');
// myfield NOT IN (:db_placeholder_1, :db_placeholder_2, :db_placeholder_3)
?>
-Wes
Deletes with subquery and not in
How would I go about doing something like:
delete from {sessions} s where s.uid not in (select ur.uid from (users_roles) ur where ur.rid not in (3,4))
Correct
I think it must be replaced by:
Please, see api condition
My site - VAZP, UZA, VG TPE
NOT conditions
Is there a way to add condition like "NOT isLogo>0" using condition() method of SelectQuery?
There are equivalent
There are equivalent operators for negative comparisons. If you're doing >, you can switch to < for the opposite. The example you gave above therefore becomes "isLogo <= 0" If you're doing equation, '!=' or '<>' should do the trick.
Dave Ingram - Gainesville, FL
www.ingraminnovation.com
Support for LEFT(), RIGHT(), SUBSTR()
I'm digging through the D7 DB API documentation as well as the dbtng module documentation, and I'm not seeing any support mentioned for portable LEFT(), RIGHT(), SUBSTR() implementations. These are parts of standard SQL.
I do see we can use:
<?php$query->where($snippet, $args = array())
?>
Is the assumption that we should be falling back from the API to regular SQL query language here in order to be able to use these features?
If so, here's how I ended up working through this. The goal was to only use one LIKE clause in my query, since LIKE is slow. One of the options was to match at the left on the input string (a typeahead query). Of course you can use LIKE% here, but since LIKE doesn't use an index, it's slow. Faster is LEFT(column, $length_of_string) = $string.
So this is how the query ended up looking. I wanted to see if the db_or() object would let me mix a condition() with where() and it did (in case any of you were wondering).
<?php
$string = What the user is typing into the typeahead....
$query = db_select('node', 'n')
->fields('n', array('nid', 'title', 'status'))
->condition('n.type', 'video')
->condition('n.status', 1)
->condition(db_or()->condition('n.title', "%$string%", 'LIKE')->where('LEFT(n.nid, :len) = :str', array(':len' => strlen($string), ':str' => $string)))
->range(0, 10);
echo $query->__toString() . '\n';
$matches = $query->execute();
?>
The condition creates an or clause with a normal middle of string LIKE (for title), or left() of string match (for match by nid). Left() is faster than LIKE since it can use the index on the nid column.
I didn't see any documentation on how to create the necessary placeholders in the ->where() condition, so I experimented and finally ended up using the colon prefix for the placeholders rather than %s or !placeholder, etc etc.:
<?php->where('LEFT(n.nid, :len) = :str', array(':len' => strlen($string), ':str' => $string)
?>
The SQL query generated by this looks like this:
<?php
echo $query->_toString() . "\n";
?>
SELECT n.nid AS nid, n.title AS title, n.status AS statusFROM
{node} n
WHERE (n.type = :db_condition_placeholder_0) AND (n.status = :db_condition_placeholder_1) AND( (n.title LIKE :db_condition_placeholder_2 ESCAPE '\\') OR (LEFT(n.nid, :len) = :str) )
LIMIT 10 OFFSET 0
And the list of arguments (my $string in this example is "24"):
<?phpvar_dump($query->getArguments());
?>
array(5) {[":db_condition_placeholder_0"]=>
string(5) "video"
[":db_condition_placeholder_1"]=>
int(1)
[":db_condition_placeholder_2"]=>
string(4) "%24%"
[":len"]=>
int(2)
[":str"]=>
string(2) "24"
}
Which means the final result sent to the database server is this:
SELECT n.nid AS nid, n.title AS title, n.status AS statusFROM
{node} n
WHERE (n.type = 'video') AND (n.status = 1) AND ( (n.title LIKE '%24%' ESCAPE '\\') OR (LEFT(n.nid, 2) = 24) )
LIMIT 10 OFFSET 0
Which gets us exactly what we wanted.
db_and() and LIKE (multiple conditions)
<?php$query=db_select('node','n')
->condition(db_and()->condition('n.uid','1')->condition('n.type','YOUR TYPE',LIKE))
->fields('n',array('nid','type'))
->execute();
?>
<?php
<?php$query->condition("date_begin","DATE_SUB(NOW(), INTERVAL 1 YEAR)", ">=");
?>
is not working for me because SQL function DATE_SUB is in double quotes
how can i escape '' in db_place_holder in this case because SQL function don't need double quotes
Thank you for you help
Little example of nesting with db_or and db_and
We want:
Either
under 25 AND beautiful AND posessing a character trait we like
OR
over 25 AND rich AND doesn't possess a character trait we hate
$traits_we_like = array("bad at chess",
"can open beer with tongue",
"laughs at stupid jokes"
);
$traits_we_hate = array(
"good at chess",
"can open beer with empty eye socket",
"only laughs at good jokes"
);
$query->condition(
db_or()
->condition(
db_and()
->condition('my_table.age', 25, '<')
->condition('my_table.is_beautiful', 1)
->condition('my_table.character_trait', $traits_we_like, 'IN')
)
->condition(
db_and()
->condition('my_table.age', 25, '>=')
->condition('my_table.bottlecaps', 25000, '>=')
->condition('my_table.character_trait', $traits_we_hate, 'NOT IN')
)
);