# OData query options

OData query options from Python - $filter, $select, $expand, $orderby, paging and counts, in both V2 and V4.

OData delegates filtering, sorting and projection of results to the server - the client specifies the required data in query options and only the matching data is returned. All the options below work with .read, .iter and .get, either as keyword arguments or through a reusable Query object.

## Keyword arguments {#keyword-arguments}

The most direct form - each OData system query option is a keyword argument, without the dollar prefix:

```python
conn = self.odata['OData.Sample']

customers = conn.read('Customers',
    filter="City eq 'London' and IsBlocked eq false",
    select='ID,DisplayName,City',
    expand='Orders',
    orderby='DisplayName desc',
    top=20,
    skip=40,
)
```

The client encodes each option correctly for the connection's OData version - for instance, inline counts are requested with `$count=true` in V4 and `$inlinecount=allpages` in V2.

| Name | Purpose |
| --- | --- |
| filter | Server-side filtering, e.g. `Price gt 100 and Category eq 'Books'` |
| select | Only the named properties are returned - a string or a list of names |
| expand | Related entities come inline, e.g. `Orders` or `Orders($top=5)` in V4 |
| orderby | Sorting, e.g. `Name` or `Price desc` - a string or a list |
| top | At most this many entities |
| skip | Skip this many entities first |
| count | Include the total match count alongside the results |
| search | Free-text search (V4) |
| apply | Aggregations and grouping (V4), e.g. `groupby((Category),aggregate(Price with sum as Total))` |
| custom | A dict of extra parameters passed through as they are, e.g. SAP's `sap-client` |

## The Query object {#the-query-object}

When the same options apply to more than one call, or when they are built up conditionally, use a Query object:

```python
# Zato
from zato.common.odata.query import Query

query = Query(
    filter="Country eq 'DE'",
    select=['ID', 'DisplayName'],
    orderby='DisplayName',
    top=50,
)

customers = conn.read('Customers', query)
```

## Escaping literals {#escaping-literals}

Values embedded in filters follow OData's literal rules - single quotes inside strings are doubled, dates and GUIDs have version-specific formats. The **format\_literal** function produces a correct literal for any Python value, matching the connection's OData version:

```python
# Zato
from zato.common.odata.query import format_literal

name = "O'Brien"
customers = conn.read('Customers', filter=f'DisplayName eq {format_literal(name)}')
```

With ODataAdapter services, {placeholder} values substituted into the filter attribute are escaped automatically.

## Counting {#counting}

To learn only the number of matching entities, use .count - it maps to the `$count` path segment and transfers a single number, not the data:

```python
how_many = conn.count('Orders', filter="Status eq 'Open'")
```

## Paging {#paging}

Servers cap how many entities one response contains and include a link to the next page. The .iter method follows those links transparently:

```python
for order in conn.iter('Orders', filter="Status eq 'Open'"):
    process(order)
```

If the connection's **Page size** is set, the client also asks the server for pages of that size using the `Prefer: odata.maxpagesize` header.

## Learn more {#learn-more}

- [OData integrations](https://zato.io/docs/dev/odata/) - Entity sets as Python dicts, across OData V2 and V4 systems
- [OData security](https://zato.io/docs/dev/odata/security/) - Basic Auth, bearer tokens and OAuth2 client credentials
- [The systems you meet](https://zato.io/docs/dev/odata/systems/) - Business Central, Dynamics 365 and other OData services
- [OData tutorial](https://zato.io/tutorials/odata.html) - From an empty connection to reading and writing records
