SQL Keywords

SQL Keywords

TIME TRAVEL (AT / BEFORE / FOR SYSTEM_TIME AS OF)

Feb 23, 2026

·

5

min read

Category: Time Travel & Versioning

Platform Support:

✅ Snowflake (AT, BEFORE) | ✅ BigQuery (FOR SYSTEM_TIME AS OF) | ✅ Databricks (VERSION AS OF, TIMESTAMP AS OF)

Description

Query historical data at a specific point in time or version. Allows accessing previous states of tables for auditing, recovery, and temporal analysis. Each platform implements time travel differently but achieves similar goals.

Syntax by Platform

Snowflake:

-- Query at specific timestamp
SELECT * FROM table_name AT(TIMESTAMP => '2024-01-15 10:00:00'::timestamp);

-- Query before timestamp
SELECT * FROM table_name BEFORE(TIMESTAMP => '2024-01-15 10:00:00'::timestamp);

-- Query at offset (seconds ago)
SELECT * FROM table_name AT(OFFSET => -3600);  -- 1 hour ago

-- Query at specific statement
SELECT * FROM table_name AT(STATEMENT => 'query_id')

-- Query at specific timestamp
SELECT * FROM table_name AT(TIMESTAMP => '2024-01-15 10:00:00'::timestamp);

-- Query before timestamp
SELECT * FROM table_name BEFORE(TIMESTAMP => '2024-01-15 10:00:00'::timestamp);

-- Query at offset (seconds ago)
SELECT * FROM table_name AT(OFFSET => -3600);  -- 1 hour ago

-- Query at specific statement
SELECT * FROM table_name AT(STATEMENT => 'query_id')

-- Query at specific timestamp
SELECT * FROM table_name AT(TIMESTAMP => '2024-01-15 10:00:00'::timestamp);

-- Query before timestamp
SELECT * FROM table_name BEFORE(TIMESTAMP => '2024-01-15 10:00:00'::timestamp);

-- Query at offset (seconds ago)
SELECT * FROM table_name AT(OFFSET => -3600);  -- 1 hour ago

-- Query at specific statement
SELECT * FROM table_name AT(STATEMENT => 'query_id')

BigQuery:

-- Query at specific timestamp
SELECT * FROM table_name
FOR SYSTEM_TIME AS OF '2024-01-15 10:00:00';

-- Query at timestamp expression
SELECT * FROM table_name
FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)

-- Query at specific timestamp
SELECT * FROM table_name
FOR SYSTEM_TIME AS OF '2024-01-15 10:00:00';

-- Query at timestamp expression
SELECT * FROM table_name
FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)

-- Query at specific timestamp
SELECT * FROM table_name
FOR SYSTEM_TIME AS OF '2024-01-15 10:00:00';

-- Query at timestamp expression
SELECT * FROM table_name
FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)

Databricks (Delta Lake):

-- Query at specific version
SELECT * FROM table_name VERSION AS OF 5;

-- Query at specific timestamp
SELECT * FROM table_name TIMESTAMP AS OF '2024-01-15 10:00:00';

-- Query at timestamp expression
SELECT * FROM table_name TIMESTAMP AS OF date_sub(current_timestamp(), 1)

-- Query at specific version
SELECT * FROM table_name VERSION AS OF 5;

-- Query at specific timestamp
SELECT * FROM table_name TIMESTAMP AS OF '2024-01-15 10:00:00';

-- Query at timestamp expression
SELECT * FROM table_name TIMESTAMP AS OF date_sub(current_timestamp(), 1)

-- Query at specific version
SELECT * FROM table_name VERSION AS OF 5;

-- Query at specific timestamp
SELECT * FROM table_name TIMESTAMP AS OF '2024-01-15 10:00:00';

-- Query at timestamp expression
SELECT * FROM table_name TIMESTAMP AS OF date_sub(current_timestamp(), 1)

Platform-Specific Notes

Snowflake:

  • Retention: 1 day (Standard), up to 90 days (Enterprise)

  • Can query by timestamp, offset, or statement ID

  • UNDROP TABLE to recover dropped tables

  • Time Travel works on all table types

  • No additional cost for Time Travel storage

BigQuery:

  • Retention: 7 days automatically

  • FOR SYSTEM_TIME AS OF clause

  • Works with both partitioned and non-partitioned tables

  • No additional storage cost for Time Travel

  • Cannot query beyond 7-day window

