GCP-222 · Lesson 2 of 12 · 19 min
Scheduled queries and transfers
▶ Video coming soon A video version of this lesson is on the way.
Tuesday's session, on October 27, starts with a failure. At 04:10 Aisha's phone showed an alert: the weekly promotions summary had not refreshed. The cause took five minutes to find. The scheduled query that builds the table had been created two years ago by an analyst who has since left Birchline, and when her account was suspended, the query lost the credentials it ran with. Aisha uses the incident to walk Mateo through the simplest orchestration tools in Google Cloud, and the habits that keep them from breaking quietly.
Scheduled queries
A scheduled query is a BigQuery query that runs on a timetable. It is the simplest way to automate SQL: no extra service, no code to deploy, and no charge beyond the query itself. Behind the scenes, scheduled queries are run by the BigQuery Data Transfer Service, which is why they appear on the transfers page of the console and share its run history.
A scheduled query has a few settings that matter. The schedule says when it runs, written in a simple form such as 'every day 07:30' or 'every mon 09:00'. The destination says where results go: a table, with a write preference to overwrite it or append to it, or no destination at all for a query such as a MERGE or a CREATE TABLE statement that writes for itself. Notifications can send an email to the owner on failure or publish a message to a Pub/Sub topic for each run. And the credentials decide whose permissions the query runs with.
That last setting caused this morning's failure. A scheduled query created in the console runs as the person who created it unless a service account is chosen. When that person leaves, the query fails. Birchline's rule since this morning is that every scheduled query in a production project runs as a service account named for its pipeline, with only the roles that pipeline needs, and lists a team, not a person, as its contact.
# Create the nightly curated build as a scheduled query that runs as a service account
bq query --use_legacy_sql=false \
--display_name="nightly curated sales" \
--schedule="every day 07:30" \
--service_account_name=sq-curated-sales@bl-data-prod.iam.gserviceaccount.com \
"CALL curated.build_sales(DATE_SUB(@run_date, INTERVAL 1 DAY))"Run-time parameters
Scheduled queries receive two parameters on every run. @run_time is the moment the run was scheduled for, as a TIMESTAMP in UTC, and @run_date is that moment's date. Using them, rather than CURRENT_DATE(), makes a scheduled query safe to rerun: a manual rerun of Tuesday's run still processes Monday's data. Destination table names can also use the run date, so a query can write to one partition per day by naming the destination with a partition decorator built from the run date.
The parameters are in UTC, and that matters in Canada and the United States. A query scheduled for 07:30 UTC runs at 03:30 in Columbus during daylight saving time, but at 02:30 after the clocks change. In 2026 that change happens on Sunday, November 1, in both countries. Mateo checks what this does to Birchline's schedule.
# Local start time of a fixed 07:30 UTC schedule, before and after the clocks change
schedule_utc = 7 * 60 + 30 # minutes after midnight UTC
offsets = {"October 31 (daylight time)": -4, "November 2 (standard time)": -5}
till_files_due = 2 * 60 # 02:00 local
for day, hours in offsets.items():
local = schedule_utc + hours * 60
h, m = divmod(local, 60)
margin = local - till_files_due
print(f"{day}: starts at {h:02d}:{m:02d} local, {margin} minutes after the till files are due")October 31 (daylight time): starts at 03:30 local, 90 minutes after the till files are due November 2 (standard time): starts at 02:30 local, 30 minutes after the till files are due
What this code does
This takes the scheduled start time in UTC and converts it to local time for a day before and a day after the clocks change, using the offset from UTC on each date. It then compares the local start with the 02:00 deadline for the till files. The output shows that the same UTC schedule starts an hour earlier in local time once standard time begins, which halves the margin between the files arriving and the query reading them.
The margin drops from 90 minutes to 30. That is still positive, but a store whose file arrives late now has less time to catch up before the build. Aisha's preference is to remove the guess altogether, which the next lessons cover: start the build when the files have arrived, not when the clock says they probably have.
The BigQuery Data Transfer Service
The BigQuery Data Transfer Service (DTS) loads data into BigQuery on a schedule from a list of supported sources. They include Cloud Storage, other clouds' object storage such as Amazon S3 and Azure Blob Storage, Google's own advertising and marketing products such as Google Ads and Campaign Manager, YouTube channel reports, and migrations from warehouses such as Teradata and Amazon Redshift. Each transfer is a configuration with a source, a destination dataset, a schedule and credentials, and each run is recorded with its status and logs.
Birchline's marketing team has used a Google Ads transfer since GCP-220. It runs every morning, loads the previous day's campaign statistics, and needs no code. When a campaign account was added in September, Aisha used the transfer's backfill option to load the earlier months for the new account: a backfill runs the transfer for a range of past dates, one run per day. The same feature lets a team recover from a few days of failed runs without writing anything.
| Need | Tool | Notes |
|---|---|---|
| Run SQL inside BigQuery on a timetable | Scheduled query | Uses DTS; @run_time and @run_date parameters |
| Load a supported SaaS or advertising source into BigQuery | BigQuery Data Transfer Service | Managed connector, backfill by date range |
| Load files from Cloud Storage or S3 into BigQuery tables on a schedule | BigQuery Data Transfer Service | Or a load job started by an event |
| Copy files between buckets, clouds or an on-premises file share | Storage Transfer Service | Moves files, not table rows; agents for on-premises |
| Call any HTTP endpoint or publish a message on a timetable | Cloud Scheduler | Cron syntax, time zone per job, retries |
Storage Transfer Service and Cloud Scheduler
Two other services are often confused with DTS. Storage Transfer Service moves files, not table rows: from another cloud, from a URL list, between buckets, or from an on-premises file system through agents. GCP-220 used it for the 40 TB photo library. It can run once or on a schedule, and it can delete files from the source after copying, which is useful for landing areas.
Cloud Scheduler is a managed cron service. A job has a schedule in standard cron syntax, a time zone, and a target: an HTTP endpoint, a Pub/Sub topic or an App Engine app. It retries failed calls according to the job's retry settings. On its own it does no data work, but it is a common way to start other things on a timetable, such as a Workflows execution, a Cloud Run job or a Dataform workflow. Unlike scheduled queries, a Cloud Scheduler job lets you choose the time zone directly, so a job set to 03:30 America/New_York keeps running at 03:30 local time when the clocks change.
# Start a Workflows execution every day at 03:30 local time
gcloud scheduler jobs create http start-nightly-sales \
--location=us-east5 \
--schedule="30 3 * * *" \
--time-zone="America/New_York" \
--uri="https://workflowexecutions.googleapis.com/v1/projects/bl-data-prod/locations/us-east5/workflows/nightly-sales/executions" \
--oauth-service-account-email=wf-nightly-sales@bl-data-prod.iam.gserviceaccount.comWriting scheduled SQL that is safe to rerun
A scheduled query that appends rows doubles them when it is rerun. Aisha's standard pattern for a daily table has two parts. First, the table is partitioned by date. Second, each run replaces only its own day: either by writing to that day's partition with an overwrite preference, or by running a MERGE that matches on a key and updates or inserts. Both make the run idempotent. A third option, DELETE for the day followed by INSERT, also works if both statements run in one multi-statement transaction, so a failure between them does not leave the day empty.
-- Rebuild one day of curated.sales inside a transaction
DECLARE d DATE DEFAULT DATE_SUB(@run_date, INTERVAL 1 DAY);
BEGIN TRANSACTION;
DELETE FROM curated.sales WHERE sale_date = d;
INSERT INTO curated.sales
SELECT * FROM staging.sales_lines WHERE sale_date = d;
COMMIT TRANSACTION;Watching scheduled work
Scheduled queries and transfers fail quietly unless someone is told. Every transfer configuration, including every scheduled query, keeps a run history that shows each run's scheduled time, status, duration and error message, and each run writes entries to Cloud Logging. Aisha's checklist for any new schedule has three items: turn on failure notifications to the team's address rather than a person's, publish run notifications to a Pub/Sub topic that the team's alerting reads, and record the schedule, its owner and its expected finish time on the pipeline's wiki page.
A failed run is not the only problem worth catching. A run can succeed while doing nothing useful: a transfer from Cloud Storage that finds no new files, or a scheduled query whose source table was empty because an upstream load was late. Both show a green tick. Aisha adds a small check after the important ones, a query that counts the rows loaded for the run date and raises an error with the ERROR() function if the count is zero or far below normal. Lesson 5 turns that idea into proper freshness and volume alerts.
Finally, schedules need housekeeping. Each quarter, the team lists every scheduled query and transfer in the production projects, with its owner, credentials and last successful run. Anything owned by a departed employee is moved to a service account, anything that has failed for weeks without anyone noticing is either fixed or deleted, and anything nobody can explain is paused for a month before removal. The promotions summary that failed this morning would have been caught by that review.
Worked problem
Choosing the schedule for the Ads transfer and the build
The Google Ads transfer usually finishes within 40 minutes of starting, and its data for a day is complete by 23:00 local time. The curated marketing table needs the Ads data and must be ready by 07:00 local. A scheduled query takes about 15 minutes. Choose start times in local time with at least 60 minutes of margin before each dependency.
Show solution
- The transfer should start after the day is complete: 23:00 plus a 60-minute margin is 00:00, so start it at 00:00 local.
- It normally finishes by 00:40, and allowing the 60-minute margin means the build should not start before 01:40.
- The build takes 15 minutes, so starting at 01:40 finishes at 01:55, far ahead of 07:00.
- Set the times with a time zone, or adjust the UTC schedule when the clocks change, so the margins do not shrink by an hour in November.
Answer: Start the transfer at 00:00 and the build at 01:40 local time, with notifications on failure for both. Better still, start the build when the transfer reports success, which removes the guessed margin entirely.
Common mistakes
MistakeCreating production scheduled queries under a personal account.
InsteadRun them as a service account with only the roles the pipeline needs, and list a team as the contact.
MistakeUsing CURRENT_DATE() in scheduled SQL.
InsteadUse @run_date or @run_time so a rerun processes the day it was meant to process.
MistakeForgetting that scheduled query times are in UTC.
InsteadConvert to local time, check both sides of the daylight saving change, or start the job from Cloud Scheduler with a time zone.
MistakeConfusing Storage Transfer Service with the BigQuery Data Transfer Service.
InsteadStorage Transfer Service copies files between storage systems; DTS loads data into BigQuery tables.
MistakeAppending results every run.
InsteadOverwrite one partition or MERGE on a key so reruns do not duplicate rows.
Summary and key terms
- Scheduled queries run SQL on a timetable through the BigQuery Data Transfer Service, with no extra service to manage.
- Their key settings are the schedule, the destination and write preference, notifications and the credentials they run as; production queries should run as service accounts.
- The @run_time and @run_date parameters, both in UTC, make reruns safe.
- DTS loads data from supported sources such as Cloud Storage, Amazon S3, Google Ads and warehouse migrations, and can backfill a range of dates.
- Storage Transfer Service copies files, and Cloud Scheduler calls endpoints on a cron schedule in a chosen time zone.
- Daily SQL should replace one day at a time, with MERGE, a partition overwrite or a transaction.
- Scheduled query
- A BigQuery query that runs automatically on a timetable.
- @run_date
- The UTC date of the scheduled run time, passed to a scheduled query.
- BigQuery Data Transfer Service
- A managed service that loads data into BigQuery from supported sources on a schedule.
- Backfill
- Running a transfer or pipeline for a range of past dates.
- Storage Transfer Service
- A service that copies files between clouds, buckets and on-premises storage.
- Cloud Scheduler
- A managed cron service that calls HTTP, Pub/Sub or App Engine targets on a schedule.
- Service account
- An identity for a workload rather than a person, used to run pipelines.
- Write preference
- Whether a query overwrites or appends to its destination table.
Practice problems
Work each problem on paper first, then reveal the answer to check.
What two parameters does every scheduled query receive?
Show answer
@run_time, a UTC timestamp, and @run_date, its date.
Why might a fixed UTC schedule start an hour earlier in local time in November?
Show answer
Because the clocks change from daylight to standard time and the local offset from UTC changes from 4 to 5 hours.
A team needs to copy 2 TB of files from Amazon S3 into a bucket every night. Which service fits?
Show answer
Storage Transfer Service.
Why must DELETE and INSERT for one day run inside a transaction?
Show answer
So a failure between them cannot leave the day empty or half loaded.
Lesson quiz · 5 questions
Score 60% or more to complete this lesson.
Create a free account to save your progress, take quizzes and earn a certificate.