GROUP BY cardinality can make a shopifyqlQuery date range unqueryable at any width

Sharing a reproducible case in case it helps others, plus two questions about the API.

The documented limit is 1000 points per query, with cost described as structural rather than row driven. What we hit is a consequence of that which was not obvious to us: the cardinality of a GROUP BY dimension can push a query over the cap for specific dates, and no amount of narrowing the date range gets you back under it.

The case

Query shape, unchanged across every test, run against one merchant store within a few minutes of each other:

FROM sessions SHOW sessions GROUP BY day, landing_page_path SINCE <a> UNTIL <b> ORDER BY day LIMIT 400000
range width rows result
2025-09-01 to 2025-10-01 30 days 2,879 OK
2026-05-01 to 2026-05-31 30 days 5,225 OK
2026-06-01 to 2026-07-01 30 days 10,416 OK
2026-07-24 to 2026-07-31 8 days 50,494 OK
2026-07-01 to 2026-07-31 30 days none THROTTLED on 3 attempts, 65s apart
2026-07-22 to 2026-07-23 1 day none THROTTLED on 3 attempts, 65s apart

A one day range failing while an eight day range covering the same period succeeds rules out width, range scaled cost, and bucket exhaustion together. A 30 day June query succeeded seconds either side of the July failures.

The cause shows up in the paths that do come back:

/collections/new-arrivals/designer_askk-ny+designer_bao-bao-by-issey-miyake+designer_dries-van-noten+style_blazer+style_dress+style_knitwear

Faceted collection URLs. Every filter combination is a distinct landing_page_path, so cardinality grows combinatorially with the number of filters a storefront offers. This store went from roughly 175 distinct paths a day in May to roughly 6,300 a day in late July, and on 2026-07-22 the query stopped being runnable at all.

The fix, for anyone hitting the same thing

Filter the high cardinality dimension inside the query rather than in your own code afterwards. We only ever wanted product pages:

FROM sessions SHOW sessions GROUP BY day, landing_page_path WHERE landing_page_path STARTS WITH '/products/' SINCE 2026-07-22 UNTIL 2026-07-23 ORDER BY day LIMIT 400000

That returned the previously unqueryable day in under a second, with 133 rows.

Two syntax notes that cost us time. STARTS WITH and CONTAINS both work; LIKE is rejected by the parser with “no viable alternative at input”. The WHERE clause has to come before SINCE.

Questions

  1. Does the cost estimate account for the cardinality of a GROUP BY dimension, or only the number of dimensions and the span of the date range? The observed behaviour suggests cardinality, and knowing that explicitly would change how people design these queries.

  2. A rejected query carries no cost information in the response. Is there any way to see the requested cost of a query that was refused? Without it a client cannot tell “this query is too expensive, change its shape” apart from “the bucket is empty, wait and retry”, and those need opposite responses. We spent a day narrowing date ranges when narrowing could never have worked.

Happy to share the store domain and exact timestamps privately with staff.

Following up with an answer to my own second question, in case it saves anyone else the time.

A rejected query returns no ShopifyQL cost information. It carries only the ordinary Admin GraphQL cost object, never shopifyqlCost.

Rejected, unfiltered, one day range:

"cost": { "requestedQueryCost": 3, "actualQueryCost": 1,
          "throttleStatus": { "maximumAvailable": 4000, "currentlyAvailable": 3999, "restoreRate": 200 } }

Successful, same day, only difference is the WHERE clause:

"cost": { "requestedQueryCost": 3, "actualQueryCost": 3,
          "throttleStatus": { "maximumAvailable": 4000, "currentlyAvailable": 3997, "restoreRate": 200 } }
"shopifyqlCost": { "requestedQueryCost": 14, "maximumAvailable": 1000,
                   "currentlyAvailable": 976, "windowResetAt": "2026-08-01T10:02:00+00:00" }

Two things worth noting.

The outer cost bucket sits at 3999 of 4000 while ShopifyQL is refusing the query outright. If you read that meter and conclude you have headroom, you will conclude wrongly. shopifyqlCost is the one that matters, and it only appears on success.

The filtered query costs 14 points against a 1000 maximum. Same day, same shape, same limit, the only change being the landing_page_path filter. So the unfiltered version is somewhere north of 1000, which is fairly direct confirmation that GROUP BY cardinality is what drives the cost.

The practical upshot is that requestedQueryCost is only observable on queries that succeed, so it can be used to size the next window but never to explain the one that just failed. On a failure the only safe assumption is that the query was too expensive rather than that the bucket was empty.

Still curious whether exposing the requested cost on a rejection is feasible. It is the one number that would let a client respond correctly first time.