Branch8

BigQuery Data Engineering Best Practices for Retail: A Step-by-Step Guide

Matt Li
July 2, 2026
14 mins read
BigQuery Data Engineering Best Practices for Retail: A Step-by-Step Guide - Hero Image

Key Takeaways

  • Partition retail tables by business date, not ingestion date, to cut scan costs 90%
  • Use `require_partition_filter` on every fact table to prevent accidental full scans
  • Incremental dbt models reduce transformation cost by 80-95% on large tables
  • Switch to BigQuery editions pricing once you exceed 5 TB monthly processing
  • Build currency and timezone conversion into dbt models, not your BI tool

Quick Answer: The most impactful BigQuery best practices for retail are partitioning fact tables by business date (not ingestion date), enforcing partition filters, using incremental dbt models, and switching to editions pricing above 5 TB monthly. These typically reduce costs 50-75% while improving query performance.


Most retail data teams start their BigQuery journey by recreating their on-premise warehouse schema verbatim in the cloud. This is the single most expensive mistake you can make. BigQuery is not Teradata, it is not Redshift, and treating it like a traditional RDBMS will cost you three to five times more than necessary while delivering worse query performance.

Related reading: Salesforce CRM Implementation Cost Breakdown APAC: Real Numbers from Real Engagements

Related reading: CDP vs CRM: What APAC Retailers Need to Make the Right Call

Related reading: n8n Workflow Automation for Retail Ops Teams: A Step-by-Step Guide

I have watched this play out repeatedly across Asia-Pacific retail clients. When we helped a Hong Kong-based omnichannel retailer migrate 4.2 TB of transaction history to BigQuery in early 2024, the initial architecture drafted by their incumbent SI produced estimated monthly costs of USD 8,400 on on-demand pricing. After restructuring with BigQuery data engineering best practices for retail — proper partitioning, clustering, and a dbt-driven transformation layer — we brought that down to USD 2,100 per month. Same data, same analytical outputs, 75% cost reduction.

This guide walks through the specific steps we follow when building retail data platforms on BigQuery. It is opinionated, based on real project outcomes, and includes copy-pasteable code you can adapt immediately.

Related reading: B2B E-Commerce Platform Replatforming Guide: Decision Framework for APAC Manufacturers

Prerequisites

Before starting, confirm you have the following in place:

  • Google Cloud project with billing enabled and BigQuery API activated
  • BigQuery Admin or BigQuery Data Editor IAM role assigned to your service account
  • gcloud CLI installed (v450+ recommended) and authenticated (gcloud auth login)
  • dbt-core v1.7+ with the dbt-bigquery adapter installed (pip install dbt-bigquery)
  • Python 3.10+ for any custom ingestion scripts
  • Familiarity with SQL and basic understanding of retail data concepts (SKUs, transactions, inventory snapshots)
  • At least one data source ready to ingest — POS exports, Shopify/Magento order feeds, or ERP extracts

Verify your setup:

1gcloud --version
2# Google Cloud SDK 450.0.0 or higher
3
4bq ls --project_id=your-project-id
5# Should list existing datasets (or empty)
6
7dbt --version
8# dbt-core 1.7.x, dbt-bigquery 1.7.x

Step 1: Design Your Dataset Layout for Multi-Market Retail

BigQuery datasets are the top-level organisational unit. For retail operations spanning multiple APAC markets — say Hong Kong, Singapore, and Taiwan — resist the urge to create one dataset per market. Instead, use a functional layout with market identifiers as partition or clustering keys.

Related reading: Global E-Commerce Expansion Trends 2026: The APAC Retailer's Cross-Border Playbook

Here is the dataset structure we deploy for most retail clients:

1# Create datasets with appropriate region
2bq mk --dataset --location=asia-southeast1 \
3 --description="Raw ingested data from all sources" \
4 your-project-id:raw_retail
5
6bq mk --dataset --location=asia-southeast1 \
7 --description="Staging layer: cleaned, typed, deduplicated" \
8 your-project-id:staging_retail
9
10bq mk --dataset --location=asia-southeast1 \
11 --description="Mart layer: business-ready aggregations" \
12 your-project-id:mart_retail
13
14bq mk --dataset --location=asia-southeast1 \
15 --description="Metadata, audit logs, data quality results" \
16 your-project-id:meta_retail

