Query Traba's data sources directly. Two backends:
- Postgres (operational replica) — live, transactional, narrow but deep. Use for the current state of any record.
- BigQuery (analytics warehouse) — modeled, broad, includes data Postgres doesn't have. Use for analytics, history, app events, third-party tools, and LLM-categorized signals.
Default is Postgres prod. Pass dev, prod, or bq as the first word to override.
/query-database [question or SQL] # postgres prod
/query-database dev <SQL> # postgres dev (write-capable)
/query-database bq <SQL> # bigquery
bash ~/workspace/.claude/skills/query-database/query.sh <env> "<SQL>"- Schema lives in
~/workspace/monorepo/apps/traba-server-node/prisma/schema.prisma. Models have@@map("table_name"); fields have@map("columnName")when the Prisma name differs. - Tables:
snake_case. Columns:camelCasein double quotes. Enums:'UPPER_SNAKE_CASE'. - The
devuser has write privileges. Only run writes when the user has explicitly asked.
bq query --use_legacy_sql=false --project_id=traba-app --format=pretty "<SQL>"--project_id is required even when the SQL fully qualifies tables; without it bq errors with "Cannot start a job without a project id".
Tables are referenced as `traba-app.<dataset>.<table>`. All identifiers are lowercase snake_case in BigQuery — column names that are camelCase in Postgres become snake_case in BQ marts (workerId → worker_id).
traba-app— primary analytics project. Most useful datasets:marts— modeled fact tables (worker_shifts, worker_applications, placements, shift_charges). LLM-enriched columns live here.metrics— aggregated rollups (placements_by_day, monthly__worker_ltv, executive summaries).worker_app,worker_app_legacy— Segment-tracked events from the worker mobile app (320+ event tables, every tap/click/screen-load).intercom,intercom_intercom— full Intercom history including conversation_part_history (the actual messages, not just ticket metadata Postgres has).freshdesk— Freshdesk tickets with conversation body.firestore_export_prod— Firestore changelogs (timesheets, worker_shift, account_status).src_pg— Postgres replica into BQ (so the same Postgres data, queryable analytically).prod_node_server— backend server logs.salesforce,prod_salesforce— Salesforce CRM data.statsig— feature flag exposures.
traba-business-intelligence— BI workspace.traba-ops— ops project (mostly user-personal datasets liketraba_<username>).
- Postgres for: current state of a record, real-time data (anything written in the last few minutes), foreign-key joins to live data, anything you'd need to act on (modify, react to).
- BigQuery for:
- LLM-enriched / categorized data.
marts.worker_shifts__cancel_categorieshas 30+ LLM-flagged cancel categories (health_flag,pay_errors_flag,transportation_issue_flag,childcare_flag, etc.) that don't exist in Postgres. - Modeled rollups.
marts.placements(worker × company placements),metrics.monthly__worker_ltv,metrics.placements_by_day__churn. Pre-joined and pre-aggregated; cheaper to read than rolling them yourself. - App-event timeline. Anything in
worker_app.*is a Segment event with timestamp, user_id, properties JSON. Use to reconstruct what a worker did in the app on a specific day. - Full conversation bodies. Postgres only has ticket metadata;
intercom.conversation_part_historyandfreshdesk.conversationhave the actual message text. - Historical / time-series queries. BQ is partitioned by day on most fact tables and is faster than Postgres for any "by day / week / month over the last N months" question.
- Cross-source joins. Salesforce + Postgres + Segment + Intercom all live in BQ; joining across them is a single query there vs three exports.
- LLM-enriched / categorized data.
- Either works for: simple lookups by ID. Default to Postgres because it's faster and lower-latency.
bq ls --project_id traba-app <dataset> # list tables
bq show --schema traba-app:<dataset>.<table> # JSON schema
bq show --format prettyjson traba-app:<dataset>.<table> # full table info incl. partitioning- Project ID required. Always pass
--project_id=traba-app(or wherever the dataset lives) even when the SQL fully qualifies tables. - Backticks for table refs.
`traba-app.marts.worker_shifts`, not double-quotes. - Dialect. Use
--use_legacy_sql=falsefor standard SQL. Legacy SQL is silently the default in some configurations and will surprise you on syntax (#standardSQLdirective also works). - Casing. BQ identifiers are case-insensitive on lookup but case-sensitive in DDL. Marts tend to be
snake_case; raw Postgres replicas undersrc_pgpreservecamelCase. - Cost. BQ charges per byte scanned. Use
--dry_runto estimate, and prefer partitioned tables with_PARTITIONTIMEfilters or explicit date columns. - Permissions. Some datasets (e.g.,
scores,dialpad) require additional access.bq lswill return "Access Denied: Dataset" if you don't have it; ask before assuming a dataset is missing.
--format=prettyfor human-readable ASCII tables (default for ad-hoc queries).--format=csvor--format=jsonfor machine-readable.- Add
--max_rows=Nfor large result sets (default 100).
- Always use
LIMIT(default 20, max 100 unless the user asks for more). - For exploratory work, start narrow (specific worker/company) before broadening — BQ scans cost real money.
- For spatial queries in Postgres, columns use PostGIS types (
geography,geometry).