You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python处理数据写入SQL Server时float类型拆分报错求助

问题解决方案:处理FirstName列float类型导致的str.replace报错

问题背景

用Python抓取生成报表,清理格式化后写入SQL Server,再推送至Tableau。执行代码时因FirstName列存在float类型对象报错:

'float' object has no attribute 'replace'

报错代码行:

df[['FirstName', 'Business_Title']] = df['FirstName'].astype(str).str.rsplit(' ', 1, expand=True)

已尝试两种写法均未解决:

  • df[['FirstName', 'Business_Title']] = df['FirstName'].str.rsplit(' ', 1, expand=True)
  • df[['FirstName', 'Business_Title']] = df['FirstName'].apply(lambda x: pd.Series(str(x).rsplit(' ', 1)) if pd.notna(x) and isinstance(x, str) else pd.Series([x, '']))

完整代码(已屏蔽敏感信息):

import pandas as pd
import pyodbc

def read_and_split_name(file_path, server_name, database_name):
    try:
        # Read the Excel file into a pandas DataFrame, skip the first 4 rows, and use the 5th row as the header
        df = pd.read_excel(file_path, header=4)

        # Split the "Name" column into "First Name" and "Last Name" based on the comma
        df[['LastName', 'FirstName']] = df['Rendering'].astype(str).str.split(', ', 1, expand=True)

        # Further split the "First Name" column by spaces and add a new column "Business_Title"
        # Handle non-string values by converting them to strings
        df['FirstName'] = df['FirstName'].astype(str)
> df[['FirstName', 'Business_Title']] = df['FirstName'].astype(str).str.rsplit(' ', 1, expand=True)
> 
        # Use transform('nunique') to get the count of unique values for each group
        df['_Unique'] = df.groupby(['Loc Name', 'LastName', 'FirstName'])['Enc Dt'].transform('nunique')

        # Use transform('count') to get the total count for each group
        df['_ENC'] = df.groupby(['Loc Name', 'LastName'])['Enc Dt'].transform('count')

        # Drop rows where last name is 'Nurse' or 'Enabling'
        df = df[~df['LastName'].isin(['Nurse', 'Enabling'])]

        # Replace spaces and dashes with underscores in the "Loc Name" column
        df['Loc Name'] = df['Loc Name'].str.replace(' ', '_').str.replace('-', '_')

        # Calculate the new column "ENC_Per_"
        df['ENC_Per_'] = df['_ENC'] / df['_Unique']

        # Replace non-finite values (NA or inf) with a suitable replacement (e.g., 0)
        df['_Unique'] = df['_Unique'].fillna(0).astype(int)
        df['_ENC'] = df['_ENC'].fillna(0).astype(int)
        df['ENC_Per_'] = df['ENC_Per_'].fillna(0).astype(int)

        # Extract the month part of the date in MMM format
        df['MMM_Format'] = pd.to_datetime(df['Enc Dt'], errors='coerce', format='%m/%d/%Y').dt.strftime('%b')

        # Convert the month abbreviations to uppercase
        df['MMM_Format'] = df['MMM_Format'].str.upper()

        # Update column headers based on the formatted month
        df.rename(columns={
            '_ENC': f"{df['MMM_Format'][0]}_ENC",
            'ENC_Per_': f"ENC_Per_{df['MMM_Format'][0]}",
            '_Unique': f"{df['MMM_Format'][0]}_Unique"
        }, inplace=True)

        # Export the DataFrame to a Microsoft SQL Server table using pyodbc
        conn_str = f"DRIVER={{ODBC Driver 17 for SQL Server}};SERVER={server_name};DATABASE={database_name};Trusted_Connection=yes;"
        with pyodbc.connect(conn_str) as conn:
            cursor = conn.cursor()
            for index, row in df.iterrows():
                table_name = row['Loc Name'].replace(' ', '_').replace('-', '_')  # Use Loc Name as the table name
                update_query = f"""
                    UPDATE {table_name}
                    SET FirstName = '{row['FirstName']}',
                        LastName = '{row['LastName']}',
                        Business_Title = '{row['Business_Title']}',
                        {row['MMM_Format']}_Unique = {row[f"{row['MMM_Format']}_Unique"]},
                        {row['MMM_Format']}_ENC = {row[f"{row['MMM_Format']}_ENC"]}
                    WHERE FirstName = '{row['FirstName']}' AND LastName = '{row['LastName']}'
                """
                cursor.execute(update_query)

        print("Update to SQL Server successful.")

    except Exception as e:
        print(f"An error occurred: {e}")

