# BigQuery jobs > Every query, load, export and copy in BigQuery runs as a job with an ID, a state and a project that is billed. How jobs work, what the job ID tells you, how to list, cancel and repeat them, and why a failed query is still a finished job. Source: https://docs.querylab.io/bigquery/jobs/ Updated: 2026-10-14 A job is the record BigQuery keeps for one piece of work. Every time you run a query, load data, export a table or copy a table, BigQuery creates a job, runs it asynchronously and keeps its details: type, state, bytes processed, who ran it, and the error if it failed. The job belongs to a project, and that project is billed. ## The four job types BigQuery has four job types: `query`, `load`, `extract` (export) and `copy`. In the job resource, the `configuration` names which type it is. Quick actions such as listing tables or reading a table's metadata are not jobs. A query job reads data and writes its result to a table. If you gave no destination table, the result goes to a temporary table that BigQuery keeps for about 24 hours, which is what makes [cached results](https://docs.querylab.io/bigquery/query-cache/) possible. A query that reads no tables, such as `SELECT 1`, is still a job. A dry run is the one exception. It validates the query and reports bytes, and it doesn't create a job. We checked this by dry-running a query with a fixed job ID and then asking for that job: "Not found". **a dry run reports bytes and creates no job** ```sql SELECT corpus, COUNT(*) AS lines FROM `bigquery-public-data.samples.shakespeare` GROUP BY corpus ``` BigQuery dry run: this query processes 2.4 MB. ## Job states A job is `PENDING` while it waits to be scheduled, `RUNNING` while it works, and `DONE` when it has stopped. `DONE` does not mean success. A failed job is also `DONE`, with an `errorResult` in its status. A job that has no `errorResult` succeeded, although a load job may still report nonfatal problems, such as a few rows it skipped, in `status.errors`. Here is a query that fails: **a query that fails at run time** ```sql SELECT 7 / 0 AS ratio ``` BigQuery rejects this query: ```text division by zero: 7 / 0 ``` The query was valid, so BigQuery accepted it and created a job. Its status came back `DONE` with the error `division by zero: 7 / 0`, reason `invalidQuery`. Code that polls a job should read `errorResult` when the state is `DONE` rather than treat `DONE` as success. ## The job ID Every job has an ID and a location. The console shows the fully qualified form `project:location.job_id`, for example `my-project-1234:US.bquijob_123x456_123y123z123c`. The `bq` tool prints the short form, `job_id` alone, and uses `--location` or the default location to find it. A job ID is only unique together with its project and location. In Querylab.io, the Info view of a result shows the job id, location, bytes processed and billed, and slot time (see [Results grid](https://docs.querylab.io/results/results-grid/)). The project in the ID is the project that is billed. For a query job on on-demand pricing, the cost goes to the project that runs it, even if the data is in another project. The client libraries generate a job ID for you, and you can supply your own, or a prefix. Job IDs are unique per project and location. We ran the same query twice with one fixed job ID. The first run worked. The second failed with `Already Exists: Job :US.`, without running the query again. A job ID you generate yourself can therefore act as an idempotency key: if a network error leaves you unsure whether a load was submitted, resubmitting with the same ID reports that the job already exists instead of loading twice. ## Scripts make several jobs A multi-statement query is one parent job with a child job for each statement. In our test, `SELECT 1 AS a; SELECT 2 AS b;` produced three rows in `INFORMATION_SCHEMA.JOBS_BY_PROJECT`: the parent with `statement_type` `SCRIPT`, and two children with `parent_job_id` set to the parent's ID and `statement_type` `SELECT`. The result of the script is the result of the last statement that returns rows: **a script returns its last result** ```sql CREATE TEMP TABLE orders AS SELECT 1 AS id, 40 AS total UNION ALL SELECT 2, 15; SELECT COUNT(*) AS order_count FROM orders; SELECT SUM(total) AS revenue FROM orders; ``` Result in BigQuery (1 row): | revenue | | --- | | 55 | The `COUNT(*)` result is not returned. Only the revenue is. Each child job has its own row, so a long script can be looked at statement by statement. You can delete the metadata of a parent job, which deletes its children too, but you can't delete a child alone. ## Listing jobs BigQuery keeps six months of job history for all jobs in a project, across all locations. In the console it is under Job history in the Explorer, with a personal view and a project view. Querylab.io's [Query history](https://docs.querylab.io/writing-sql/query-history/) tab lists the jobs your projects ran, from Querylab.io or any other tool, with bytes, cost and status, and searches by status, type, user and job id. In SQL, the `INFORMATION_SCHEMA.JOBS` views have one row per job, qualified by a region: ```sql -- Not run: reads your project's job history SELECT job_id, job_type, statement_type, state, total_bytes_billed FROM `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT WHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY) ORDER BY creation_time DESC LIMIT 20; ``` `bq ls -j` lists jobs and `bq show -j ` shows one. Listing other users' jobs needs more than the roles that run jobs. `roles/bigquery.jobUser` and `roles/bigquery.user` let you run jobs and see your own, and listing every job needs `bigquery.jobs.listAll`, which is in `roles/bigquery.resourceAdmin` and `roles/bigquery.admin`. Without it, details of other users' jobs are redacted. For monitoring queries on cost and slots, see [monitoring jobs and costs](https://docs.querylab.io/bigquery/monitoring-jobs-and-costs/). ## Cancelling a job You can cancel a job that is `PENDING` or `RUNNING`. Elsewhere, cancel it from the console, with `bq cancel`, through the API, or in SQL: ```sql -- Not run: cancels a job in the current project CALL BQ.JOBS.CANCEL('job_id'); ``` The procedure returns immediately and BigQuery does the cancellation after that. A cancel request can lose the race: the job may finish first, or sit in a stage that can't be cancelled. Cancellation usually takes under a minute. Calling it with an ID that doesn't exist fails with `Not found: Job :US.`. ## Deleting and repeating Job metadata can be deleted once the job is done, with `bq rm -j `, for example to remove sensitive values that sit in a query's text. A job can't be repeated under the same job ID. Repeating one means submitting a new job with the same configuration: the console and `bq` assign a new ID, and with the API or a client library you generate one. ## Jobs and locations A query, load or extract job runs in the same location as the tables it references. This is the reason a job ID carries a location and why `bq show -j` needs `--location` for a short ID. See [locations](https://docs.querylab.io/bigquery/locations/). Open the playground at https://app.querylab.io?playground=true&utm_source=docs&utm_medium=concepts to run it. It is the real app on public BigQuery datasets and needs no sign-in. ## Sources Based on the BigQuery documentation by Google, CC BY 4.0, rewritten with examples checked against BigQuery: - [Manage jobs](https://cloud.google.com/bigquery/docs/managing-jobs) - [Running jobs programmatically](https://cloud.google.com/bigquery/docs/running-jobs) - [Cached results](https://cloud.google.com/bigquery/docs/cached-results) - [Multi-statement queries](https://cloud.google.com/bigquery/docs/multi-statement-queries)