Databricks:

  • Requires Delta Lake format

  • Retention: 30 days default (configurable)

  • Can query by version number or timestamp

  • DESCRIBE HISTORY to see available versions

  • Version numbers increment with each write operation

Example 1: Query Historical Data

Snowflake:

-- See data as it was yesterday
SELECT 
    product_id,
    product_name,
    price,
    last_updated
FROM products
AT(TIMESTAMP => DATEADD(day, -1, CURRENT_TIMESTAMP()))
WHERE category = 'Electronics'

-- See data as it was yesterday
SELECT 
    product_id,
    product_name,
    price,
    last_updated
FROM products
AT(TIMESTAMP => DATEADD(day, -1, CURRENT_TIMESTAMP()))
WHERE category = 'Electronics'

-- See data as it was yesterday
SELECT 
    product_id,
    product_name,
    price,
    last_updated
FROM products
AT(TIMESTAMP => DATEADD(day, -1, CURRENT_TIMESTAMP()))
WHERE category = 'Electronics'

BigQuery:

SELECT 
    product_id,
    product_name,
    price,
    last_updated
FROM products
FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY)
WHERE category = 'Electronics'

SELECT 
    product_id,
    product_name,
    price,
    last_updated
FROM products
FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY)
WHERE category = 'Electronics'

SELECT 
    product_id,
    product_name,
    price,
    last_updated
FROM products
FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY)
WHERE category = 'Electronics'

Databricks:

SELECT 
    product_id,
    product_name,
    price,
    last_updated
FROM products
TIMESTAMP AS OF date_sub(current_timestamp(), 1)
WHERE category = 'Electronics'

SELECT 
    product_id,
    product_name,
    price,
    last_updated
FROM products
TIMESTAMP AS OF date_sub(current_timestamp(), 1)
WHERE category = 'Electronics'

SELECT 
    product_id,
    product_name,
    price,
    last_updated
FROM products
TIMESTAMP AS OF date_sub(current_timestamp(), 1)
WHERE category = 'Electronics'

Current products table (2024-01-20):

product_id

product_name

price

last_updated

1

Laptop

899.99

2024-01-20

2

Mouse

24.99

2024-01-19

3

Keyboard

89.99

2024-01-20

Historical products table (2024-01-19):

product_id

product_name

price

last_updated

1

Laptop

999.99

2024-01-15

2

Mouse

29.99

2024-01-10

3

Keyboard

89.99

2024-01-18

Example 2: Compare Current vs Historical

Snowflake:

SELECT 
    current.product_id,
    current.product_name,
    current.price as current_price,
    historical.price as price_yesterday,
    current.price - historical.price as price_change,
    ROUND((current.price - historical.price) * 100.0 / historical.price, 2) as pct_change
FROM products current
LEFT JOIN products AT(OFFSET => -86400) historical  -- 24 hours ago
    ON current.product_id = historical.product_id
WHERE current.price != historical.price OR historical.price IS NULL

SELECT 
    current.product_id,
    current.product_name,
    current.price as current_price,
    historical.price as price_yesterday,
    current.price - historical.price as price_change,
    ROUND((current.price - historical.price) * 100.0 / historical.price, 2) as pct_change
FROM products current
LEFT JOIN products AT(OFFSET => -86400) historical  -- 24 hours ago
    ON current.product_id = historical.product_id
WHERE current.price != historical.price OR historical.price IS NULL

SELECT 
    current.product_id,
    current.product_name,
    current.price as current_price,
    historical.price as price_yesterday,
    current.price - historical.price as price_change,
    ROUND((current.price - historical.price) * 100.0 / historical.price, 2) as pct_change
FROM products current
LEFT JOIN products AT(OFFSET => -86400) historical  -- 24 hours ago
    ON current.product_id = historical.product_id
WHERE current.price != historical.price OR historical.price IS NULL

BigQuery:

SELECT 
    current.product_id,
    current.product_name,
    current.price as current_price,
    historical.price as price_yesterday,
    current.price - historical.price as price_change,
    ROUND((current.price - historical.price) * 100.0 / historical.price, 2) as pct_change
FROM products current
LEFT JOIN products 
    FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY) historical
    ON current.product_id = historical.product_id
WHERE current.price != historical.price OR historical.price IS NULL

SELECT 
    current.product_id,
    current.product_name,
    current.price as current_price,
    historical.price as price_yesterday,
    current.price - historical.price as price_change,
    ROUND((current.price - historical.price) * 100.0 / historical.price, 2) as pct_change
