tessl install tessl/golang-cloud-google-com--go--bigquery@1.72.0Google Cloud BigQuery client library providing comprehensive Go APIs for querying, loading data, managing datasets and tables, streaming inserts, and accessing BigQuery's ecosystem of services including Storage, Analytics Hub, Data Transfer, and Migration APIs
This document covers executing SQL queries in BigQuery, including parameterized queries, query configuration, result iteration, and query statistics.
BigQuery supports Standard SQL and Legacy SQL for querying data. Queries can be executed synchronously (waiting for results) or asynchronously (starting a job and checking status later).
func (c *Client) Query(q string) *QueryCreate a query:
q := client.Query(`
SELECT name, COUNT(*) as count
FROM ` + "`bigquery-public-data.usa_names.usa_1910_2013`" + `
WHERE year > 2000
GROUP BY name
ORDER BY count DESC
LIMIT 10
`)type Query struct {
JobIDConfig
QueryConfig
}type QueryConfig struct {
// The query to execute
Q string
// Destination table for query results
Dst *Table
// CreateDisposition specifies the circumstances under which the destination table will be created
CreateDisposition TableCreateDisposition
// WriteDisposition specifies how existing data in the destination table is treated
WriteDisposition TableWriteDisposition
// DisableQueryCache prevents results from being cached
DisableQueryCache bool
// DisableFlattenedResults prevents the creation of flattened results
DisableFlattenedResults bool
// AllowLargeResults allows large result sets (when writing to a destination table)
AllowLargeResults bool
// Priority sets the priority of the query
Priority QueryPriority
// MaxBillingTier limits the billing tier for the query
MaxBillingTier int
// MaxBytesBilled limits bytes billed for the query
MaxBytesBilled int64
// UseStandardSQL causes the query to use Standard SQL
UseStandardSQL bool
// UseLegacySQL causes the query to use Legacy SQL
UseLegacySQL bool
// Parameters for parameterized queries
Parameters []QueryParameter
// TimePartitioning for destination table
TimePartitioning *TimePartitioning
// RangePartitioning for destination table
RangePartitioning *RangePartitioning
// Clustering for destination table
Clustering *Clustering
// DestinationEncryptionConfig for destination table
DestinationEncryptionConfig *EncryptionConfig
// SchemaUpdateOptions allows schema modifications
SchemaUpdateOptions []string
// Labels for the query job
Labels map[string]string
// DefaultDatasetID for unqualified table names
DefaultProjectID string
DefaultDatasetID string
// MaximumBytesBilled is deprecated, use MaxBytesBilled
MaximumBytesBilled int64
// ConnectionProperties for external connections
ConnectionProperties []*ConnectionProperty
// CreateSession enables session creation
CreateSession bool
// JobTimeout sets a timeout for the query job
JobTimeout time.Duration
// Reservation for the query
Reservation string
// MaxSlots limits slot usage
MaxSlots int32
}q := client.Query(`SELECT * FROM dataset.table WHERE id = @id`)
q.Parameters = []bigquery.QueryParameter{
{Name: "id", Value: 12345},
}
q.UseLegacySQL = false
q.MaxBytesBilled = 1000000000 // 1 GB limit
q.Priority = bigquery.InteractivePriority
q.Labels = map[string]string{
"team": "analytics",
}func (q *Query) Read(ctx context.Context) (*RowIterator, error)Execute a query and wait for results:
it, err := q.Read(ctx)
if err != nil {
return err
}
for {
var values []bigquery.Value
err := it.Next(&values)
if err == iterator.Done {
break
}
if err != nil {
return err
}
fmt.Println(values)
}Note: Read() uses the optimized jobs.query API for queries with small result sets. For large results, it automatically falls back to creating a job.
func (q *Query) Run(ctx context.Context) (*Job, error)Start a query as a background job:
job, err := q.Run(ctx)
if err != nil {
return err
}
// Save job ID for later retrieval
jobID := job.ID()
fmt.Printf("Query job ID: %s\n", jobID)
// Wait for job completion
status, err := job.Wait(ctx)
if err != nil {
return err
}
if err := status.Err(); err != nil {
return err
}
// Read results
it, err := job.Read(ctx)type QueryParameter struct {
Name string
Value interface{}
}q := client.Query(`
SELECT *
FROM dataset.users
WHERE age > @min_age
AND country = @country
AND created_at > @start_date
`)
q.Parameters = []bigquery.QueryParameter{
{Name: "min_age", Value: 18},
{Name: "country", Value: "US"},
{Name: "start_date", Value: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)},
}q := client.Query(`
SELECT *
FROM dataset.users
WHERE age > ?
AND country = ?
`)
q.Parameters = []bigquery.QueryParameter{
{Value: 18},
{Value: "US"},
}q := client.Query(`
SELECT *
FROM dataset.products
WHERE category IN UNNEST(@categories)
`)
q.Parameters = []bigquery.QueryParameter{
{
Name: "categories",
Value: []string{"electronics", "books", "toys"},
},
}type DateRange struct {
Start time.Time `bigquery:"start"`
End time.Time `bigquery:"end"`
}
q := client.Query(`
SELECT *
FROM dataset.events
WHERE timestamp BETWEEN @range.start AND @range.end
`)
q.Parameters = []bigquery.QueryParameter{
{
Name: "range",
Value: DateRange{
Start: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
End: time.Date(2023, 12, 31, 23, 59, 59, 0, time.UTC),
},
},
}type RowIterator struct {
// Schema is the schema of the table. Available after a call to Next.
Schema Schema
// TotalRows is the total number of rows in the result set.
// Available after a call to Next.
TotalRows uint64
// StartIndex is the index of the first row in the iterator.
// Used for pagination.
StartIndex uint64
}func (it *RowIterator) Next(dst interface{}) error
func (it *RowIterator) PageInfo() *iterator.PageInfo
func (it *RowIterator) ArrowIterator() ArrowIteratorit, err := q.Read(ctx)
if err != nil {
return err
}
for {
var values []bigquery.Value
err := it.Next(&values)
if err == iterator.Done {
break
}
if err != nil {
return err
}
// Access values by index
name := values[0].(string)
count := values[1].(int64)
fmt.Printf("%s: %d\n", name, count)
}type Result struct {
Name string
Count int64
}
it, err := q.Read(ctx)
if err != nil {
return err
}
for {
var result Result
err := it.Next(&result)
if err == iterator.Done {
break
}
if err != nil {
return err
}
fmt.Printf("%s: %d\n", result.Name, result.Count)
}it, err := q.Read(ctx)
if err != nil {
return err
}
for {
var row map[string]bigquery.Value
err := it.Next(&row)
if err == iterator.Done {
break
}
if err != nil {
return err
}
fmt.Printf("Row: %+v\n", row)
}it, err := q.Read(ctx)
if err != nil {
return err
}
// Call Next once to populate schema and total rows
var firstRow []bigquery.Value
if err := it.Next(&firstRow); err != nil && err != iterator.Done {
return err
}
fmt.Printf("Schema: %v\n", it.Schema)
fmt.Printf("Total rows: %d\n", it.TotalRows)it, err := q.Read(ctx)
if err != nil {
return err
}
pageInfo := it.PageInfo()
pageInfo.MaxSize = 1000 // 1000 rows per page
// Get first page
for i := 0; i < pageInfo.MaxSize; i++ {
var row []bigquery.Value
err := it.Next(&row)
if err == iterator.Done {
break
}
if err != nil {
return err
}
// Process row
}
// Get page token for next page
token := pageInfo.Token
// Use token to continue paginationtype ArrowIterator interface {
Next() (*ArrowRecordBatch, error)
Schema() Schema
SerializedArrowSchema() []byte
}type ArrowRecordBatch struct {
Data []byte
Schema []byte
PartitionID string
}Get Arrow format results (experimental):
it, err := q.Read(ctx)
if err != nil {
return err
}
arrowIt := it.ArrowIterator()
for {
batch, err := arrowIt.Next()
if err == iterator.Done {
break
}
if err != nil {
return err
}
// Process Arrow batch
fmt.Printf("Batch size: %d bytes\n", len(batch.Data))
}func NewArrowIteratorReader(it ArrowIterator) io.ReaderConvert Arrow iterator to io.Reader:
arrowIt := it.ArrowIterator()
reader := bigquery.NewArrowIteratorReader(arrowIt)
// Use reader with Arrow libraries
// data, err := ioutil.ReadAll(reader)type QueryPriority string
const (
InteractivePriority QueryPriority = "INTERACTIVE"
BatchPriority QueryPriority = "BATCH"
)Set query priority:
q := client.Query("SELECT * FROM dataset.large_table")
q.Priority = bigquery.BatchPriority // Lower priority, more economicaldataset := client.Dataset("my_dataset")
destTable := dataset.Table("query_results")
q := client.Query("SELECT * FROM source_table WHERE condition")
q.Dst = destTable
q.WriteDisposition = bigquery.WriteTruncate
job, err := q.Run(ctx)
if err != nil {
return err
}
status, err := job.Wait(ctx)
if err != nil {
return err
}type TableCreateDisposition string
const (
CreateIfNeeded TableCreateDisposition = "CREATE_IF_NEEDED"
CreateNever TableCreateDisposition = "CREATE_NEVER"
)type TableWriteDisposition string
const (
WriteEmpty TableWriteDisposition = "WRITE_EMPTY"
WriteTruncate TableWriteDisposition = "WRITE_TRUNCATE"
WriteAppend TableWriteDisposition = "WRITE_APPEND"
)q.CreateDisposition = bigquery.CreateIfNeeded
q.WriteDisposition = bigquery.WriteAppendjob, err := q.Run(ctx)
if err != nil {
return err
}
status, err := job.Wait(ctx)
if err != nil {
return err
}
stats := job.LastStatus().Statistics
queryStats := stats.Details.(*bigquery.QueryStatistics)
fmt.Printf("Total bytes processed: %d\n", queryStats.TotalBytesProcessed)
fmt.Printf("Total bytes billed: %d\n", queryStats.TotalBytesBilled)
fmt.Printf("Cache hit: %v\n", queryStats.CacheHit)
fmt.Printf("Total slot ms: %d\n", queryStats.TotalSlotMs)type QueryStatistics struct {
TotalBytesProcessed int64
TotalBytesBilled int64
BillingTier int64
CacheHit bool
DDLTargetTable *Table
DDLOperationPerformed string
DDLTargetRoutine *Routine
DDLTargetRowAccessPolicy *RowAccessPolicyReference
DDLTargetDataset *DatasetReference
DDLAffectedRowAccessPolicyCount int64
StatementType string
TotalPartitionsProcessed int64
TotalSlotMs int64
ReferencedTables []*Table
ReferencedRoutines []*Routine
Schema Schema
NumDMLAffectedRows int64
DMLStats *DMLStatistics
Timeline []*QueryTimelineIteration
QueryPlan []*ExplainQueryStage
UndeclaredQueryParameters []QueryParameter
SearchStatistics *SearchStatistics
PerformanceInsights *PerformanceInsights
MaterializedViewStatistics *MaterializedViewStatistics
MetadataCacheStatistics *MetadataCacheStatistics
BiEngineStatistics *BIEngineStatistics
DclTargetTable *Table
DclTargetView *Table
DclTargetDataset *DatasetReference
ExportDataStatistics *ExportDataStatistics
ExternalServiceCosts []*ExternalServiceCost
TransactionInfo *TransactionInfo
}type DMLStatistics struct {
InsertedRowCount int64
DeletedRowCount int64
UpdatedRowCount int64
}Get DML statistics:
q := client.Query("DELETE FROM dataset.table WHERE condition")
job, err := q.Run(ctx)
if err != nil {
return err
}
status, err := job.Wait(ctx)
if err != nil {
return err
}
queryStats := job.LastStatus().Statistics.Details.(*bigquery.QueryStatistics)
if queryStats.DMLStats != nil {
fmt.Printf("Deleted rows: %d\n", queryStats.DMLStats.DeletedRowCount)
}type ExplainQueryStage struct {
CompletedParallelInputs int64
ComputeAvg time.Duration
ComputeMax time.Duration
ComputeRatioAvg float64
ComputeRatioMax float64
EndTime time.Time
ID int64
InputStages []int64
Name string
ParallelInputs int64
ReadAvg time.Duration
ReadMax time.Duration
ReadRatioAvg float64
ReadRatioMax float64
RecordsRead int64
RecordsWritten int64
ShuffleOutputBytes int64
ShuffleOutputBytesSpilled int64
StartTime time.Time
Status string
Steps []*ExplainQueryStep
WaitAvg time.Duration
WaitMax time.Duration
WaitRatioAvg float64
WaitRatioMax float64
WriteAvg time.Duration
WriteMax time.Duration
WriteRatioAvg float64
WriteRatioMax float64
}type ExplainQueryStep struct {
Kind string
Substeps []string
}Get query execution plan:
queryStats := job.LastStatus().Statistics.Details.(*bigquery.QueryStatistics)
for _, stage := range queryStats.QueryPlan {
fmt.Printf("Stage %d: %s\n", stage.ID, stage.Name)
fmt.Printf(" Records read: %d\n", stage.RecordsRead)
fmt.Printf(" Records written: %d\n", stage.RecordsWritten)
for _, step := range stage.Steps {
fmt.Printf(" Step: %s\n", step.Kind)
}
}Execute a dry run to estimate costs without running the query:
q := client.Query("SELECT * FROM large_dataset.large_table")
q.DryRun = true
job, err := q.Run(ctx)
if err != nil {
return err
}
queryStats := job.LastStatus().Statistics.Details.(*bigquery.QueryStatistics)
fmt.Printf("This query will process %d bytes\n", queryStats.TotalBytesProcessed)
estimatedCostUSD := float64(queryStats.TotalBytesProcessed) / 1e12 * 5.0 // $5 per TB
fmt.Printf("Estimated cost: $%.2f\n", estimatedCostUSD)Performance insights provide detailed information about query performance issues and optimization opportunities.
type PerformanceInsights struct {
AvgPreviousExecution time.Duration
StagePerformanceStandaloneInsights []*StagePerformanceStandaloneInsight
StagePerformanceChangeInsights []*StagePerformanceChangeInsight
}type StagePerformanceStandaloneInsight struct {
StageID int64
BIEngineReasons []*BIEngineReason
HighCardinalityJoins []*HighCardinalityJoin
SlotContention bool
InsufficientShuffleQuota bool
PartitionSkew *PartitionSkew
}type StagePerformanceChangeInsight struct {
StageID int64
InputDataChange *InputDataChange
}type BIEngineReason struct {
Code string
Message string
}type HighCardinalityJoin struct {
LeftRows int64
RightRows int64
OutputRows int64
StepIndex int64
}type PartitionSkew struct {
SkewSources []*SkewSource
}type SkewSource struct {
StageID int64
}type InputDataChange struct {
RecordsReadDiffPercentage float64
}Performance insights are available in query statistics to help diagnose and optimize query performance:
q := client.Query("SELECT * FROM large_table JOIN other_table USING (id)")
job, err := q.Run(ctx)
if err != nil {
return err
}
status, err := job.Wait(ctx)
if err != nil {
return err
}
queryStats := status.Statistics.Details.(*bigquery.QueryStatistics)
if queryStats.PerformanceInsights != nil {
insights := queryStats.PerformanceInsights
fmt.Printf("Average previous execution: %s\n", insights.AvgPreviousExecution)
// Check for standalone performance issues
for _, standalone := range insights.StagePerformanceStandaloneInsights {
fmt.Printf("\nStage %d performance issues:\n", standalone.StageID)
if standalone.SlotContention {
fmt.Println(" - Slot contention detected")
}
if standalone.InsufficientShuffleQuota {
fmt.Println(" - Insufficient shuffle quota")
}
for _, join := range standalone.HighCardinalityJoins {
fmt.Printf(" - High cardinality join: %d left × %d right = %d output rows\n",
join.LeftRows, join.RightRows, join.OutputRows)
}
if standalone.PartitionSkew != nil {
fmt.Println(" - Partition skew detected")
}
for _, reason := range standalone.BIEngineReasons {
fmt.Printf(" - BI Engine disabled: %s - %s\n", reason.Code, reason.Message)
}
}
// Check for performance changes compared to previous runs
for _, change := range insights.StagePerformanceChangeInsights {
if change.InputDataChange != nil {
fmt.Printf("Stage %d: Input data changed by %.2f%%\n",
change.StageID, change.InputDataChange.RecordsReadDiffPercentage)
}
}
}Set a default dataset for unqualified table names:
q := client.Query("SELECT * FROM users") // Unqualified table name
q.DefaultProjectID = "my-project"
q.DefaultDatasetID = "my_dataset"
// Query will use `my-project.my_dataset.users`type JobIDConfig struct {
JobID string
Location string
// AddJobIDSuffix causes a job ID suffix to be added
AddJobIDSuffix bool
}Specify custom job ID:
q := client.Query("SELECT 1")
q.JobID = "my-custom-job-id"
q.Location = "US"
job, err := q.Run(ctx)Add random suffix to job ID:
q.JobID = "my-job"
q.AddJobIDSuffix = true // Results in "my-job-<random-suffix>"Create a session for multi-statement transactions:
q := client.Query("BEGIN TRANSACTION")
q.CreateSession = true
job, err := q.Run(ctx)
if err != nil {
return err
}
// Get session ID from job
sessionID := job.SessionID()
// Use session in subsequent queries
q2 := client.Query("INSERT INTO table VALUES (1, 'test')")
q2.ConnectionProperties = []*bigquery.ConnectionProperty{
{Key: "session_id", Value: sessionID},
}package main
import (
"context"
"fmt"
"log"
"cloud.google.com/go/bigquery"
"google.golang.org/api/iterator"
)
type SalesData struct {
Date string
Product string
Revenue float64
Units int64
}
func main() {
ctx := context.Background()
client, err := bigquery.NewClient(ctx, "my-project")
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Create parameterized query
q := client.Query(`
SELECT
DATE(timestamp) as date,
product_name as product,
SUM(amount) as revenue,
COUNT(*) as units
FROM ` + "`my-project.sales.transactions`" + `
WHERE DATE(timestamp) BETWEEN @start_date AND @end_date
AND region = @region
GROUP BY date, product
ORDER BY revenue DESC
LIMIT @limit
`)
// Set parameters
q.Parameters = []bigquery.QueryParameter{
{Name: "start_date", Value: "2023-01-01"},
{Name: "end_date", Value: "2023-12-31"},
{Name: "region", Value: "US"},
{Name: "limit", Value: 100},
}
// Configure query
q.Priority = bigquery.InteractivePriority
q.MaxBytesBilled = 10 * 1024 * 1024 * 1024 // 10 GB
q.Labels = map[string]string{
"team": "analytics",
"report": "sales-summary",
}
// Execute query
it, err := q.Read(ctx)
if err != nil {
log.Fatal(err)
}
// Process results
fmt.Printf("Total rows: %d\n", it.TotalRows)
for {
var sales SalesData
err := it.Next(&sales)
if err == iterator.Done {
break
}
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s | %s | $%.2f | %d units\n",
sales.Date, sales.Product, sales.Revenue, sales.Units)
}
}