We pick asia-southeast1 (Singapore) as the default for APAC retail projects because it offers sub-50ms latency to Hong Kong, Kuala Lumpur, and Manila. According to Google Cloud's documentation on data residency, keeping compute and storage in the same region eliminates cross-region data transfer fees entirely.

Why functional datasets over market-based datasets? Cross-market analytics — currency conversion, regional inventory rebalancing, consolidated P&L — requires joining across datasets when you split by market. BigQuery charges for bytes scanned regardless of dataset boundaries, but functional grouping keeps your dbt project cleaner and your RBAC policies simpler.

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

Step 2: Set Up Partitioning That Matches Retail Query Patterns

Partitioning is where retail-specific thinking matters most. Generic BigQuery guides recommend partitioning by ingestion time. For retail, this is almost always wrong.

Retail analysts query by transaction date, not ingestion date. Your store manager wants last Saturday's sales. Your merchandiser wants week-over-week comparison by product category. Your finance team wants month-end close numbers. Every one of these queries filters on the business date.

1-- Transaction fact table: partition by order_date, cluster by market and category
2CREATE TABLE `your-project-id.raw_retail.orders`
3(
4 order_id STRING NOT NULL,
5 order_date DATE NOT NULL,
6 market_code STRING NOT NULL, -- HK, SG, TW, AU
7 store_id STRING,
8 channel STRING, -- online, pos, marketplace
9 customer_id STRING,
10 currency_code STRING,
11 total_amount NUMERIC,
12 total_items INT64,
13 created_at TIMESTAMP,
14 _ingested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP()
15)
16PARTITION BY order_date
17CLUSTER BY market_code, channel
18OPTIONS (
19 partition_expiration_days = NULL,
20 require_partition_filter = TRUE,
21 description = "Raw order transactions from all markets"
22);

Key decisions in this DDL:

  • PARTITION BY order_date — daily partitions align with how retail teams think. Google's own cost optimisation guide confirms that partition pruning can reduce bytes scanned by 90% or more on well-structured tables.
  • CLUSTER BY market_code, channel — clustering further reduces scan volume when analysts filter by market or channel. BigQuery supports up to four clustering columns; put the highest-cardinality filter first.
  • require_partition_filter = TRUE — this prevents accidental full-table scans. A junior analyst running SELECT * FROM orders without a date filter will get an error instead of a USD 50 bill.

For inventory snapshots, use a different approach:

1CREATE TABLE `your-project-id.raw_retail.inventory_snapshots`
2(
3 snapshot_date DATE NOT NULL,
4 sku STRING NOT NULL,
5 store_id STRING NOT NULL,
6 market_code STRING NOT NULL,
7 quantity_on_hand INT64,
8 quantity_reserved INT64,
9 quantity_in_transit INT64,
10 _ingested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP()
11)
12PARTITION BY snapshot_date
13CLUSTER BY market_code, sku
14OPTIONS (
15 partition_expiration_days = 730, -- 2 year retention
16 require_partition_filter = TRUE
17);

Notice the partition_expiration_days = 730. Inventory snapshots grow fast — a retailer with 50,000 SKUs across 200 stores generates 10 million rows daily. At two years of history, that is 7.3 billion rows. Setting expiration keeps storage costs predictable. According to Google Cloud pricing as of 2024, BigQuery long-term storage drops to USD 0.01 per GB after 90 days of no modification, but eliminating unneeded partitions entirely is cheaper still.

Step 3: Implement Cost Governance Before You Need It

Retail data volumes grow faster than most teams anticipate. A single mid-sized omnichannel retailer can generate 2-5 GB of raw event data daily from web analytics, POS, and marketplace feeds. Without guardrails, a single poorly-written query can scan terabytes and cost hundreds of dollars.

Set up these controls on day one:

1# Set a per-user daily query limit (in bytes)
2# 1 TB = 1000000000000 bytes
3bq update --max_bytes_billed=1000000000000 \
4 your-project-id:raw_retail
5
6# Create a custom cost control using BigQuery reservations
7# For predictable workloads, switch to editions pricing
8gcloud bigquery reservations create retail-analytics \
9 --project=your-project-id \
10 --location=asia-southeast1 \
11 --slots=100 \
12 --edition=standard

For retail projects processing between 5-50 TB monthly, we consistently find that BigQuery Standard edition with 100 slots costs 30-50% less than on-demand pricing. Google's own pricing calculator confirms this breakpoint — Forrester's 2023 Total Economic Impact study for BigQuery pegged median cost savings at 26% when moving from on-demand to capacity-based pricing.

Additionally, set up INFORMATION_SCHEMA monitoring to catch expensive queries proactively:

1-- Find the top 10 most expensive queries in the past 7 days
2SELECT
3 user_email,
4 job_id,
5 query,
6 total_bytes_processed / POW(1024, 3) AS gb_processed,
7 total_slot_ms / 1000 AS slot_seconds,
8 creation_time
9FROM `region-asia-southeast1`.INFORMATION_SCHEMA.JOBS
10WHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
11 AND job_type = 'QUERY'
12 AND state = 'DONE'
13ORDER BY total_bytes_processed DESC
14LIMIT 10;

We schedule this query to run daily via Cloud Scheduler and pipe results to a Slack channel. When we deployed this for a Taiwanese fashion retailer, we caught a Looker dashboard that was running an unfiltered scan every 15 minutes — costing USD 1,200 per month unnecessarily.

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

Step 4: Build Your dbt Transformation Layer

Raw data in BigQuery is only valuable once it is modelled for consumption. dbt (data build tool) is the standard for retail BigQuery projects, and for good reason — it brings version control, testing, and documentation to SQL transformations.

Initialise your dbt project:

1dbt init retail_analytics
2cd retail_analytics

Configure profiles.yml for BigQuery:

1# ~/.dbt/profiles.yml
2retail_analytics:
3 target: dev
4 outputs:
5 dev:
6 type: bigquery
7 method: oauth
8 project: your-project-id
9 dataset: staging_retail
10 location: asia-southeast1
11 threads: 8
12 timeout_seconds: 300
13 priority: interactive
14 maximum_bytes_billed: 500000000000 # 500 GB safety net
15 prod:
16 type: bigquery
17 method: service-account
18 project: your-project-id
19 dataset: mart_retail
20 location: asia-southeast1
21 threads: 16
22 timeout_seconds: 600
23 keyfile: /secrets/bq-service-account.json
24 priority: batch

Notice the maximum_bytes_billed in the dev profile — this prevents developers from accidentally running expensive transformations during development.

Create your staging model for orders:

1-- models/staging/stg_orders.sql
2{{
3 config(
4 materialized='incremental',
5 partition_by={
6 "field": "order_date",
7 "data_type": "date",
8 "granularity": "day"
9 },
10 cluster_by=["market_code", "channel"],
11 unique_key='order_id',
12 on_schema_change='sync_all_columns'
13 )
14}}
15
16WITH source AS (
17 SELECT
18 *,
19 ROW_NUMBER() OVER (
20 PARTITION BY order_id
21 ORDER BY _ingested_at DESC
22 ) AS row_num
23 FROM {{ source('raw_retail', 'orders') }}
24 {% if is_incremental() %}
25 WHERE _ingested_at > (
26 SELECT MAX(_ingested_at) FROM {{ this }}
27 )
28 {% endif %}
29),
30
31deduped AS (
32 SELECT * FROM source WHERE row_num = 1
33)
34
35SELECT
36 order_id,
37 order_date,
38 market_code,
39 store_id,
40 channel,
41 customer_id,
42 currency_code,
43 CAST(total_amount AS NUMERIC) AS total_amount_local,
44 total_items,
45 created_at,
46 _ingested_at
47FROM deduped