FROM products current
LEFT JOIN products 
    FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY) historical
    ON current.product_id = historical.product_id
WHERE current.price != historical.price OR historical.price IS NULL

SELECT 
    current.product_id,
    current.product_name,
    current.price as current_price,
    historical.price as price_yesterday,
    current.price - historical.price as price_change,
    ROUND((current.price - historical.price) * 100.0 / historical.price, 2) as pct_change
FROM products current
LEFT JOIN products 
    FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY) historical
    ON current.product_id = historical.product_id
WHERE current.price != historical.price OR historical.price IS NULL

Databricks:

SELECT 
    current.product_id,
    current.product_name,
    current.price as current_price,
    historical.price as price_yesterday,
    current.price - historical.price as price_change,
    ROUND((current.price - historical.price) * 100.0 / historical.price, 2) as pct_change
FROM products current
LEFT JOIN products TIMESTAMP AS OF date_sub(current_timestamp(), 1) historical
    ON current.product_id = historical.product_id
WHERE current.price != historical.price OR historical.price IS NULL

SELECT 
    current.product_id,
    current.product_name,
    current.price as current_price,
    historical.price as price_yesterday,
    current.price - historical.price as price_change,
    ROUND((current.price - historical.price) * 100.0 / historical.price, 2) as pct_change
FROM products current
LEFT JOIN products TIMESTAMP AS OF date_sub(current_timestamp(), 1) historical
    ON current.product_id = historical.product_id
WHERE current.price != historical.price OR historical.price IS NULL

SELECT 
    current.product_id,
    current.product_name,
    current.price as current_price,
    historical.price as price_yesterday,
    current.price - historical.price as price_change,
    ROUND((current.price - historical.price) * 100.0 / historical.price, 2) as pct_change
FROM products current
LEFT JOIN products TIMESTAMP AS OF date_sub(current_timestamp(), 1) historical
    ON current.product_id = historical.product_id
WHERE current.price != historical.price OR historical.price IS NULL

Result:

product_id

product_name

current_price

price_yesterday

price_change

pct_change

1

Laptop

899.99

999.99

-100.00

-10.00

2

Mouse

24.99

29.99

-5.00

-16.67

Example 3: Recover Accidentally Deleted Data

Snowflake:

-- Create backup table from before delete
CREATE TABLE customers_backup AS
SELECT *
FROM customers
BEFORE(STATEMENT => '01a6a3b4-0000-5678-0000-000012345678');  -- Query ID of DELETE

-- Or restore entire table
CREATE OR REPLACE TABLE customers AS
SELECT * FROM customers AT(OFFSET => -3600);  -- 1 hour ago
-- Create backup table from before delete
CREATE TABLE customers_backup AS
SELECT *
FROM customers
BEFORE(STATEMENT => '01a6a3b4-0000-5678-0000-000012345678');  -- Query ID of DELETE

-- Or restore entire table
CREATE OR REPLACE TABLE customers AS
SELECT * FROM customers AT(OFFSET => -3600);  -- 1 hour ago
-- Create backup table from before delete
CREATE TABLE customers_backup AS
SELECT *
FROM customers
BEFORE(STATEMENT => '01a6a3b4-0000-5678-0000-000012345678');  -- Query ID of DELETE

-- Or restore entire table
CREATE OR REPLACE TABLE customers AS
SELECT * FROM customers AT(OFFSET => -3600);  -- 1 hour ago

BigQuery:

-- Create backup from before delete
CREATE OR REPLACE TABLE `project.dataset.customers_backup` AS
SELECT *
FROM `project.dataset.customers`
FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)

-- Create backup from before delete
CREATE OR REPLACE TABLE `project.dataset.customers_backup` AS
SELECT *
FROM `project.dataset.customers`
FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)

-- Create backup from before delete
CREATE OR REPLACE TABLE `project.dataset.customers_backup` AS
SELECT *
FROM `project.dataset.customers`
FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)

Databricks:

-- Restore from previous version
RESTORE TABLE customers TO VERSION AS OF 5;

-- Or create backup
CREATE TABLE customers_backup AS
SELECT * FROM customers VERSION AS OF 5

-- Restore from previous version
RESTORE TABLE customers TO VERSION AS OF 5;

-- Or create backup
CREATE TABLE customers_backup AS
SELECT * FROM customers VERSION AS OF 5

-- Restore from previous version
RESTORE TABLE customers TO VERSION AS OF 5;

-- Or create backup
CREATE TABLE customers_backup AS
SELECT * FROM customers VERSION AS OF 5

