3.7.3.10 Complex Find Conditions
Most of the model's find calls involve passing sets of conditions in one way or another. The simplest approach to this is to use a WHERE clause snippet of SQL. If you find yourself needing more control, you can use arrays.
Using arrays is clearer and easier to read, and also makes it very easy to build queries. This syntax also breaks out the elements of your query (fields, values, operators, etc.) into discrete, manipulatable parts. This allows CakePHP to generate the most efficient query possible, ensure proper SQL syntax, and properly escape each individual part of the query.
At it's most basic, an array-based query looks like this:
$conditions = array("Post.title" => "This is a post");
//Example usage with a model:
$this->Post->find($conditions);
$conditions = array("Post.title" => "This is a post");//Example usage with a model:$this->Post->find($conditions);
The structure here is fairly self-explanatory: it will find any post where the title equals "This is a post". Note that we could have used just "title" as the field name, but when building queries, it is good practice to always specify the model name, as it improves the clarity of the code, and helps prevent collisions in the future, should you choose to change your schema.
What about other types of matches? These are equally simple. Let's say we wanted to find all the posts where the title is not "This is a post":
array("Post.title <>" => "This is a post")
array("Post.title <>" => "This is a post")
Notice the '<>' that follows the field name. CakePHP can parse out any valid SQL comparison operator, including match expressions using LIKE, BETWEEN, or REGEX, as long as you leave a space between field name and the operator. The one exception here is IN (...)-style matches. Let's say you wanted to find posts where the title was in a given set of values:
array(
"Post.title" => array("First post", "Second post", "Third post")
)
array("Post.title" => array("First post", "Second post", "Third post"))
To do a NOT IN(...) match to find posts where the title is not in the given set of values:
array(
"NOT" => array( "Post.title" => array("First post", "Second post", "Third post") )
)
array("NOT" => array( "Post.title" => array("First post", "Second post", "Third post") ))
Adding additional filters to the conditions is as simple as adding additional key/value pairs to the array:
array (
"Post.title" => array("First post", "Second post", "Third post"),
"Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
)
array ("Post.title" => array("First post", "Second post", "Third post"),"Post.created >" => date('Y-m-d', strtotime("-2 weeks")))
You can also create finds that compare two fields in the database
array("Post.created = Post.modified")
array("Post.created = Post.modified")
This above example will return posts where the created date is equal to the modified date (ie it will return posts that have never been modified).
Remember that if you find yourself unable to form a WHERE clause in this method (ex. boolean operations), you can always specify it as a string like:
array(
'Model.field & 8 = 1',
//other conditions as usual
)
array('Model.field & 8 = 1',//other conditions as usual)
By default, CakePHP joins multiple conditions with boolean AND; which means, the snippet above would only match posts that have been created in the past two weeks, and have a title that matches one in the given set. However, we could just as easily find posts that match either condition:
array( "or" => array (
"Post.title" => array("First post", "Second post", "Third post"),
"Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
)
)
array( "or" => array ("Post.title" => array("First post", "Second post", "Third post"),"Post.created >" => date('Y-m-d', strtotime("-2 weeks"))))
Cake accepts all valid SQL boolean operations, including AND, OR, NOT, XOR, etc., and they can be upper or lower case, whichever you prefer. These conditions are also infinitely nest-able. Let's say you had a belongsTo relationship between Posts and Authors. Let's say you wanted to find all the posts that contained a certain keyword (“magic”) or were created in the past two weeks, but you want to restrict your search to posts written by Bob:
array (
"Author.name" => "Bob",
"or" => array (
"Post.title LIKE" => "%magic%",
"Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
)
)
array ("Author.name" => "Bob","or" => array ("Post.title LIKE" => "%magic%","Post.created >" => date('Y-m-d', strtotime("-2 weeks"))))
Cake can also check for null fields. In this example, the query will return records where the post title is not null:
array ("not" => array (
"Post.title" => null,
)
)
array ("not" => array ("Post.title" => null,))
To handle BETWEEN queries, you can use the following:
array('Post.id BETWEEN ? AND ?' => array(1,10)) array('Post.id BETWEEN ? AND ?' => array(1,10))
Note: CakePHP will quote the numeric values depending on the field type in your DB.
You can create very complex conditions, by nesting multiple condition arrays:
array(
'OR' => array(
array('Company.name' => 'Future Holdings'),
array('Company.name' => 'Steel Mega Works')
),
'AND' => array(
array(
'OR'=>array(
array('Company.status' => 'active'),
'NOT'=>array(
array('Company.status'=> array('inactive', 'suspended'))
)
)
)
)
);
array('OR' => array(array('Company.name' => 'Future Holdings'),array('Company.name' => 'Steel Mega Works')),'AND' => array(array('OR'=>array(array('Company.status' => 'active'),'NOT'=>array(array('Company.status'=> array('inactive', 'suspended')))))));
Which produces the following SQL:
SELECT `Company`.`id`, `Company`.`name`,
`Company`.`description`, `Company`.`location`,
`Company`.`created`, `Company`.`status`, `Company`.`size`
FROM
`companies` AS `Company`
WHERE
((`Company`.`name` = 'Future Holdings')
OR
(`Company`.`name` = 'Steel Mega Works'))
AND
((`Company`.`status` = 'active')
OR (NOT (`Company`.`status` IN ('inactive', 'suspended'))))
SELECT `Company`.`id`, `Company`.`name`,`Company`.`description`, `Company`.`location`,`Company`.`created`, `Company`.`status`, `Company`.`size`FROM`companies` AS `Company`WHERE((`Company`.`name` = 'Future Holdings')OR(`Company`.`name` = 'Steel Mega Works'))AND((`Company`.`status` = 'active')OR (NOT (`Company`.`status` IN ('inactive', 'suspended'))))