# Specify the path to your .xls Excel file
file_path = "FILEPATH"

# Specify SQL Server connection details
server_name = "SERVERNAME"
database_name = "DATABASENAME"

# Call the function to read, split names, replace spaces and dashes, calculate new column, convert to integers, update column headers, and update SQL Server
read_and_split_name(file_path, server_name, database_name)

解决方法

方案1:先处理空值再统一转字符串

问题核心是FirstName列中的NaN会被识别为float类型,直接astype(str)会把NaN转为字符串'nan',需先填充空值再处理:

# 替换原问题代码块:
# df['FirstName'] = df['FirstName'].astype(str)
# df[['FirstName', 'Business_Title']] = df['FirstName'].astype(str).str.rsplit(' ', 1, expand=True)

# 改成:
# 先填充空值为空白字符串,再转换为str类型
df['FirstName'] = df['FirstName'].fillna("").astype(str)
# 执行拆分,expand=True会自动生成两列,空值填充为NaN
df[['FirstName', 'Business_Title']] = df['FirstName'].str.rsplit(' ', 1, expand=True)
# 最后把拆分后的空值统一填充为空白字符串
df[['FirstName', 'Business_Title']] = df[['FirstName', 'Business_Title']].fillna("")

方案2:用自定义函数做健壮性处理

通过apply遍历每个元素,覆盖空值、浮点、纯字符串等所有场景:

# 替换原问题代码块:
# df['FirstName'] = df['FirstName'].astype(str)
# df[['FirstName', 'Business_Title']] = df['FirstName'].astype(str).str.rsplit(' ', 1, expand=True)

# 改成:
def split_name_title(value):
    # 处理空值或非字符串类型
    if pd.isna(value) or not isinstance(value, str):
        return pd.Series(["", ""])
    # 从右往左按空格拆分1次
    parts = value.rsplit(' ', 1)
    # 如果只有1个部分,说明没有头衔,返回原内容+空字符串
    if len(parts) == 1:
        return pd.Series([parts[0], ""])
    # 拆分成功返回两部分
    return pd.Series(parts)

df[['FirstName', 'Business_Title']] = df['FirstName'].apply(split_name_title)

额外优化:修复SQL注入风险

原代码中用字符串拼接生成SQL语句存在注入风险,改用参数化查询(表名需单独校验合法性):

# 替换原SQL更新代码块:
# update_query = f"""
#     UPDATE {table_name}
#     SET FirstName = '{row['FirstName']}',
#         LastName = '{row['LastName']}',
#         Business_Title = '{row['Business_Title']}',
#         {row['MMM_Format']}_Unique = {row[f"{row['MMM_Format']}_Unique"]},
#         {row['MMM_Format']}_ENC = {row[f"{row['MMM_Format']}_ENC"]}
#     WHERE FirstName = '{row['FirstName']}' AND LastName = '{row['LastName']}'
# """
# cursor.execute(update_query)

# 改成:
import re

# 校验并清理表名,只保留合法字符
valid_table_name = re.sub(r'[^a-zA-Z0-9_]', '', row['Loc Name'].replace(' ', '_').replace('-', '_'))
# 参数化SQL,表名无法参数化,需提前校验
update_query = f"""
    UPDATE {valid_table_name}
    SET FirstName = ?,
        LastName = ?,
        Business_Title = ?,
        {row['MMM_Format']}_Unique = ?,
        {row['MMM_Format']}_ENC = ?
    WHERE FirstName = ? AND LastName = ?
"""
# 传入参数列表
cursor.execute(update_query,
               row['FirstName'],
               row['LastName'],
               row['Business_Title'],
               row[f"{row['MMM_Format']}_Unique"],
               row[f"{row['MMM_Format']}_ENC"],
               row['FirstName'],
               row['LastName'])

内容的提问来源于stack exchange,提问作者jarodmwk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 01:12:32