Example 4: Audit Trail Analysis

Snowflake:

-- Find all changes to a specific customer in last 7 days
WITH time_points AS (
    SELECT DATEADD(hour, -seq.value, CURRENT_TIMESTAMP()) as check_time
    FROM TABLE(GENERATOR(ROWCOUNT => 168))  -- 7 days * 24 hours
    AS seq
)
SELECT DISTINCT
    tp.check_time,
    c.customer_id,
    c.email,
    c.status,
    c.tier
FROM time_points tp
CROSS JOIN customers AT(TIMESTAMP => tp.check_time) c
WHERE c.customer_id = 12345
ORDER BY

-- Find all changes to a specific customer in last 7 days
WITH time_points AS (
    SELECT DATEADD(hour, -seq.value, CURRENT_TIMESTAMP()) as check_time
    FROM TABLE(GENERATOR(ROWCOUNT => 168))  -- 7 days * 24 hours
    AS seq
)
SELECT DISTINCT
    tp.check_time,
    c.customer_id,
    c.email,
    c.status,
    c.tier
FROM time_points tp
CROSS JOIN customers AT(TIMESTAMP => tp.check_time) c
WHERE c.customer_id = 12345
ORDER BY

-- Find all changes to a specific customer in last 7 days
WITH time_points AS (
    SELECT DATEADD(hour, -seq.value, CURRENT_TIMESTAMP()) as check_time
    FROM TABLE(GENERATOR(ROWCOUNT => 168))  -- 7 days * 24 hours
    AS seq
)
SELECT DISTINCT
    tp.check_time,
    c.customer_id,
    c.email,
    c.status,
    c.tier
FROM time_points tp
CROSS JOIN customers AT(TIMESTAMP => tp.check_time) c
WHERE c.customer_id = 12345
ORDER BY

Result shows all state changes:

check_time

customer_id

email

status

tier

2024-01-13 10:00

12345

old@email.com

Active

Standard

2024-01-13 11:00

12345

old@email.com

Active

Standard

2024-01-15 14:00

12345

new@email.com

Active

Standard

2024-01-17 09:00

12345

new@email.com

Active

Premium

Example 5: Version History (Databricks)

Databricks:

-- View table history
DESCRIBE HISTORY customers;

-- Query specific version
SELECT * FROM customers VERSION AS OF 10;

-- See all versions of a specific row
SELECT 
    version,
    customer_id,
    email,
    tier,
    _commit_timestamp
FROM (
    SELECT 0 as version, customer_id, email, tier, current_timestamp() as _commit_timestamp
    FROM customers VERSION AS OF 0
    WHERE customer_id = 12345
    
    UNION ALL
    
    SELECT 1 as version, customer_id, email, tier, current_timestamp() as _commit_timestamp
    FROM customers VERSION AS OF 1
    WHERE customer_id = 12345
    
    -- ... repeat for each version
)
ORDER BY

-- View table history
DESCRIBE HISTORY customers;

-- Query specific version
SELECT * FROM customers VERSION AS OF 10;

-- See all versions of a specific row
SELECT 
    version,
    customer_id,
    email,
    tier,
    _commit_timestamp
FROM (
    SELECT 0 as version, customer_id, email, tier, current_timestamp() as _commit_timestamp
    FROM customers VERSION AS OF 0
    WHERE customer_id = 12345
    
    UNION ALL
    
    SELECT 1 as version, customer_id, email, tier, current_timestamp() as _commit_timestamp
    FROM customers VERSION AS OF 1
    WHERE customer_id = 12345
    
    -- ... repeat for each version
)
ORDER BY

-- View table history
DESCRIBE HISTORY customers;

-- Query specific version
SELECT * FROM customers VERSION AS OF 10;

-- See all versions of a specific row
SELECT 
    version,
    customer_id,
    email,
    tier,
    _commit_timestamp
FROM (
    SELECT 0 as version, customer_id, email, tier, current_timestamp() as _commit_timestamp
    FROM customers VERSION AS OF 0
    WHERE customer_id = 12345
    
    UNION ALL
    
    SELECT 1 as version, customer_id, email, tier, current_timestamp() as _commit_timestamp
    FROM customers VERSION AS OF 1
    WHERE customer_id = 12345
    
    -- ... repeat for each version
)
ORDER BY

Example 6: Temporal Joins

All Platforms:

-- Join current orders with historical customer data at order time
SELECT 
    o.order_id,
    o.order_date,
    o.customer_id,
    -- Get customer tier as it was when order was placed
    c_historical.tier as tier_at_order_time,
    c_current.tier as current_tier,
    o.order_total
