To run incremental data loads from a source table with no last-modified timestamp, capture a full snapshot of the table on each run and compare it against the previous run's snapshot to identify new, changed, and deleted rows, then load only the difference. This guide is for data engineers and integration managers pulling from ERPs, legacy databases, or third-party systems where some tables lack a reliable timestamp or change-tracking column. After following it, you'll have a repeatable snapshot-diff pattern to apply to any table that can't support timestamp-based incremental extraction.
Timestamp-based incremental loading is the default approach for a reason: it's cheap to run and easy to reason about. It breaks down the moment a table doesn't have a consistently populated last-modified column, which is common in older ERP tables, certain SaaS API objects, and tables where updates happen through paths that don't touch the timestamp field. When that happens, the practical fallback is comparing full snapshots between runs rather than falling back to a full reload every time.
The Problem
Some source tables simply don't track when a row last changed. This shows up often in ERP systems where certain tables were never designed with downstream integration in mind, and in APIs where an object type doesn't expose a modified_at or equivalent field. Teams facing this usually pick one of two bad options: pull the entire table on every run, which gets slower and more expensive as the table grows, or try to infer changes indirectly (checking a status field, a version number, or a sequence ID) which breaks the moment the source system's behavior changes.
A snapshot-and-diff approach solves this without needing the source system to track anything extra. It compares what you have now against what you had last time, at the row level, and only moves the difference.
What You'll Need
- Read access to the source table, ideally with a stable primary key or unique identifier per row
- Storage for the previous run's snapshot (this can be a staging table, a file, or a dedicated snapshot store in your pipeline platform)
- A defined comparison key: the column or combination of columns that uniquely identifies a row
- A pipeline platform that supports storing and comparing snapshots between runs, such as Integrate.io
How to Run Incremental Loads Without a Timestamp: Step-by-Step
Step 1: Confirm the table genuinely has no usable timestamp
Before building a snapshot-diff process, rule out simpler options. Some tables have a timestamp field that's just inconsistently populated or named unexpectedly.
What to do:
- Check for any date, timestamp, or sequence field on the table, even if it's not labeled "last modified"
- Sample recent known changes and check whether any field reflects them reliably
- Confirm with the source system's documentation or an administrator whether a change-tracking mechanism exists that isn't obvious from the schema (SAP's change pointers, for example, work even when a table itself has no timestamp column)
Output of this step: A confirmed decision that no reliable timestamp-based approach exists, and snapshot-diff is the right method for this specific table.
Step 2: Define a stable comparison key
The diff process depends entirely on being able to match a row in the current pull to the same row in the previous pull.
What to do:
- Identify the primary key or a combination of fields that uniquely identifies each row
- Confirm this key doesn't change for a given business record over time (an internal record ID is safer than a field that could be edited)
- Document what happens if the key itself is missing or null on some rows, since these rows can't be reliably diffed
Output of this step: A defined, tested comparison key that will be used to match rows between the current and previous snapshot.
Step 3: Capture and store the first full snapshot
The first run establishes the baseline. There's no diff to compute yet, so this pull will be a full load.
What to do:
- Extract the complete table for the first run
- Store this snapshot in a staging location, keyed by the comparison key from Step 2
- Record the run timestamp separately from any source-side field, since this is your own tracking metadata, not the source system's
Output of this step: A stored baseline snapshot representing the full state of the table at the time of the first run.
Where Integrate.io helps: Integrate.io's snapshot-based change detection stores the prior run's result set automatically, so you don't need to build and maintain a separate staging table manually for this purpose.
Step 4: Capture the next snapshot and compare against the baseline
On the next scheduled run, pull the full table again and compare it row by row against the stored baseline.
What to do:
- Extract the current full table state
- For each row, compare against the stored snapshot using the comparison key
- Classify each row as new (key exists now, didn't before), changed (key exists in both, but field values differ), or deleted (key existed before, doesn't now)
- Load only the new and changed rows to the destination; handle deletions according to your destination's requirements (soft delete flag, hard delete, or ignore)
Output of this step: A classified set of new, changed, and deleted rows, ready to load, without needing to pull or process rows that haven't changed.
Where Integrate.io helps: The platform's diff logic runs this comparison automatically between the current and stored snapshot, so the classification step doesn't require custom scripting to compare full result sets.
Step 5: Load the difference and update the stored snapshot
Once the diff is computed, load only what changed, and update your stored baseline for the next run.
What to do:
- Load new and changed rows into the destination using an upsert (insert or update based on the comparison key)
- Apply your chosen deletion handling (mark inactive, hard delete, or skip depending on business requirements)
- Overwrite the stored snapshot with the current full pull, so the next run compares against this state rather than the original baseline
Output of this step: The destination reflects only the actual changes since the last run, and the stored snapshot is current for the next comparison.
Step 6: Monitor for full-table pull performance as the source grows
Snapshot-diff still requires pulling the full table on every run, even though it only loads the difference downstream. As the source table grows, this extraction step becomes the bottleneck.
What to do:
- Track extraction time for the full pull step specifically, separate from the diff and load steps
- If extraction time grows past an acceptable window, investigate whether a partial timestamp exists on a subset of rows that could reduce the pull size
- Consider whether the source system offers any batch or partition mechanism (date ranges, ID ranges) that can be combined with the snapshot-diff approach to limit how much is pulled per run
Output of this step: A monitored extraction step with an escalation path if full-table pulls become too slow as data volume grows.
Where Integrate.io helps: Built-in monitoring surfaces run time per pipeline step, making it clear when the extraction phase specifically is degrading, rather than only seeing an overall slower run.
Common Mistakes to Avoid
-
Assuming a table has no timestamp without checking for indirect signals first. Sequence numbers, version fields, or a source system's own change-tracking mechanism (like SAP's change pointers) sometimes exist even without an obvious timestamp column.
-
Using an unstable or nullable field as the comparison key. If the key can be null or can change for a record over time, the diff will misclassify rows as new when they're actually the same record.
-
Not handling deletions explicitly. A row disappearing from the current pull needs a defined behavior (soft delete, hard delete, or ignore); leaving this undefined causes stale data to linger in the destination.
-
Forgetting to update the stored snapshot after each run. If the baseline isn't refreshed, every subsequent run recomputes the same diff against outdated data.
-
Ignoring full-pull performance as the table grows. Snapshot-diff avoids reprocessing unchanged rows downstream, but it doesn't reduce the cost of the extraction step itself; this needs separate monitoring.
-
Applying snapshot-diff to every table by default. It's more resource-intensive than timestamp-based extraction; reserve it specifically for tables that genuinely lack a usable change-tracking field.
Conclusion
Running incremental loads from a source table with no last-modified timestamp comes down to comparing full snapshots between runs rather than trusting a field that doesn't exist. A stable comparison key, a stored baseline snapshot, and a clear rule for handling deletions are the core pieces that make this reliable. Integrate.io's snapshot-based change detection handles the storage and comparison steps automatically, so teams don't need to build and maintain custom diff logic for every table that lacks a timestamp. Once this pattern is in place, it extends to any source system where change tracking is inconsistent, without falling back to slow, expensive full reloads on every run.