In most of the cases we will want to get only one row, or only a group of rows.
We will see how to do that now, to filter data and get only the rows **where** a condition is true.
## Continue From Previous Code
We'll continue with the same examples we have been using in the previous chapters to create and select data.
And now we will update `select_heroes()` to filter the data.
<details>
<summary>👀 Full file preview</summary>
```Python hl_lines="36-41"
{!./docs_src/tutorial/select/tutorial001.py!}
```
</details>
If you already executed the previous examples and have a database with data, **remove the database file** before running each example, that way you won't have duplicate data and you will be able to get the same results.
## Filter Data with SQL
Let's check first how to filter data with **SQL** using the `WHERE` keyword.
```SQL hl_lines="3"
SELECT id, name, secret_name, age
FROM hero
WHERE name = "Deadpond"
```
The first part means the same as before:
> Hey SQL database 👋, please go and `SELECT` some data for me.
>
> I'll first tell you the columns I want:
>
> * `id`
> * `name`
> * `secret_name`
> * `age`
>
> And I want you to get them `FROM` the table called `"hero"`.
Then the `WHERE` keyword adds the following:
> So, SQL database, I already told you what columns to `SELECT` and where to select them `FROM`.
> But I don't want you to bring me all the rows, I only want the rows `WHERE` the `name` column has a value of `"Deadpond"`.
These additional keywords with some sections like `WHERE` and `FROM` that go after `SELECT` (or others) have a technical name, they are called **clauses**.
There are others clauses too, with their own SQL keywords.
I won't use the term **clause** too much here, but it's good for you to know it as it will probably show up in other tutorials you could study later. 🤓
## `SELECT` and `WHERE`
Here's a quick tip that helps me think about it.
* **`SELECT`** is used to tell the SQL database what **columns** to return.
* **`WHERE`** is used to tell the SQL database what **rows** to return.
The size of the table in the two dimensions depend mostly on those two keywords.
### `SELECT` Land
If the table has too many or too few **columns**, that's changed in the **`SELECT`** part.
Now, the same way that we add `WHERE` to a SQL statement to filter rows, we can add a `.where()` to a **SQLModel**`select()` statment to filter rows, which will filter the objects returned:
Now, this `.where()` method is special and very powerful. It is tightly integrated with **SQLModel** (actually SQLAlchemy) to let you use very familiar Python syntax and code.
Notice that we didn't call it with a single equal (`=`) sign, and with something like:
```Python
# Not supported 🚨
select(Hero).where(name="Deadpond")
```
That would have been shorter, of course, but it would have been much more error prone and limited. I'll show you why in a bit.
Instead, we used two `==`:
```Python
select(Hero).where(Hero.name == "Deadpond")
```
So, what's happening there?
## `.where()` and Expressions
In the example above we are using two equal signs (`==`). That's called the "**equality operator**".
!!! tip
An **operator** is just a symbol that is put beside one value or in the middle of two values to do something with them.
But after understanding that difference between classes and instances it can feel natural, and you can do very powerful things. 🚀
For example, as `hero.name` works like a `str` and `Hero.name` works like a special object for comparisons, you could write some code like:
```Python
select(Hero).where(Hero.name == hero.name)
```
That would mean:
> Hey SQL Database 👋, please `SELECT` all the columns
>
> `FROM` the table for the model class `Hero` (the table `"hero"`)
>
> `WHERE` the column `"name"` is equal to the name of this hero instance I have here: `hero.name` (in the example above, the value `"Deadpond"`).
## `.where()` and Expressions Instead of Keyword Arguments
Now, let me tell you why I think that for this use case of interacting with SQL databases it's better to have these expressions:
```Python
# Expression ✨
select(Hero).where(Hero.name == "Deadpond")
```
...instead of keyword arguments like this:
```Python
# Not supported, keyword argument 🚨
select(Hero).where(name="Deadpond")
```
Of course, the keyword arguments would have been a bit shorter.
But with the **expressions** your editor can help you a lot with autocompletion and inline error checks. ✨
Let me give you an example. Let's imagine that keword arguments were supported in SQLModel and you wanted to filter using the secret identity of Spider-Boy.
The editor would see the code, and because it doesn't have any information of which keyword arguments are allowed and which not, it would have no way to help you **detect the error**.
Maybe your code could even run and seem like it's all fine, and then some months later you would be wondering why your app *never finds rows* although you were sure that there was one `"Pedro Parqueador"`. 😱
And maybe finally you would realize that we wrote the code using `secret_identity` which is not a column in the table. We should have written `secret_name` instead.
Now, with the the expressions, your editor would show you an error right away if you tried this:
I think that alone, having better editor support, autocompletion, and inline errors, is enough to make it worth having expressions instead of keyword arguments. ✨
!!! tip
**Expressions** also provide more features for other types of comparisons, shown down below. 👇
## Exec the Statement
Now that we know how `.where()` works, let's finish the code.
It's actually the same as in previous chapters for selecting data:
We could imagine that **Spider-Boy** is even **younger**. But because we don't know the age, it is `NULL` in the database (`None` in Python), it doesn't match any of these age comparisons with numbers.
### Less Than or Equal
Finally, we can use `<=` to get the rows where a column is **less than or equal** to a value:
## `.where()` With Multiple Expressions Using `OR`
These last examples use `where()` with multiple expressions. And then those are combined in the final SQL using `AND`, which means that *all* of the expressions must be true in a row for it to be included in the results.
But we can also combine expressions using `OR`. Which means that **any** (but not necessarily all) of the expressions should be true in a row for it to be included.
secret_name='Esteban Rogelios' age=93 id=7 name='Captain North America'
```
</div>
## Type Annotations and Errors
There's a chance that your editor gives you an error when using these comparisons, like:
```Python
Hero.age > 35
```
It would be an error telling you that
> `Hero.age` is potentially `None`, and you cannot compare `None` with `>`
This is because as we are using pure and plain Python annotations for the fields, `age` is indeed annotated as `Optional[int]`, which means `int` or `None`.
By using this simple and standard Python type annotations We get the benefit of the extra simplicity and the inline error checks when creating or using instances. ✨
And when we use these special **class attributes** in a `.where()`, during execution of the program, the special class attribute will know that the comparison only applies for the values that are not `NULL` in the database, and it will work correctly.
But the editor doesn't know that it's a special **class attribute**, so it tries to help us preventing an error (that in this case is a false alarm).
Nevertheless, we can easily fix. 🎉
We can tell the editor that this class attribute is actually a special **SQLModel** column (instead of an instance attribute with a normal value).
To do that, we can import `col()` (as short for "column"):
Up to now, the database would have been **looking through each one of the records** (rows) to find the ones that match what you want. If you have thousands or millions of records, this could be very **slow**. 😱
In the next section I'll tell you how to add **indexes** to the database, this is what will make the queries **very efficient**. 😎