FROM orders o
JOIN customers FOR SYSTEM_TIME AS OF o.order_date c_historical  -- BigQuery syntax
    ON o.customer_id = c_historical.customer_id
JOIN customers c_current
    ON o.customer_id = c_current.customer_id
WHERE o.order_date >= '2024-01-01'

-- Join current orders with historical customer data at order time
SELECT 
    o.order_id,
    o.order_date,
    o.customer_id,
    -- Get customer tier as it was when order was placed
    c_historical.tier as tier_at_order_time,
    c_current.tier as current_tier,
    o.order_total
FROM orders o
JOIN customers FOR SYSTEM_TIME AS OF o.order_date c_historical  -- BigQuery syntax
    ON o.customer_id = c_historical.customer_id
JOIN customers c_current
    ON o.customer_id = c_current.customer_id
WHERE o.order_date >= '2024-01-01'

-- Join current orders with historical customer data at order time
SELECT 
    o.order_id,
    o.order_date,
    o.customer_id,
    -- Get customer tier as it was when order was placed
    c_historical.tier as tier_at_order_time,
    c_current.tier as current_tier,
    o.order_total
FROM orders o
JOIN customers FOR SYSTEM_TIME AS OF o.order_date c_historical  -- BigQuery syntax
    ON o.customer_id = c_historical.customer_id
JOIN customers c_current
    ON o.customer_id = c_current.customer_id
WHERE o.order_date >= '2024-01-01'

Time Travel Retention Comparison

Platform

Default Retention

Maximum Retention

Cost

Snowflake

1 day (Standard)

90 days (Enterprise)

Included

BigQuery

7 days

7 days

Included

Databricks

30 days

Configurable

Storage cost

Common Use Cases

  1. Data recovery: Restore accidentally deleted/modified data

  2. Auditing: Track changes over time

  3. Debugging: Investigate data quality issues

  4. Regulatory compliance: Maintain historical records

  5. A/B testing: Compare before/after states

  6. Temporal analysis: Understand data evolution

  7. Rollback: Undo unwanted changes

Best Practices

  1. Document retention periods: Know your platform's limits

  2. Test recovery procedures: Practice before you need it

  3. Use for auditing: Track important changes

  4. Monitor Time Travel queries: Can be expensive

  5. Consider long-term archival: Beyond Time Travel retention

  6. Snapshot important changes: CREATE TABLE AS SELECT for backups

  7. Log query IDs: For Snowflake BEFORE(STATEMENT)

  8. Set retention appropriately: Balance cost vs needs (Databricks)

Limitations

  • Snowflake: Cannot query beyond retention period

  • BigQuery: Fixed 7-day limit

  • Databricks: Requires Delta Lake format

  • All: Performance may degrade for very old timestamps

  • All: Time Travel doesn't replace backup strategy

Interested to Learn More?
Try Out the Free 14-Days Trial

More Articles

decorative icon

Experience Analytics for the AI-Era

Start your 14-day trial today - it's free and no credit card needed

decorative icon

Experience Analytics for the AI-Era

Start your 14-day trial today - it's free and no credit card needed

decorative icon

Experience Analytics for the AI-Era

Start your 14-day trial today - it's free and no credit card needed

Copyright © 2026 Paradime Labs, Inc.

Made with ❤️ in San Francisco ・ London

*dbt® and dbt Core® are federally registered trademarks of dbt Labs, Inc. in the United States and various jurisdictions around the world. Paradime is not a partner of dbt Labs. All rights therein are reserved to dbt Labs. Paradime is not a product or service of or endorsed by dbt Labs, Inc.

Copyright © 2026 Paradime Labs, Inc.

Made with ❤️ in San Francisco ・ London

*dbt® and dbt Core® are federally registered trademarks of dbt Labs, Inc. in the United States and various jurisdictions around the world. Paradime is not a partner of dbt Labs. All rights therein are reserved to dbt Labs. Paradime is not a product or service of or endorsed by dbt Labs, Inc.

Copyright © 2026 Paradime Labs, Inc.

Made with ❤️ in San Francisco ・ London

*dbt® and dbt Core® are federally registered trademarks of dbt Labs, Inc. in the United States and various jurisdictions around the world. Paradime is not a partner of dbt Labs. All rights therein are reserved to dbt Labs. Paradime is not a product or service of or endorsed by dbt Labs, Inc.