mcs query workflow
Hard rule
Never run mcs build or mcs package propose while answering a query.
mcs build is maintenance / onboarding work and must be run only when
the user explicitly asks to build, refresh, onboard, or maintain a profile.
Profile resolution
All mcs commands auto-resolve the active profile via --profile →
MCS_PROFILE → cwd-link → env-var fallback. Do not pass identity flags
unless you need to override. Do not cd before running mcs; cwd-link
binding is keyed on the starting directory.
Use mcs -f json ... whenever the next step depends on parsing output.
Read-only by default
mcs sql execute and mcs sql submit are read-only by default. The same
guard is enforced by the MaxCompute client APIs unless a managed write path
sets allow_write=True. They refuse INSERT/UPDATE/DELETE/MERGE/CREATE/DROP/
ALTER/TRUNCATE/GRANT/REVOKE, session mutations such as SET, and any
statement sqlglot can't classify as a known read shape before the cost gate,
returning a WriteOpRejected envelope (exit code 2). Pass --allow-write
only when the user explicitly asked for a write; it confirms write intent but
does not skip the cost gate. For UDF lifecycle use mcs udf *; for profile
rebuilds use mcs build.
Workflow
- Understand the question: metrics, fields, time range, filters, candidate tables.
- Try package-backed schema:
mcs -f json show
mcs -f json show --table T
mcs -f json show --tables T1,T2,T3
- If no semantic package exists, use live metadata:
mcs -f json meta list-tables
mcs -f json meta search-tables KEYWORD
mcs -f json meta search-columns KEYWORD
mcs -f json meta describe-table TABLE
See references/cold-start.md.
- Probe ambiguous low-cardinality literal values before final SQL. See
references/value-discovery.md.
- Compose SQL using
references/rules.md, references/sql.md,
references/projection.md, and references/from-table.md (FROM-table
choice + join cardinality / COUNT(DISTINCT) discipline). Before
deriving an aggregation from scratch, check for a named metric — the
user may have vetted the math once already:mcs -f json metric list
mcs -f json metric show <name>
If one matches, copy its expression verbatim instead of
reimplementing it (same name should mean the same number). Then run
the SQL correctness checklist below before finalizing the SELECT.
- Review SQL:
mcs -f json sql review '<SQL>'
Fix every error-severity issue. If the envelope has
review_mode: syntax_only and semantic_checks_skipped: true, the
profile has no package; fix syntax / dialect / tier issues, ignore
missing semantic hints, and continue with cold-start metadata. Do not
run mcs build to make review more complete.
- Cost-gate real table scans:
mcs -f json sql cost '<SQL>'
Read the JSON verdict; the command exits 0 even on blocked.
- Choose the execution mode from the cost verdict and query shape:
verdict=blocked: do not run; explain the cost and add a tighter
partition filter, predicate, or preview LIMIT.
verdict=confirm: ask the user. After confirmation, prefer async
submit / wait / result and pass -y so the confirmed query does
not stop at the non-TTY cost prompt.
verdict=ok (or cost skipped because the SQL is clearly tiny): use
synchronous execute only for probes and small-result queries:
SELECT 1, schema/value probes, explicit small LIMIT previews, or
tightly partition-filtered lookups/aggregations expected to finish in
the current turn.
- Use async for final analytical scans, joins, or aggregations over real
tables; SQL without a tight partition/filter/
LIMIT; any query after a
prior timeout; or any query the user says can run in the background.
Synchronous path:
mcs -f json sql execute '<SQL>'
execute waits --timeout seconds (default 30). If it exceeds that wait it
does not fail: the instance keeps running, so it returns
data.sync_timed_out: true with data.instance_id (+ data.logview_url /
data.next_step). Continue with the async path below using that
instance_id — do not resubmit the SQL.
Async path:
mcs -f json sql submit -y '<SQL>'
mcs -f json sql wait <instance_id>
mcs -f json sql result <instance_id>
If submit returns data.status == "Submitted" with
data.status_probe_error, the SQL was submitted but the immediate status
probe failed. Keep data.instance_id and use sql status / sql wait
later.
Read data.lifecycle_state, data.terminal, data.successful, and
data.task_statuses[].status_name; do not decide from raw
data.status == "Terminated" because MaxCompute's instance status can be
terminated even when the task failed or was cancelled. Call result only
after data.lifecycle_state == "success".
execute and result cap returned rows at 10000 by default without
rewriting SQL. If data.has_more is true, fetch the next page with
--offset <data.next_offset>; use --max-rows N to change page size.
- If the user confirms correctness, record:
mcs memory verify --question Q --sql '<SQL>' --tables T1,T2
SQL correctness checklist (inline)
The highest-leverage rules from the references — they apply to almost
every query, so they live in the workflow body, not behind --full.
Load mcs skill get query --full only when you need the worked examples.
Projection — SELECT only what the question names.
- "which / who / list X" → project the one column naming X (its label or
id). "what is the highest / total / average X" → project the scalar
aggregate of X, not the group it falls under.
- Columns used only in WHERE / JOIN / ORDER BY are filter / ranking
signal, not output. Don't project intermediate values either.
- Don't wrap with
ROUND / CAST / CONCAT unless the question asked
for that format — it breaks exact result-set comparison.
- One statement per answer: a compound "what is X and list Y" is one
JOIN-ed SELECT, not two
;-separated queries.
FROM — the subject of the question decides the FROM table.
- "how many / what percentage of X" →
FROM x_table with
COUNT(x_table.pk); pull filter tables in via JOIN. Don't count from a
fan-out child table (it inflates the denominator).
- A filter column living in another table → JOIN to it; don't switch the
FROM to the filter's table.
Join cardinality — read the joins_to [..] markers in mcs show.
- After a
[1:n] JOIN where the partner is only in WHERE, use
COUNT(*). Reach for COUNT(DISTINCT pk) ONLY when the question says
"distinct / unique / different X" or a partner column is in SELECT.
Defensive DISTINCT on every 1:n JOIN changes the answer.
References
- Cold-start query path:
references/cold-start.md
- Projection discipline:
references/projection.md
- FROM-table choice + join cardinality:
references/from-table.md
- Literal value probing:
references/value-discovery.md
- MaxCompute rules (syntax, column markers, tier-aware table form):
references/rules.md
- Advanced SQL:
references/sql.md
1---2name: query3description: Use when answering MaxCompute data questions, writing SQL, inspecting schema for a query, using cold-start live metadata, reviewing SQL, cost-gating, executing SQL, or recording verified/failed query memory.4---56# mcs query workflow78## Hard rule910Never run `mcs build` or `mcs package propose` while answering a query.11`mcs build` is maintenance / onboarding work and must be run only when12the user explicitly asks to build, refresh, onboard, or maintain a profile.1314## Profile resolution1516All `mcs` commands auto-resolve the active profile via `--profile` →17`MCS_PROFILE` → cwd-link → env-var fallback. Do not pass identity flags18unless you need to override. Do not `cd` before running `mcs`; cwd-link19binding is keyed on the starting directory.2021Use `mcs -f json ...` whenever the next step depends on parsing output.2223## Read-only by default2425`mcs sql execute` and `mcs sql submit` are read-only by default. The same26guard is enforced by the MaxCompute client APIs unless a managed write path27sets `allow_write=True`. They refuse INSERT/UPDATE/DELETE/MERGE/CREATE/DROP/28ALTER/TRUNCATE/GRANT/REVOKE, session mutations such as `SET`, and any29statement sqlglot can't classify as a known read shape *before* the cost gate,30returning a `WriteOpRejected` envelope (exit code 2). Pass `--allow-write`31only when the user explicitly asked for a write; it confirms write intent but32does not skip the cost gate. For UDF lifecycle use `mcs udf *`; for profile33rebuilds use `mcs build`.3435## Workflow36371. Understand the question: metrics, fields, time range, filters, candidate tables.382. Try package-backed schema:39 ```bash40 mcs -f json show41 mcs -f json show --table T42 mcs -f json show --tables T1,T2,T343 ```443. If no semantic package exists, use live metadata:45 ```bash46 mcs -f json meta list-tables47 mcs -f json meta search-tables KEYWORD48 mcs -f json meta search-columns KEYWORD49 mcs -f json meta describe-table TABLE50 ```51 See `references/cold-start.md`.524. Probe ambiguous low-cardinality literal values before final SQL. See53 `references/value-discovery.md`.545. Compose SQL using `references/rules.md`, `references/sql.md`,55 `references/projection.md`, and `references/from-table.md` (FROM-table56 choice + join cardinality / `COUNT(DISTINCT)` discipline). Before57 deriving an aggregation from scratch, check for a named metric — the58 user may have vetted the math once already:59 ```bash60 mcs -f json metric list61 mcs -f json metric show <name>62 ```63 If one matches, copy its `expression` verbatim instead of64 reimplementing it (same name should mean the same number). Then run65 the **SQL correctness checklist** below before finalizing the SELECT.666. Review SQL:67 ```bash68 mcs -f json sql review '<SQL>'69 ```70 Fix every error-severity issue. If the envelope has71 `review_mode: syntax_only` and `semantic_checks_skipped: true`, the72 profile has no package; fix syntax / dialect / tier issues, ignore73 missing semantic hints, and continue with cold-start metadata. Do not74 run `mcs build` to make review more complete.757. Cost-gate real table scans:76 ```bash77 mcs -f json sql cost '<SQL>'78 ```79 Read the JSON `verdict`; the command exits 0 even on `blocked`.808. Choose the execution mode from the cost verdict and query shape:81 - `verdict=blocked`: do not run; explain the cost and add a tighter82 partition filter, predicate, or preview `LIMIT`.83 - `verdict=confirm`: ask the user. After confirmation, prefer async84 `submit` / `wait` / `result` and pass `-y` so the confirmed query does85 not stop at the non-TTY cost prompt.86 - `verdict=ok` (or cost skipped because the SQL is clearly tiny): use87 synchronous `execute` only for probes and small-result queries:88 `SELECT 1`, schema/value probes, explicit small `LIMIT` previews, or89 tightly partition-filtered lookups/aggregations expected to finish in90 the current turn.91 - Use async for final analytical scans, joins, or aggregations over real92 tables; SQL without a tight partition/filter/`LIMIT`; any query after a93 prior timeout; or any query the user says can run in the background.94 Synchronous path:95 ```bash96 mcs -f json sql execute '<SQL>'97 ```98 `execute` waits `--timeout` seconds (default 30). If it exceeds that wait it99 does **not** fail: the instance keeps running, so it returns100 `data.sync_timed_out: true` with `data.instance_id` (+ `data.logview_url` /101 `data.next_step`). Continue with the async path below using that102 `instance_id` — do **not** resubmit the SQL.103 Async path:104 ```bash105 mcs -f json sql submit -y '<SQL>'106 mcs -f json sql wait <instance_id>107 mcs -f json sql result <instance_id>108 ```109 If `submit` returns `data.status == "Submitted"` with110 `data.status_probe_error`, the SQL was submitted but the immediate status111 probe failed. Keep `data.instance_id` and use `sql status` / `sql wait`112 later.113 Read `data.lifecycle_state`, `data.terminal`, `data.successful`, and114 `data.task_statuses[].status_name`; do not decide from raw115 `data.status == "Terminated"` because MaxCompute's instance status can be116 terminated even when the task failed or was cancelled. Call `result` only117 after `data.lifecycle_state == "success"`.118 `execute` and `result` cap returned rows at 10000 by default without119 rewriting SQL. If `data.has_more` is true, fetch the next page with120 `--offset <data.next_offset>`; use `--max-rows N` to change page size.1219. If the user confirms correctness, record:122 ```bash123 mcs memory verify --question Q --sql '<SQL>' --tables T1,T2124 ```125126## SQL correctness checklist (inline)127128The highest-leverage rules from the references — they apply to almost129every query, so they live in the workflow body, not behind `--full`.130Load `mcs skill get query --full` only when you need the worked examples.131132**Projection — SELECT only what the question names.**133134- "which / who / list X" → project the one column naming X (its label or135 id). "what is the highest / total / average X" → project the scalar136 aggregate of X, not the group it falls under.137- Columns used only in WHERE / JOIN / ORDER BY are filter / ranking138 signal, not output. Don't project intermediate values either.139- Don't wrap with `ROUND` / `CAST` / `CONCAT` unless the question asked140 for that format — it breaks exact result-set comparison.141- One statement per answer: a compound "what is X and list Y" is one142 JOIN-ed SELECT, not two `;`-separated queries.143144**FROM — the subject of the question decides the FROM table.**145146- "how many / what percentage of X" → `FROM x_table` with147 `COUNT(x_table.pk)`; pull filter tables in via JOIN. Don't count from a148 fan-out child table (it inflates the denominator).149- A filter column living in another table → JOIN to it; don't switch the150 FROM to the filter's table.151152**Join cardinality — read the `joins_to [..]` markers in `mcs show`.**153154- After a `[1:n]` JOIN where the partner is only in WHERE, use155 `COUNT(*)`. Reach for `COUNT(DISTINCT pk)` ONLY when the question says156 "distinct / unique / different X" or a partner column is in SELECT.157 Defensive `DISTINCT` on every 1:n JOIN changes the answer.158159## References160161- Cold-start query path: `references/cold-start.md`162- Projection discipline: `references/projection.md`163- FROM-table choice + join cardinality: `references/from-table.md`164- Literal value probing: `references/value-discovery.md`165- MaxCompute rules (syntax, column markers, tier-aware table form): `references/rules.md`166- Advanced SQL: `references/sql.md`