The incremental materialisation is critical for retail. Processing only new records since the last run keeps transformation costs proportional to data volume growth, not total table size. The dbt documentation on BigQuery incremental models confirms this pattern reduces both cost and execution time by 80-95% compared to full-refresh on tables exceeding 100 million rows.

Now create a mart model for daily sales aggregation:

1-- models/marts/mart_daily_sales.sql
2{{
3 config(
4 materialized='incremental',
5 partition_by={
6 "field": "order_date",
7 "data_type": "date",
8 "granularity": "day"
9 },
10 cluster_by=["market_code"],
11 unique_key=['order_date', 'market_code', 'channel', 'store_id']
12 )
13}}
14
15SELECT
16 order_date,
17 market_code,
18 channel,
19 store_id,
20 COUNT(DISTINCT order_id) AS total_orders,
21 COUNT(DISTINCT customer_id) AS unique_customers,
22 SUM(total_amount_local) AS gross_revenue_local,
23 SUM(total_items) AS total_units_sold,
24 SAFE_DIVIDE(
25 SUM(total_amount_local),
26 COUNT(DISTINCT order_id)
27 ) AS avg_order_value_local
28FROM {{ ref('stg_orders') }}
29{% if is_incremental() %}
30WHERE order_date > (
31 SELECT MAX(order_date) FROM {{ this }}
32) - INTERVAL 3 DAY -- reprocess 3-day window for late-arriving data
33{% endif %}
34GROUP BY 1, 2, 3, 4

The INTERVAL 3 DAY lookback handles a common retail data problem: late-arriving transactions from offline stores that batch-upload POS data with delays. In Southeast Asian markets especially, we have seen stores in Indonesia and Philippines submit POS data 24-72 hours late due to connectivity issues.

Step 5: Add Data Quality Tests for Retail-Specific Edge Cases

Retail data has predictable failure modes. Duplicate orders from marketplace integrations, negative quantities from returns processed incorrectly, and currency mismatches across markets. Catch these with dbt tests:

1# models/staging/schema.yml
2version: 2
3
4models:
5 - name: stg_orders
6 description: "Deduplicated, typed order transactions"
7 columns:
8 - name: order_id
9 tests:
10 - unique
11 - not_null
12 - name: order_date
13 tests:
14 - not_null
15 - dbt_utils.accepted_range:
16 min_value: "'2020-01-01'"
17 max_value: "CURRENT_DATE()"
18 - name: market_code
19 tests:
20 - accepted_values:
21 values: ['HK', 'SG', 'TW', 'AU', 'MY', 'ID', 'PH', 'VN']
22 - name: total_amount_local
23 tests:
24 - dbt_utils.accepted_range:
25 min_value: 0
26 max_value: 1000000
27 config:
28 severity: warn # Returns can be negative; warn, don't fail
29 - name: channel
30 tests:
31 - accepted_values:
32 values: ['online', 'pos', 'marketplace', 'wholesale']

Run the tests:

1dbt test --select stg_orders
2# Expected output:
3# Finished running 7 tests in 0 hours 0 minutes and 12.45 seconds
4# Completed successfully

According to Monte Carlo's 2024 State of Data Quality report, organisations implementing automated data quality testing reduce data incidents by 60%. For retail, where a wrong price or inventory count directly impacts revenue, this is not optional.

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

Step 6: Optimise Query Performance for Omnichannel Reporting

Retail dashboards have specific performance requirements. A store operations dashboard needs to load in under 3 seconds. A weekly merchandising report might tolerate 15 seconds. Here are the patterns that consistently deliver sub-second performance on retail datasets up to 50 TB.

Use Materialised Views for High-Frequency Queries

1-- Materialised view for real-time store performance
2CREATE MATERIALIZED VIEW `your-project-id.mart_retail.mv_store_performance`
3PARTITION BY order_date
4CLUSTER BY market_code, store_id
5AS
6SELECT
7 order_date,
8 market_code,
9 store_id,
10 channel,
11 COUNT(*) AS order_count,
12 SUM(total_amount_local) AS revenue,
13 COUNT(DISTINCT customer_id) AS unique_customers
14FROM `your-project-id.staging_retail.stg_orders`
15WHERE order_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY)
16GROUP BY 1, 2, 3, 4;

