Skip to main content
Replace <catalog> and <namespace> (or <project> and <dataset> on BigQuery) with the names Autumn sent you. Examples on the other v2.6 pages are written as plain SQL against <namespace>.v2_6_<object>; this page shows how each engine spells them.

Addressing tables

The Glue catalog mounts as a database, and the whole <namespace>.v2_6_<object> is one table name with a literal dot in it. Backtick both parts:
`<catalog>.<namespace>`.`v2_6_customers` does not resolve, and neither does the single-quoted form.

Types

Every v2.6 column is one of five types. There are no array, map or struct columns. events.timestamp is the one native timestamp.

Timestamps

Columns typed long (epoch ms) (created_at, started_at, expires_at, current_period_end, as_of and the like) count milliseconds since 1970-01-01 UTC. Convert them before you display or compare with dates:

JSON columns

Objects and arrays are stored as JSON text in string columns, in the same shape the API returns. Columns typed string (JSON) in the Schema Reference hold either:
  • an object, such as v2_6_customers.metadata = '{"region":"eu"}' or v2_6_features.display = '{"singular":"message","plural":"messages"}', or
  • an array, such as v2_6_features.event_names = '["message_sent","message_edited"]', v2_6_invoices.plan_ids = '["pro","seats"]', v2_6_plan_items.price_tiers or events.deductions.
Each engine below shows the same four operations: read a field of an object, check whether an array contains a value, turn an array into rows, and read fields from an array of objects.
The columns are Nullable(String), and ClickHouse won’t build an array from a nullable value, so wrap them in ifNull(col, '[]') before JSONExtract(..., 'Array(...)') or JSONExtractArrayRaw.
Price tiers end with "to": "inf" (a string) on the last tier, and a number on the others. Read to as text, then convert the numeric ones.

Nulls in ClickHouse

On Iceberg tables, ClickHouse can raise NOT_FOUND_COLUMN_IN_BLOCK for WHERE col IS NULL. If you hit it, write coalesce(col, '') = '' for strings and coalesce(col, 0) = 0 for numbers instead. BigQuery handles IS NULL normally.

Rebuild a customer

Each part of POST /v1/customers.get comes from one table. Resolve the customer’s internal id once, then read each part with it. Join on org_id and env as well when you query more than one environment. An entity (POST /v1/entities.get) works the same way with the entity’s view: scope = 'entity' and its internal_entity_id. The balances part, in each engine:

Trials a customer has used

trials_used[] has no table of its own. A trial was used when a subscription had a trial and did not stay pending. The API also counts trials used by other customers with the same fingerprint:

What the lakehouse doesn’t have

Three parts of the customer are fetched from Stripe or computed when you call the API, so they are in no lakehouse version: rewards.discounts[], invoice_previews[], and the payment method and billing details.

Examples

Active subscriptions with their plan

Customers close to running out

Usage per day, by plan

events.deductions records which plan each unit of usage came from:

Cross-database joins

Join lakehouse tables against your own data in the same engine by qualifying both sides. Join on your own customer id (customer_id), which is the id you share with Autumn, and filter early by env and time range on large tables such as events.

Important notes

  • Join on internal ids. Inside the lakehouse, join on internal_* ids, customer_plan_id and balance_id, never on customer_id, plan_id or entity_id. Your own ids can be renamed, can be null (customer_id), are shared by every version of a plan (plan_id), or are unique only within one customer (entity_id).
  • Filter env. live and sandbox rows sit in the same tables.
  • Pick a balance view. See Working with balances.