# pyquickbase API reference: Queries This describes version 1.0.0rc13. Other pages: [Quickbase](https://matthew-williams-solutions.github.io/pyquickbase/reference/quickbase.txt), [App](https://matthew-williams-solutions.github.io/pyquickbase/reference/app.txt), [Table](https://matthew-williams-solutions.github.io/pyquickbase/reference/table.txt), [Report](https://matthew-williams-solutions.github.io/pyquickbase/reference/report.txt), [Operation](https://matthew-williams-solutions.github.io/pyquickbase/reference/operation.txt), [SchemaOperation](https://matthew-williams-solutions.github.io/pyquickbase/reference/schema-operation.txt), [Records and results](https://matthew-williams-solutions.github.io/pyquickbase/reference/records.txt), [Metadata](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata.txt), [Schema changes](https://matthew-williams-solutions.github.io/pyquickbase/reference/schema-changes.txt), [Policies](https://matthew-williams-solutions.github.io/pyquickbase/reference/policies.txt), [Rules](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules.txt), [Retries](https://matthew-williams-solutions.github.io/pyquickbase/reference/retries.txt), [Tools for models](https://matthew-williams-solutions.github.io/pyquickbase/reference/tools.txt), [Errors](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors.txt). Conditions choose which records a query, count, or delete applies to. Build them with [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F), which escapes values for you, or with [`where_from_dict`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#where_from_dict) for plain data from a model or a web form. The examples below use `tasks`, made as shown here, and `app`, which is `qb.app("bq1234567")`. `Job Tasks` has `Title`, `Status`, `Hours`, `Due Date`, `Code`, which is unique, and `Attachment`. It starts with three records, and each example starts from those again. A live test runs every example with both clients and checks its output. Sync: ```python from pyquickbase import Quickbase with Quickbase( realm="acme", token="your_user_token" ) as qb: tasks = qb.app("bq1234567").table("Job Tasks") ``` Async: ```python from pyquickbase import AsyncQuickbase async with AsyncQuickbase( realm="acme", token="your_user_token" ) as qb: tasks = qb.app("bq1234567").table("Job Tasks") ``` ## Build a condition For the full picture, see [Reading records](https://matthew-williams-solutions.github.io/pyquickbase/guides/reading/). ### `F` A field in a query, named by label (`str`) or ID (`int`). `(F("Status") == "Done") & (F("Due Date") < date.today())`. Values are escaped for you, so this is the safe way to use text you don't control. `== None` matches empty fields. On a `user` or `multiuser` field, compare with an email or user ID. On a `percent` field, a number is a fraction, as it is when writing, so `F("Done") >= 0.5` means 50%. On a `duration` field, a number is milliseconds, or use a `timedelta`. Text is sent as written, such as `F("Budget") > "8 hours"`. ```python F(field: str | int) ``` **Parameters** - **`field`** `str | int`, required The field's label or ID. #### `F.contains()` Match records whose value contains this text. ```python contains(value: Any) -> Condition ``` **Parameters** - **`value`** `Any`, required The text to look for. **Returns** `Condition` A condition to pass as `where`. #### `F.not_contains()` Match records whose value doesn't contain this text. ```python not_contains(value: Any) -> Condition ``` **Parameters** - **`value`** `Any`, required The text to look for. **Returns** `Condition` A condition to pass as `where`. #### `F.starts_with()` Match records whose value starts with this text. ```python starts_with(value: Any) -> Condition ``` **Parameters** - **`value`** `Any`, required The text to look for. **Returns** `Condition` A condition to pass as `where`. #### `F.not_starts_with()` Match records whose value doesn't start with this text. ```python not_starts_with(value: Any) -> Condition ``` **Parameters** - **`value`** `Any`, required The text to look for. **Returns** `Condition` A condition to pass as `where`. #### `F.has()` Match records where one of the field's values is exactly this, such as one choice of a multi-select field, or one user of a list-user field. ```python has(value: Any) -> Condition ``` **Parameters** - **`value`** `Any`, required The whole value to look for. Unlike `contains`, part of a value doesn't match. **Returns** `Condition` A condition to pass as `where`. #### `F.not_has()` Match records where none of the field's values is exactly this. ```python not_has(value: Any) -> Condition ``` **Parameters** - **`value`** `Any`, required The whole value to look for. **Returns** `Condition` A condition to pass as `where`. #### `F.is_in()` Match any of the values, as a series of `EX` conditions joined with `OR`. ```python is_in(values: Iterable[Any]) -> Expression ``` **Parameters** - **`values`** `Iterable[Any]`, required The values to match. There must be at least one. **Returns** [`Expression`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#Expression) A condition to pass as `where`. **Raises** - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): There are no values. #### `F.op()` Use any Quickbase operator directly, such as `TV` or `IR`. ```python op(operator: str, value: Any) -> Condition ``` **Parameters** - **`operator`** `str`, required The operator, in capitals. Quickbase treats a lowercase one as "contains". - **`value`** `Any`, required The value to compare with. **Returns** `Condition` A condition to pass as `where`. **Raises** - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): Quickbase has no such operator. Sync example: ```python from pyquickbase import F where = (F("Status") == "Open") & (F("Hours") >= 10) for task in tasks.query(where=where, select=["Title"]): print(task["Title"]) print(tasks.count(F("Title").contains("wall"))) print(tasks.count(F("Title").starts_with("Pour"))) print(tasks.count(F("Status").is_in(["Open", "Done"]))) print(tasks.count(F("Due Date") == None)) print(tasks.count(F("Title").op("XCT", "wall"))) ``` Async example: ```python from pyquickbase import F where = (F("Status") == "Open") & (F("Hours") >= 10) async for task in tasks.query(where=where, select=["Title"]): print(task["Title"]) print(await tasks.count(F("Title").contains("wall"))) print(await tasks.count(F("Title").starts_with("Pour"))) print(await tasks.count(F("Status").is_in(["Open", "Done"]))) print(await tasks.count(F("Due Date") == None)) print(await tasks.count(F("Title").op("XCT", "wall"))) ``` Output: ```text Frame walls 2 1 3 1 1 ``` ### `Expression` A query condition. Combine with `&` and `|`, not `and` and `or`. Sync example: ```python from pyquickbase import F done_or_long = (F("Status") == "Done") | (F("Hours") > 10) print(tasks.count(done_or_long)) ``` Async example: ```python from pyquickbase import F done_or_long = (F("Status") == "Done") | (F("Hours") > 10) print(await tasks.count(done_or_long)) ``` Output: ```text 2 ``` ## Conditions from plain data For the full picture, see [Using pyquickbase from an AI agent](https://matthew-williams-solutions.github.io/pyquickbase/agents/). ### `where_from_dict` Build a condition from plain data, such as the JSON from a model's tool call. The result works anywhere a condition built with [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F) does, including combined with one, which is how to keep a model inside a scope your code decides: ```python scope = F("Project") == project allowed = ["Status", "Due Date"] where = scope & where_from_dict( data, fields=allowed ) ``` A condition is `{"field": "Status", "op": "==", "value": "Open"}`, and `{"all": [...]}` and `{"any": [...]}` combine them, nested if needed. `field` is a label or ID. `op` is `==`, `!=`, `<`, `<=`, `>`, `>=`, `contains`, `not_contains`, `starts_with`, `not_starts_with`, `has`, `not_has`, `in`, `not_in`, or a Quickbase operator such as `TV`, other than `IR` and `XIR`. `has` matches one of a multi-select field's values exactly. Spellings a model tends to write are read as the operator they mean: case, spaces, hyphens, and camelCase don't matter, so `starts with` and `startsWith` are `starts_with`, and `eq`, `=`, `ne`, `<>`, `lt`, `gte`, and the like are the matching symbol. `and` and `or` are read as `all` and `any`. Each of these means exactly one operator, so none can change what a filter means. Labels are never guessed: a label must match exactly. Values are escaped as they are with [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F). Unlike [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F), a value is also checked against the type of its field when the query is built: dates must be `YYYY-MM-DD`, timestamps need a time zone or to be dates, and numbers and checkboxes must be what they say. So `today` is refused, as Quickbase matches nothing, with no error, for text it can't read as a date. This protects a query's shape, not its meaning. A model that chooses the field and operator chooses the result, so a condition can still match every record. See [Using pyquickbase from an AI agent](https://matthew-williams-solutions.github.io/pyquickbase/agents/untrusted-input/#filters-from-a-model). ```python where_from_dict( data: Mapping[str, Any], *, fields: ( Iterable[str | int] | Iterable[Field] | None ) = None, ) -> Expression ``` **Parameters** - **`data`** `Mapping[str, Any]`, required The condition or group. `value` is required on every condition, so that a dropped key can't turn into a search for empty fields. Send `null` to mean that on purpose, which works with `==`, `!=`, `in`, and `not_in`. - **`fields`** `Iterable[str | int] | Iterable[Field] | None`, optional The only fields this filter may use, as labels, IDs, or [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field) objects, which count by label. Pass the same list as to [`where_schema`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#where_schema). Without this, it may use any field the token can see, and a model could learn a value it can't read by filtering on it. A field listed by label can't be named by its ID. **Returns** [`Expression`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#Expression) A condition to pass as `where`. **Raises** - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): The data can't be used, and the message says where. Nothing was sent. One with over 100 conditions, counting each value of `in` and `not_in`, or groups over 20 levels deep, is refused too. Type checks raise this when the query is built, not here. - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The table has no such field, raised when the query is built. Sync example: ```python from pyquickbase import where_from_dict data = { "all": [ {"field": "Status", "op": "==", "value": "Open"}, {"field": "Hours", "op": ">", "value": 10}, ] } where = where_from_dict(data, fields=["Status", "Hours"]) print(tasks.count(where)) ``` Async example: ```python from pyquickbase import where_from_dict data = { "all": [ {"field": "Status", "op": "==", "value": "Open"}, {"field": "Hours", "op": ">", "value": 10}, ] } where = where_from_dict(data, fields=["Status", "Hours"]) print(await tasks.count(where)) ``` Output: ```text 1 ``` ### `where_schema` Describe the format of [`where_from_dict`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#where_from_dict) as JSON Schema, for a tool's input. The schema is self-contained, with no `$ref`, so it can go anywhere in a tool's input schema. It's a new dictionary each time, so you can add to it, such as a `description`. ```python where_schema( *, fields: ( Iterable[str | int] | Iterable[Field] | None ) = None, field_descriptions: Mapping[str | int, str] | None = None, max_depth: int = 2, ) -> dict[str, Any] ``` **Parameters** - **`fields`** `Iterable[str | int] | Iterable[Field] | None`, optional The fields to offer, as labels, IDs, or [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field) objects, so a model can't name one that doesn't exist. Pass the same fields to [`where_from_dict`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#where_from_dict), by label or ID, to enforce the list, as this only describes it. Field labels can contain any text, and this shows them to the model, so only offer fields from an app you trust. Given [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field) objects, the description also says what a number means on a percent or duration field. - **`field_descriptions`** `Mapping[str | int, str] | None`, optional What each field means, as `{label or ID: text}`, added to the schema's description so the model can choose the right field. Each must be for a field in [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields), so a renamed field can't silently keep a description. - **`max_depth`** `int`, default `2` How many levels of `all` and `any` to describe, from 0 to 5. The default allows `A and (B or C)`. A tool's schema is sent to the model with every request, so each level has a cost. [`where_from_dict`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#where_from_dict) accepts deeper filters regardless. **Returns** `dict[str, Any]` The JSON Schema, as a dictionary. **Raises** - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) is empty, `max_depth` is out of range, or a description isn't for a field in [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields). Sync example: ```python from pyquickbase import where_schema schema = where_schema(fields=["Status", "Hours"], max_depth=1) print(type(schema).__name__, "$ref" in str(schema)) ``` Async example: ```python from pyquickbase import where_schema schema = where_schema(fields=["Status", "Hours"], max_depth=1) print(type(schema).__name__, "$ref" in str(schema)) ``` Output: ```text dict False ``` ## Check a hand-written condition For the full picture, see [Reading records](https://matthew-williams-solutions.github.io/pyquickbase/guides/reading/). ### `parse_where` Check a hand-written `where` string and return its conditions. Useful for extra checks of your own, or for finding out which fields a string refers to. ```python parse_where( where: str, fields: Iterable[Field] ) -> list[WhereCondition] ``` **Parameters** - **`where`** `str`, required The string, such as `{7.EX.'Open'}`. - **`fields`** `Iterable[Field]`, required The table's fields, from [`Table.fields()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#fields). **Returns** `list[` [`WhereCondition`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#WhereCondition) `]` A [`WhereCondition`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#WhereCondition) for each condition in the string. **Raises** - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): It has the same mistakes that `table.query()` would refuse. - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): It names a field that isn't in [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields). Sync example: ```python from pyquickbase import parse_where fields = tasks.fields() where = "{7.EX.'Open'}AND{8.GT.'5'}" print([c.field_id for c in parse_where(where, fields)]) ``` Async example: ```python from pyquickbase import parse_where fields = await tasks.fields() where = "{7.EX.'Open'}AND{8.GT.'5'}" print([c.field_id for c in parse_where(where, fields)]) ``` Output: ```text [7, 8] ``` ### `WhereCondition` One condition from a `where` string, with its value unquoted and unescaped. **Attributes** - **`field_id`** `int` The ID of the field it tests. - **`operator`** `str` Quickbase's operator, such as `EX`. - **`value`** `str` The value it compares with, as text. Sync example: ```python from pyquickbase import parse_where fields = tasks.fields() condition = parse_where("{7.EX.'Open'}", fields)[0] print(condition.field_id, condition.operator, condition.value) ``` Async example: ```python from pyquickbase import parse_where fields = await tasks.fields() condition = parse_where("{7.EX.'Open'}", fields)[0] print(condition.field_id, condition.operator, condition.value) ``` Output: ```text 7 EX Open ``` ## Ways to name a field For the full picture, see [Reading records](https://matthew-williams-solutions.github.io/pyquickbase/guides/reading/). ### `FieldRef` A field, by label (`str`) or ID (`int`). ```python FieldRef = str | int ``` ### `FieldRefs` One or more fields, by label, ID, or [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F), as `select` and `order_by` take them. ```python FieldRefs = str | int | F | Sequence[str | int | F] ```