summary
A summary field is a live rollup: refused on write even where editable=true, unfilterable, unsortable, and null on every custom one here, so re-run the query /schema exposes to select on it.
Data type summary: a per-row rollup defined in the schema, distinct from the _summarize
endpoint (probe 020), which aggregates rows named at call time into one number for the whole query.
On the probed site, scanning all 114 entity types, one /schema/<Type>/fields each (33s, probe 002),
finds 42 summary fields on 41 types under four names, every one of them probed below.
| field | types | editable |
aggregate | reads here | its own query counts |
|---|---|---|---|---|---|
open_notes_count |
38 | false |
record_count |
0 to 19 |
the same number |
sg_latest_version |
2 (Asset, Project) | true |
single_record |
null, every row |
0, except one Project holding Versions |
sg_query |
1 (Asset) | true |
single_record |
null, every row |
0 |
sg_test_results |
1 (CustomEntity01) | true |
record_count |
null, 100 of 100 rows |
70, 29, 11 |
| operation | result |
|---|---|
| read | the rollup value, under attributes |
PUT and POST create, editable=false field |
400 API update() Version.open_notes_count is read only. and API create() Version.open_notes_count is read only. |
PUT, editable=true field |
400 API update() of data type 'summary' not supported in API |
| filter | 400 API read() Shot.open_notes_count's 'summary' data type cannot be used in a filter. |
?sort=open_notes_count, ?sort=-open_notes_count |
200, row order unchanged |
_summarize grouping |
400 Grouping is not allowed for field Shot.open_notes_count. |
_summarize summary_fields [{"field": "open_notes_count", "type": "sum"}] |
200 {"summaries": {}, "groups": []}, field dropped without an error |
The rollup definition is exposed. GET /schema/<Type>/fields/<field> returns properties with
five keys. default_value is {"value": null, "editable": false} on all 42 of them and is not the
value of an unpopulated row; the other four define the rollup:
| key | holds |
|---|---|
summary_default |
the aggregate: record_count or single_record |
query |
entity_type of the rows aggregated, and the filter conditions |
summary_field |
the field aggregated: id for record_count, code for Project.sg_latest_version |
summary_value |
null for record_count; {"column": "created_at", "direction": "desc", "detail_link": true} for Project.sg_latest_version |
"summary_default": "record_count", "summary_field": "id", "summary_value": null,
"query": {"entity_type": "Note", "filters": {"logical_operator": "and", "conditions": [
{"path": "note_links", "relation": "is",
"values": [{"id": 0, "name": "Current Entity", "type": "Entity", "valid": "parent_entity_token"}]},
{"logical_operator": "or", "conditions": [... one per status: "opn", "ip", "rdy"]}]}}
{"id": 0, "valid": "parent_entity_token"} stands for the row being read. Rewrite each leaf condition
as a triple with the row in place of the token, keep the logical_operator groups, and send the tree
as filters under Content-Type: application/vnd+shotgun.api3_hash+json:
{"path": "note_links", "relation": "is", "values": [<the token>]}
-> ["note_links", "is", {"type": "Shot", "id": 862}]
Run that way, Shot.open_notes_count reproduces on three rows: 19, 18 and 18, against 19, 18 and 18.
It is the only summary field returning a number here, so that is the whole of the evidence that the
translation is right. null is not an empty rollup: CustomEntity01.sg_test_results is a
record_count reading null on 100 of 100 rows whose own query counts 70. No row here holds a
single_record value, so that aggregate is unproven, its populated shape included.
Read
| call | reads |
|---|---|
GET /entity/shots?fields=code,open_notes_count |
{"attributes": {"code": "sh010", "open_notes_count": 13}, "relationships": {}, "id": 862}. A record_count rollup is a plain integer in attributes, never relationships, and GET /entity/shots/862 with no ?fields returns it among the 77 attributes |
fields ["code", "entity.Shot.open_notes_count"] on a Version |
{"code": "sh010_comp_v001", "entity.Shot.open_notes_count": 13}. A dotted path through a link reads the linked row's count, unlike a dotted path through a multi_entity field (probe 016) |
the four sg_* fields, every row: Asset.sg_latest_version on charA, which has a Version linking to it, Asset.sg_query, CustomEntity01.sg_test_results on 100 rows, and Project.sg_latest_version on all 22 projects |
null |
The value is computed per read, with no lag. Each row below is the next request after the change:
| step | open_notes_count on a sandbox Version |
|---|---|
| before any Note | 0 |
Note created with sg_status_list: "opn", read 308ms later |
1 |
same Note moved to clsd |
0 |
| Note deleted | 0 |
Write Refused; the whole request is rejected rather than accepted and discarded like
cached_display_name (probe 004). Re-reading after each attempt returned the value unchanged.
| field | sent | result |
|---|---|---|
Project.sg_latest_version (editable=true) |
"zzprobe_summary", 42, {"type","id"} |
400 on all three |
Version.open_notes_count (editable=false), PUT and POST create |
3 |
400 |
Clear Not reachable.
| case | result |
|---|---|
PUT {"sg_latest_version": null} |
400, the same string as any other write |
a record_count field with nothing to count |
0 on open_notes_count: a Version created with no Notes has open_notes_count: 0 in the 201 body. sg_test_results reads null with 70 rows to count |
Filter No operator works, and the 400 names no vocabulary: every other type answers a bogus
relation with its Valid relations list (probe 017). source for definitely_not_an_operator:
{"Shot.open_notes_count": " data type cannot be used in a filter. Value: {"path" =>
"open_notes_count", "relation" => "definitely_not_an_operator", "values" => [nil]}"}
| attempt | result |
|---|---|
is 0, is_not null, greater_than 3, less_than 5, in [0,1] |
400, identical title |
GET ?filter[open_notes_count]=3, and ["entity.Shot.open_notes_count", "greater_than", 3] through a link |
400, identical title |
Page the rows and compare in the client, or run properties.query against the target type and aggregate.
Traps
- Fill rate is meaningless on this type.
open_notes_countis never null, so it scans as 100% filled while holding one value: 100 Versions all read0(probe 007). The other four readnullon every row and scan as 0% filled while their queries match rows. Excludedata_type == "summary"from fill ranking; theis_not Noneprobe 400s, as does_summarizegrouping. - Sort fails silently.
?sort=codeand?sort=-codereturn different orders,?sort=open_notes_countreturns the unsorted order, and so does?sort=definitely_not_a_field: an unsortable field and a typo are indistinguishable at 200. editable: truedescribes the field-configuration form in the web interface, not the REST value, and the refusal cites the data type rather than the field. Three of the 42 summary fields on this site claim it, so a client building an update form fromeditableoffers three that can never be saved.- 38 of the 42 are
open_notes_count, one per entity type, all counting Notes whose status isopn,iporrdy. That set is the site's definition of "open", readable fromproperties.queryand site configuration rather than an API constant: read it instead of hardcoding the three codes.