How to Implement Incremental Load with Watermark by updated_at in Python
Load only new or changed rows into SQLite by comparing an updated_at timestamp against a stored watermark, returning counts and the new watermark.
Python code
56 linesimport sqlite3
from datetime import datetime, timedelta
def watermark_incremental_load(db_path, table_name, last_watermark, source_data):
"""Load only rows with updated_at greater than the last watermark."""
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Create table if it doesn't exist
cursor.execute(f"""
CREATE TABLE IF NOT EXISTS {table_name} (
id INTEGER PRIMARY KEY,
name TEXT,
updated_at TEXT
)
""")
new_watermark = last_watermark
loaded_count = 0
for row in source_data:
if row["updated_at"] > last_watermark:
cursor.execute(
f"INSERT INTO {table_name} (id, name, updated_at) VALUES (?, ?, ?)",
(row["id"], row["name"], row["updated_at"])
)
loaded_count += 1
# Update watermark to the max timestamp seen
if row["updated_at"] > new_watermark:
new_watermark = row["updated_at"]
conn.commit()
conn.close()
return {
"loaded_count": loaded_count,
"new_watermark": new_watermark,
"skipped_count": len(source_data) - loaded_count
}
if __name__ == "__main__":
# Test data: some rows are new, some are already loaded
last_run = "2024-01-15 10:00:00"
source = [
{"id": 1, "name": "Alice", "updated_at": "2024-01-15 08:00:00"},
{"id": 2, "name": "Bob", "updated_at": "2024-01-15 09:30:00"},
{"id": 3, "name": "Charlie", "updated_at": "2024-01-15 10:30:00"},
{"id": 4, "name": "Diana", "updated_at": "2024-01-15 11:00:00"}
]
result = watermark_incremental_load(
"example.db", "users", last_run, source
)
print(result)
Output
{'loaded_count': 2, 'new_watermark': '2024-01-15 11:00:00', 'skipped_count': 2}
How it works
The function compares each row's updated_at string to the persisted last_watermark and inserts only rows newer than it. String comparison works reliably here because ISO-8601 timestamps sort lexicographically in the same order as chronologically. As rows load, the watermark advances to the maximum updated_at seen, so the next run starts from the most recent timestamp and avoids reprocessing. Committing at the end ensures all inserts are durable before the connection closes. Skipped rows are counted simply as the difference between source size and loaded count, which is correct because no upsert logic is applied on conflict.
Common mistakes
- Comparing timestamps as strings when format is inconsistent (e.g., mixed timezone offsets) — always use ISO-8601 or convert to datetime objects.
- Using `INSERT` without handling primary key conflicts for rows that were partially loaded in a previous failed run — consider `INSERT OR REPLACE` or upsert logic.
- Assuming the database table already exists and skipping `CREATE TABLE IF NOT EXISTS`, which breaks first-run scenarios.
- Not persisting the returned new watermark back to a store, so the next run reverts to the old value and reloads the same rows.
Variations
- Store the watermark in a separate metadata table instead of returning it, so state survives across processes.
- Use `INSERT OR REPLACE` to overwrite rows that were updated after being previously loaded, turning the load into an incremental upsert.
Real-world use cases
- Syncing a production OLTP database to a staging warehouse nightly by pulling only records modified since the last sync timestamp.
- Refreshing a dashboard's materialized view from an event stream, fetching only events newer than the last processed offset.
- Keeping a search index current by ingesting only documents whose `last_modified` field exceeds the previous crawl's watermark.
Sponsored
More from Data pipelines & processing
- Add a UUID Surrogate Key to Each Row in a CSV with Python easy
- Attach Source File Metadata to Records in Python easy
- Build a Python Utility That Detects Duplicate Records Across Multiple Excel Sheets medium
- Check Null Rate Threshold in PySpark DataFrame medium
- Count Records Processed per Category in Python easy
- Create Data Helper Functions in Python for Beginners easy
Keep learning
Related tutorials and quizzes for this topic.