Materialised views in BigQuery auto-refresh and the optimiser automatically routes queries to them when applicable. Google's documentation states that materialised views can improve query performance by up to 10x while reducing costs because they scan pre-aggregated data.

Leverage BI Engine for Dashboard Acceleration

1# Reserve BI Engine capacity for your mart dataset
2bq update --bi_reservation_size=2 \
3 --project_id=your-project-id \
4 --location=asia-southeast1

BI Engine caches frequently-accessed data in memory. For a retail client running 50+ Looker dashboards, we saw p95 query latency drop from 4.2 seconds to 0.3 seconds after enabling 2 GB of BI Engine reservation. At USD 36.50 per GB per month (Google Cloud BigQuery BI Engine pricing, 2024), the ROI is obvious for any team running more than a handful of dashboards.

Step 7: Handle Multi-Currency and Multi-Timezone Correctly

This is where APAC retail gets tricky. A retailer selling in Hong Kong (HKD), Singapore (SGD), Taiwan (TWD), and Australia (AUD) needs consolidated reporting in a base currency. Build currency conversion into your dbt models, not your BI tool.

1-- models/staging/stg_exchange_rates.sql
2{{
3 config(
4 materialized='table',
5 partition_by={
6 "field": "rate_date",
7 "data_type": "date"
8 }
9 )
10}}
11
12SELECT
13 rate_date,
14 source_currency,
15 target_currency,
16 exchange_rate
17FROM {{ source('raw_retail', 'daily_exchange_rates') }}
18WHERE target_currency = 'USD' -- Standardise to USD as base
1-- models/marts/mart_daily_sales_usd.sql
2SELECT
3 s.order_date,
4 s.market_code,
5 s.channel,
6 s.store_id,
7 s.total_orders,
8 s.gross_revenue_local,
9 s.gross_revenue_local * COALESCE(fx.exchange_rate, 1) AS gross_revenue_usd,
10 s.avg_order_value_local * COALESCE(fx.exchange_rate, 1) AS aov_usd,
11 s.unique_customers,
12 s.total_units_sold
13FROM {{ ref('mart_daily_sales') }} s
14LEFT JOIN {{ ref('stg_exchange_rates') }} fx
15 ON s.order_date = fx.rate_date
16 AND s.currency_code = fx.source_currency

For timezone handling, always store timestamps in UTC at the raw layer and convert at the mart layer:

1-- Timezone mapping for APAC retail markets
2CASE market_code
3 WHEN 'HK' THEN TIMESTAMP(created_at, 'Asia/Hong_Kong')
4 WHEN 'SG' THEN TIMESTAMP(created_at, 'Asia/Singapore')
5 WHEN 'TW' THEN TIMESTAMP(created_at, 'Asia/Taipei')
6 WHEN 'AU' THEN TIMESTAMP(created_at, 'Australia/Sydney')
7 WHEN 'VN' THEN TIMESTAMP(created_at, 'Asia/Ho_Chi_Minh')
8 WHEN 'PH' THEN TIMESTAMP(created_at, 'Asia/Manila')
9 ELSE created_at
10END AS local_created_at

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

Step 8: Automate Orchestration With Minimal Overhead

For most retail BigQuery projects, you do not need Airflow. That is a controversial statement, but hear me out: dbt Cloud or a simple Cloud Scheduler + Cloud Run setup handles 90% of retail ELT orchestration needs at a fraction of the operational cost.

1# Deploy dbt run as a Cloud Run job
2gcloud run jobs create dbt-daily-run \
3 --image=gcr.io/your-project-id/dbt-retail:latest \
4 --region=asia-southeast1 \
5 --task-timeout=1800 \
6 --max-retries=2 \
7 --set-env-vars="DBT_TARGET=prod" \
8 --command="dbt" \
9 --args="run,--select,tag:daily"
10
11# Schedule it for 6 AM SGT (10 PM UTC previous day)
12gcloud scheduler jobs create http dbt-daily-schedule \
13 --location=asia-southeast1 \
14 --uri="https://asia-southeast1-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/your-project-id/jobs/dbt-daily-run:run" \
15 --http-method=POST \
16 --schedule="0 22 * * *" \
17 --time-zone="UTC" \
18 --oauth-service-account-email=dbt-runner@your-project-id.iam.gserviceaccount.com

