pd.read_sql_query读取Oracle数据时无法获取时区信息怎么办
问题场景
尝试将Oracle数据库中包含/不包含时区信息的多类datetime数据读取到pandas DataFrame中,不同类型日期时间列结构参考:
其中c2列自带时区信息,但使用如下代码调用pd.read_sql_query读取时,时区信息始终无法正确获取:
data = pd.read_sql_query(select_query, connection_string, chunksize=chunk_size)
问题原因
pd.read_sql_query没有专门针对Oracle时区类型的内置配置参数,时区丢失来自两个核心环节:
- Oracle连接驱动(cx_Oracle/oracledb)默认的类型映射规则不会主动返回带
tzinfo属性的Python datetime对象,会把带时区的时间戳静默转成无时区的本地时间 - pandas读取时默认将所有datetime类值统一转换为无时区的
datetime64[ns]类型,即使上游传入了带时区的对象,也会在类型转换时丢弃时区信息
解决步骤
1. 配置Oracle连接的类型返回规则
连接建立后先注册输出类型处理器,强制驱动返回原生带时区的时间戳类型,同时设置会话时区避免时间偏移:
import cx_Oracle # 使用新版oracledb驱动的话,替换为 import oracledb as cx_Oracle 即可 # 建立数据库连接 connection = cx_Oracle.connect("用户名/密码@数据库地址:端口/服务名") # 设置会话时区,替换为业务实际需要的时区即可 cursor = connection.cursor() cursor.execute("ALTER SESSION SET TIME_ZONE = 'Asia/Shanghai'") # 注册类型处理器,强制返回TIMESTAMP WITH TIME ZONE原生类型 def timestamp_tz_handler(cursor, name, default_type, size, precision, scale): if default_type == cx_Oracle.DB_TYPE_TIMESTAMP_TZ: return cursor.var(cx_Oracle.DB_TYPE_TIMESTAMP_TZ, arraysize=cursor.arraysize) connection.outputtypehandler = timestamp_tz_handler
注意:如果用SQLAlchemy封装连接,需要从引擎中取出原生连接对象再做上述配置,否则SQLAlchemy的方言层会覆盖类型映射规则,导致配置失效。
小提示:如果是TIMESTAMP WITH LOCAL TIME ZONE类型的列,可以在查询SQL中用CAST(列名 AS TIMESTAMP WITH TIME ZONE)将其显式转为带时区的时间戳类型,再按上述流程读取即可。
2. 读取时显式转换带时区的列
不要依赖pandas自动类型推断,读取后对带时区的列单独做类型转换,分块读取场景下对每个chunk单独处理即可:
import pandas as pd chunk_list = [] # 注意这里传入的是配置好的原生connection对象,不要直接传连接字符串 for chunk in pd.read_sql_query(select_query, connection, chunksize=chunk_size): # 将c2替换为所有带时区信息的列名,多列可以循环处理 chunk["c2"] = pd.to_datetime(chunk["c2"]) # 如果需要统一转换到指定时区,追加如下配置 # chunk["c2"] = chunk["c2"].dt.tz_convert("Asia/Shanghai") chunk_list.append(chunk) df = pd.concat(chunk_list, ignore_index=True)
3. 校验配置是否生效
可以先跳过pandas,直接用驱动取数验证时区是否正常返回:
cursor.execute("SELECT c2 FROM 目标表 WHERE rownum = 1") test_val = cursor.fetchone()[0] print(test_val.tzinfo) # 正常返回时区对象而非None,说明驱动层配置生效
内容的提问来源于stack exchange,提问作者Saurabh
相关产品推荐
相关产品推荐

