Build table name
Name =dataset.table. Dataset say what kind, what about. Table say rest.
Metrics table = ONE measure. Columns = level ids +
timestamp + one value column. Value column name = table name minus period. Table Source = Ladder, projected? Table also has provenance: ladder or projected, the source of that row.
metrics_projects.fees_daily → project_id, timestamp, fees. Done. No page needed. Do NOT select column the name not promise.
Levels roll up: apps_by_chain → apps → projects. asset_tokens → assets_by_chain → assets. Use biggest level that answer question. Rollup already exist. Cheaper. Already correct.
Rules. Break rule = wrong answer or big bill.
All these seen for real.-
Bound time column. ALWAYS. No bound = read whole table. You pay, not us. Facts use
block_timestamp. Metrics usetimestamp. Reports useperiod_start. Screener = one row per thing as of now, no time column. -
Time bound alone is NOT enough. Not every table is partitioned, and a bound on an unpartitioned column prunes nothing. Every table is clustered, so also filter its cluster key: the entity id for a metrics table,
token_addressfor token facts. Read the keys fromINFORMATION_SCHEMA.COLUMNS:is_partitioning_columnandclustering_ordinal_position. Filter the leading cluster column first, order matters. Empty result = the object is a VIEW, which reports no keys; take its contract from its page. -
Partition column naked on left.
date(block_timestamp) >= x= read every partition. Function hide column from pruner. Writeblock_timestamp >= timestamp('2026-01-01'). - Bound BOTH sides of join. Bound not travel across join. Two tables, two bounds.
-
One token = filter
token_address+chain_id. NOTtoken_id. Same rows, buttoken_addressis the cluster key andchain_iddrops whole chains at plan time.token_idis derived, reaches no cluster key, and reads every chain: terabytes against megabytes. Keeptoken_idfor join todimensions.tokens. -
Set
maximum_bytes_billedon EVERY job. Over the cap, the query fails before it runs and bills nothing; the error names the bytes it needed. Wrong query costs you an error, not money.bq query --maximum_bytes_billed=<bytes>, ormaximumBytesBilledin the job config of any client library. Set it low, raise it when the failure tells you to. -
Bytes scanned is the bill, NOT rows returned. BigQuery on-demand charges per byte the query reads, at a rate per TiB, with a 10 MiB floor per table per query.
limitchanges NOTHING:limit 10reads the same bytes as no limit. Naming columns DOES cut the bill, because storage is columnar. Neverselect *. -
No recursive CTE. No unbounded self-join.
with recursivereads again every step and the bytes compound; a fan-out join multiplies rows before you see them. Size the join with a count first. A question that seems to need recursion wants a smaller question. - Empty ≠ zero. Empty = no figure exist for that measure, that thing, that day. Do NOT coalesce 0 then average or sum across. Do NOT call it decline.
-
Get unit from
dimensions.metricsBEFORE format. Unit =usd,count,ratio,pct. Ratio = fraction:apy0.05 = 5 percent,off_peg-0.02 = 2 percent under peg. NEVER assume dollar. -
Sum only how
dimensions.metricsallow.
- Additive (fees, volume): sum across day, sum across thing. Both fine.
- Semi-additive (TVL, supply, open interest): sum across thing. NEVER across day. Week = last day of week.
- Non-additive (APY, active users, share, funding rate): sum NEITHER way. Week active users = count distinct address over week from fact table. Add 7 daily counts = count same person 7 time. Wrong.
-
Address case matter. EVM stored lowercase: wrap explorer address in
lower(). Solana base58, case-sensitive, match verbatim. -
amount_raw= STRING.uint256overflow every BigQuery number type. CastBIGNUMERIC, divide bypow(10, decimals)fromdimensions.tokens. -
Financial statements wide.
reports_projects.financial_statements_*= one row per project per period, one column per line. Flows summed over period. Balances (treasury,tvl,price, market caps, supplies) = value on last day of period with value: NEVER sum balance across periods. User counts (daily_active_users,token_holders) = average daily count. -
Screener column =
<measure>_<window>_<aggregation>.screeners.<things>= one row per thing (projects,chains,assets,assets_by_chain,asset_tokens), every window endsas_of. Window1d,7d,30d,90d,180d,365d.avg= mean of window.sum= total of window, flows ONLY: distinct count (daily_active_users,token_holders,*_active_addresses,tradeable_*_count) has NO_sum.changeandtrendcompare two aggregation.change= value onas_ofvs value N day before,(now - then) / abs(then); null if either end missing or base 0.trend= N-day avg vs avg of N day before it; null unless prior window has value ALL N day, null if prior avg 0. Null = unknown, NOT zero._max_latest= value ONas_of, null if none: NOT last value before. Plus_max_ath,_max_atl. Which aggregation a measure get follows itsover_timeindimensions.metrics:sumONLY ifover_time=sum; every measure get latest,avg,change,trend, ath, atl. Asset token and asset chainsumadd up to the coarser level; holders, users,change,trendNOT. Rank or filter projects? Read screener. Do NOT rebuild windows from metrics tables. - NEVER query table from Not served yet list. Those are spec, not table. Tell user data not available. Do NOT write query that cannot run.
Join keys
Id = lowercase name. Combined id = parts joined with dash. Every table carry at least one. That is why catalog join.
Asset = one identity, all chains. Token = one contract, one chain. USDC = one row
dimensions.assets, one row per chain dimensions.assets_by_chain, one row per contract dimensions.asset_tokens.
Question → table
Shortest correct path for common question. Prefer metrics table over sum facts table yourself: smaller, and additivity already settled.All tables
Every table in catalog. Generated from pages, so complete at build time.Dimensions
dimensions.accounts, dimensions.apps, dimensions.asset_disclosure_fields, dimensions.asset_disclosures, dimensions.asset_token_fields, dimensions.asset_tokens, dimensions.assets, dimensions.assets_by_chain, dimensions.chains, dimensions.contracts, dimensions.dex_pools, dimensions.lending_market_reserves, dimensions.lending_markets, dimensions.metric_instances, dimensions.metrics, dimensions.order_book_markets, dimensions.perp_markets, dimensions.projects, dimensions.reference_assets, dimensions.tokens, dimensions.vaults, dimensions.venues
Facts
Metrics
Reports
Screeners
screeners.asset_tokens, screeners.assets, screeners.assets_by_chain, screeners.chains, screeners.projects
Functions
functions.calculate_historical_eod_asset_token_balances, functions.calculate_historical_eod_token_balances, functions.calculate_latest_asset_token_balances, functions.calculate_latest_token_balances