This setup costs under USD 5 per month for daily runs — compared to USD 300-500 per month for a managed Airflow instance on Cloud Composer. We deploy Airflow only when a client has complex dependency chains across 200+ dbt models or needs to orchestrate non-SQL workloads like ML training pipelines.

How We Applied These BigQuery Data Engineering Best Practices for a Retail Client

In Q3 2023, Branch8 was engaged by a Hong Kong-based lifestyle retail group operating 80+ stores across Hong Kong and Macau with a growing e-commerce presence on Shopify Plus. Their existing setup involved nightly CSV exports from their ERP (SAP Business One) loaded into Google Sheets, then manually reconciled with Shopify data by a team of three analysts.

Over six weeks, we implemented the full architecture described above: BigQuery as the warehouse, Fivetran for Shopify and SAP connectors, dbt-core v1.7 for transformations (43 models across staging and mart layers), and Looker for dashboards. The project involved 12 TB of historical data migration and daily incremental loads averaging 3.2 GB.

Results after 90 days: monthly data infrastructure cost of USD 1,850 (BigQuery Standard edition + Fivetran), daily reporting available by 7 AM instead of noon, and the three analysts reallocated from manual reconciliation to actual analysis work. The CFO told us the inventory visibility alone — seeing stock positions across all stores updated hourly instead of daily — prevented an estimated HKD 2M in missed sales during the Mid-Autumn Festival peak.

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

What to Do Next

You now have a production-grade BigQuery data engineering architecture for retail. Before moving forward, use this decision checklist:

  • Partitioning: Are all fact tables partitioned by business date (not ingestion date) with require_partition_filter = TRUE?
  • Clustering: Have you identified the top 2-3 filter columns per table from actual dashboard usage patterns?
  • Cost governance: Is maximum_bytes_billed set on every dbt profile and every BI tool connection?
  • Pricing model: Are you processing more than 5 TB monthly? If yes, run the numbers on BigQuery editions vs on-demand.
  • Incremental models: Are all dbt models on tables larger than 10M rows using incremental materialisation?
  • Data quality: Do you have dbt tests covering uniqueness, nulls, and accepted values on every staging model?
  • Currency/timezone: Is conversion handled in the transformation layer, not the BI layer?
  • Late-arriving data: Does your incremental strategy include a lookback window for late POS data?

If you are running a retail operation across multiple APAC markets and need help building or optimising your BigQuery data platform, reach out to Branch8. We have built these systems for retailers ranging from 20-store chains to 500+ location enterprises across the region.

Further Reading

FAQ

BigQuery works best as an analytical warehouse, not an operational database. For retail, ingest raw data from POS, e-commerce, and ERP systems into BigQuery, then use dbt to transform it into business-ready mart tables. Avoid using BigQuery for transactional workloads — pair it with Cloud SQL or Firestore for operational queries that need sub-10ms response times.

About the Author

Matt Li

Co-Founder & CEO, Branch8 & Second Talent

Matt Li is Co-Founder and CEO of Branch8, a Y Combinator-backed (S15) Adobe Solution Partner and e-commerce consultancy headquartered in Hong Kong, and Co-Founder of Second Talent, a global tech hiring platform ranked #1 in Global Hiring on G2. With 12 years of experience in e-commerce strategy, platform implementation, and digital operations, he has led delivery of Adobe Commerce Cloud projects for enterprise clients including Chow Sang Sang, HomePlus (HKBN), Maxim's, Hong Kong International Airport, Hotai/Toyota, and Evisu. Prior to founding Branch8, Matt served as Vice President of Mid-Market Enterprises at HSBC. He serves as Vice Chairman of the Hong Kong E-Commerce Business Association (HKEBA). A self-taught software engineer, Matt graduated from the University of Toronto with a Bachelor of Commerce in Finance and Economics.