Python developer resource
Writing pandas timedeltas to SQL? Choose the units first.
If your SQL column should contain integer nanoseconds, convert the duration values explicitly and preserve missing values as NULL before writing. This small helper makes that choice visible and testable.
Download the Python files (ZIP)MIT-licensed original example files. No signup or payment. Tested locally with Python 3.12, pandas 2.3.3 and 3.1.1, and SQLAlchemy 2.0.45.
A storage choice, not an upstream fix
As of November 11, 2026, pandas issue #71941 describes timedelta SQL writes that can store integers in the input resolution while warning that they are nanoseconds. Missing values can become the NaT integer sentinel instead of SQL NULL.
The original PR #72006 is open and unmerged. The upstream storage policy remains unresolved. The maintainer discussion considers conversion versus documenting native-unit behavior. Recheck the issue, PR, and your installed pandas version before adopting this example.
For example, one second stored as microseconds is 1,000,000; in nanoseconds it is 1,000,000,000. An integer column alone cannot tell you which unit was intended.
Start with an explicit conversion
Use this only when you choose integer nanosecond storage for a new table or have independently established that contract for an existing column. In this example, duration_ns starts as a NumPy-backed timedelta Series, not stored integers. The name documents the intended output unit.
s = df["duration_ns"]
ns = s.dt.as_unit("ns")
prepared = df.copy()
prepared["duration_ns"] = (
ns.astype("int64").astype("Int64").mask(s.isna())
)
# Convert every duration column before the first SQL write.
prepared.to_sql("new_duration_ns", conn, index=False)The lowercase int64 cast keeps exact integer values; nullable Int64 plus the missing-value mask replaces NaT with a missing value that the tested SQL paths write as NULL. There is no float intermediate. The original DataFrame is unchanged.
If you create a new column name such as duration_ns from duration, drop the original timedelta column from the prepared frame before calling to_sql. For multiple duration columns, finish all conversions before the first write.
Use the helper at the preparation step
The downloaded nanoseconds_for_sql function accepts only a NumPy-backed timedelta Series and returns nullable Int64. It does not connect to a database, write data, or modify its input.
from pandas_timedelta_sql import nanoseconds_for_sql
prepared = df.copy()
prepared["duration_ns"] = nanoseconds_for_sql(df["duration_ns"])
prepared.to_sql("new_duration_ns", conn, index=False)Both examples use a new table name and the default if_exists="fail" behavior. Supply your own connection. Do not append to an existing table until its unit contract is independently known.
Keep these limits explicit
- Values outside the nanosecond range raise during conversion. There is no lossless nanosecond representation for them. Do not catch the error, coerce values, and continue writing.
- This does not infer units or repair existing mixed-unit data. Multiplying every stored value by 1,000 is not a general fix.
- The tests cover in-memory SQLite through Python sqlite3 and SQLAlchemy 2 only. They do not validate production migrations, locks, other databases, or an existing schema.
- Arrow-backed data, ADBC, and native database interval types are outside this helper's scope. Choose and test a separate storage policy for those paths.
Inspect and test locally
The ZIP contains pandas_timedelta_sql.py, test_pandas_timedelta_sql.py, README.md, and LICENSE.txt. The MIT license covers only these original example files, not pandas or the company repository. No dependencies are bundled.
Extract it into a local folder and read the README. In an isolated Python environment with pandas, SQLAlchemy, and pytest available, run:
python -m pytest test_pandas_timedelta_sql.py -qThe tests use local in-memory databases. Compatibility beyond the versions and SQL paths listed above has not been verified. Test your own schema and consumers before adopting this storage contract.