def save_to_mysql_or_local_excel(df, table_name, username, password, host, database):

    try:

        # 创建数据库连接
        engine = sa.create_engine(f"mysql+pymysql://{username}:{password}@{host}/{database}")
        Session = sessionmaker(bind=engine)
        session = Session()

        inspector = sa.inspect(engine)
        tables_in_database = inspector.get_table_names()

        if table_name not in tables_in_database:
            df.to_sql(table_name, engine, if_exists='fail', index=False)
        else:
            primary_key_column = 'address'
            updated_fields = [col for col in df.columns if col not in [primary_key_column, 'id']]
            block_field =  'is_blocked'
            # 从数据库表中获取现有的主键和待更新字段的值
            # 从数据库表中获取现有的所有字段的值
            existing_data_query = f"SELECT {', '.join([primary_key_column] + updated_fields)} FROM {table_name}"
            existing_data = pd.read_sql(existing_data_query, engine)

            # # 确定需要更新的数据：在df中存在的address且cost不同
            # df_to_update = pd.merge(df, existing_data, on=primary_key_column, how='inner', indicator=True)
            # df_to_update = df_to_update[df_to_update[updated_field + "_x"] != df_to_update[updated_field + "_y"]]
            # 检测哪些行需要更新
            comparison_fields = [primary_key_column] + updated_fields
            df_to_update = df.merge(existing_data, on=primary_key_column, suffixes=('', '_old'), how='inner')
            df_to_update = df_to_update[df_to_update.apply(lambda x: not all(x[field] == x[f"{field}_old"] for field in updated_fields), axis=1)]


            def safe_compare(a, b):
                # 检查是否两个值都是数字
                if pd.api.types.is_number(a) and pd.api.types.is_number(b):
                    # 转换为浮点数进行比较
                    return float(a) == float(b)
                elif pd.isna(a) and pd.isna(b):
                    # 如果两个值都是 NaN，我们认为它们相等
                    return True
                else:
                    # 对于非数值，我们仅进行普通比较
                    return a == b

            def get_differences(row):
                differences = {}
                for field in updated_fields:
                    new_value = row[field]
                    old_value = row[f"{field}_old"]
                    if field == 'effects':  # 特殊处理 JSON 字段
                        try:
                            # 尝试比较 JSON 对象而不是 JSON 字符串
                            old_json = json.loads(row[f"{field}_old"])
                            new_json = json.loads(row[field])
                            if old_json != new_json:
                                # differences.append(field)
                                differences[field] = { 'new' : new_value, 'old': old_value}
                        except json.JSONDecodeError as e:
                            print(f"Error decoding JSON for field {field} at address {row['address']}: {e}")
                            # differences.append(field)  # 如果 JSON 解析失败，标记为不同
                            differences[field] = { 'new' : new_value, 'old': old_value}
                    else:
                        # 如果 type 不等于 0，则跳过 'Attack' 和 'Health' 字段
                        if row['type'] != 0 and field in ['attack', 'health']:
                            continue
                        
                        if not safe_compare(new_value, old_value):
                            differences[field] = { 'new' : new_value, 'old': old_value}
                            # differences.append(field)
                # print("differences:", differences)
                return differences

            df_to_update['differences'] = df_to_update.apply(get_differences, axis=1)

            # 只选择那些存在差异的行
            df_to_update = df_to_update[df_to_update['differences'].apply(bool)]

            # 打印出将要更新的行数
            print('Number of rows to be updated: ', df_to_update.shape[0])

            # 打印出不同的字段
            # 打印出具体不同的字段及其新旧值
            for index, row in df_to_update.iterrows():
                if row['differences']:
                    diffs = ["{field}: new({new_val}) vs old({old_val})".format(
                        field=field,
                        new_val=differences['new'],
                        old_val=differences['old']
                    ) for field, differences in row['differences'].items()]
                    differences_str = ', '.join(diffs)
                    # print(f"Address {row[primary_key_column]} - Differences: {differences_str}")
            

             # 对于需要更新的每一行
            for _, row in df_to_update.iterrows():
                # 创建一个字典，其中键是列名，值是新值
                row_dict = row.to_dict()

                # 创建一个空列表以保存每个字段的更新语句
                updates = []
                for field in updated_fields:
                    if row_dict[field] is not None:
                    # 这里假设所有的字段都是字符串类型，做了字符串的转义处理，如果不全是字符串，需要根据实际情况适当修改
                        updates.append(f"{field} = '{row_dict[field]}'")
                # print(row_dict['effects'])


                # 用","拼接所有的更新语句
                updates_str = ", ".join(updates)

                # 使用拼接好的更新语句创建一个完整的SQL UPDATE语句
                update_query = sa.text(f"UPDATE {table_name} SET {updates_str} WHERE {primary_key_column} = '{row_dict[primary_key_column]}'")
        
                result = session.execute(update_query)

            # 确定需要插入的新数据：在df中存在但在数据库表中不存在的address
            df_to_insert = pd.merge(df, existing_data, on=primary_key_column, how='outer', indicator=True)
            df_to_insert = df_to_insert[df_to_insert['_merge'] == 'left_only']

            # 打印出将要插入的行数
            print('Number of rows to be inserted: ', df_to_insert.shape[0])


            # 插入新行到表格中（不使用inplace=True，将结果赋值回给df_to_insert）
            df_to_insert = df_to_insert.drop(columns=['_merge'])
            # df_to_insert = df_to_insert.rename(columns={updated_field + '_x': updated_field})
            df_to_insert.to_sql(table_name, engine, if_exists='append', index=False)

            

             # 确定在数据库中存在，但在df中不存在的数据
            df_to_block = pd.merge(df, existing_data, on=primary_key_column, how='right', indicator=True)

            # 打印出将要被标记为禁用的行数
            df_to_block = df_to_block[df_to_block['_merge'] == 'right_only'].dropna(subset=['address'])

            df_to_block = df_to_block.dropna(subset=['address'])
            # 打印出将要被标记为禁用的行数
            print('Number of vaild rows to be blocked: ', df_to_block.shape[0])

            addresses = "','".join(df_to_block[primary_key_column])
            addresses = f"'{addresses}'"

            # 使用DataFrame中的地址来构建一组独特的地址
            unique_addresses = set(df_to_block[primary_key_column].dropna())

            # 使用SQLAlchemy的绑定参数来构建更新语句
            update_query = sa.text("""
                UPDATE skills
                SET is_blocked = 1
                WHERE address IN :addresses
            """)

            # with engine.begin() as connection:
            result = session.execute(update_query, {'addresses': tuple(unique_addresses)})
            print(f"{result.rowcount} records in skills have been blocked.")


            delete_query = sa.text(f"""
                DELETE FROM skills 
                WHERE is_blocked = 1 
                AND address IN ({addresses})
                AND NOT EXISTS (
                    SELECT 1 
                    FROM cards_skill_links
                    WHERE skills.id = cards_skill_links.skill_id
                )
            """)

            # block_cards_query = sa.text(f"""
            #     UPDATE cards 
            #     SET is_blocked = 1
            #     WHERE id IN (
            #         SELECT card_id
            #         FROM cards_skill_links
            #         JOIN skills ON 
            #         skills.id = cards_skill_links.skill_id
            #         AND skills.is_blocked = 1
            #         AND skills.address IN ({addresses})
            #     )
            #     OR skill IS NULL
            # """)
            block_cards_query = sa.text(f"""
                UPDATE cards 
                SET is_blocked = 1
                WHERE id IN (
                    SELECT card_id 
                    FROM (
                        -- 这是第一条更新语句中的查询部分
                        SELECT cards.id as card_id
                        FROM cards
                        LEFT JOIN cards_skill_links ON cards.id = cards_skill_links.card_id
                        WHERE cards_skill_links.skill_id IS NULL

                        UNION

                        -- 这是第二条更新语句中的查询部分
                        SELECT card_id
                        FROM cards_skill_links
                        LEFT JOIN skills ON skills.id = cards_skill_links.skill_id
                        WHERE 
                            (skills.is_blocked = 1 AND skills.address IN ({addresses}))
                            OR cards_skill_links.skill_id IS NULL
                    ) as temp_table
                );
            """)

            # with engine.begin() as connection:
                # result = connection.execute(delete_query)
                # print(f"Deleted {result.rowcount} records from skills.")

                # result = connection.execute(block_cards_query)
                # print(f"Blocked {result.rowcount} records on cards.")

            result = session.execute(delete_query)
            print(f"Deleted {result.rowcount} records from skills.")

            result = session.execute(block_cards_query)
            print(f"Blocked {result.rowcount} records on cards.")

            update_card_attr_query = sa.text(f"""
                UPDATE cards
                INNER JOIN cards_skill_links ON cards.id = cards_skill_links.card_id
                INNER JOIN skills ON skills.id = cards_skill_links.skill_id
                SET 
                    cards.Cost = skills.Cost,
                    cards.Attack = skills.Attack,
                    cards.Health = skills.Health,
                    cards.card_class = skills.card_class,
                    cards.Type = skills.Type,
                    cards.Description = skills.effect_desc
            """)

            result = session.execute(update_card_attr_query)
            print(f"Update {result.rowcount} records on cards.")

            # # 将不在df中但存在于数据库的数据标记为禁用
            # for _, row in df_to_block.iterrows():
            #     block_query = sa.text(f"UPDATE {table_name} SET is_blocked = 1 WHERE {primary_key_column}='{row[primary_key_column]}'")
            #     with engine.begin() as connection:
            #         result = connection.execute(block_query)

            # # 在数据库中查找被阻止 skill 的 id
            # skill_ids_query = "SELECT id FROM skills WHERE is_blocked = 1"

            # with engine.connect() as connection:
            #     skill_ids = pd.read_sql(skill_ids_query, connection)

            # # 构建 SQL 语句
            # sql = f"UPDATE cards SET is_blocked = 1 WHERE skill IN ({','.join(map(str, skill_ids['id'].values))})"

            # # 执行 SQL 语句
            # with engine.begin() as connection:
            #     result = connection.execute(sql)

            # # 打印影响的行数
            # print(f"Number of cards blocked: {result.rowcount}")  
            session.commit()

    except Exception as e:
        # 如果数据库操作出错，将数据存储为本地Excel文件
        print("Database operation error: ", str(e))

        save_to_local_excel(df)


def update_to_mysql_or_local_excel(df, table_name, username, password, host, database):

    try:

        # 创建数据库连接
        engine = sa.create_engine(f"mysql+pymysql://{username}:{password}@{host}/{database}")
        Session = sessionmaker(bind=engine)
        session = Session()

        inspector = sa.inspect(engine)
        tables_in_database = inspector.get_table_names()

        if table_name not in tables_in_database:
            df.to_sql(table_name, engine, if_exists='fail', index=False)
        else:
            primary_key_column = 'address'
            updated_fields = [col for col in df.columns if col not in ['id', 'address']]
            block_field =  'is_blocked'
            # 从数据库表中获取现有的主键和待更新字段的值
            existing_data = pd.read_sql(f'SELECT {primary_key_column} FROM {table_name}', engine)

            # 确定需要更新的数据：在df中存在的address且cost不同
            df_to_update = pd.merge(df, existing_data, on=primary_key_column, how='inner', indicator=True)
            # df_to_update = df_to_update[df_to_update[updated_field + "_x"] != df_to_update[updated_field + "_y"]]

            # 打印出将要更新的行数
            print('Number of rows to be updated: ', df_to_update.shape[0])

            # 确定需要插入的新数据：在df中存在但在数据库表中不存在的address
            df_to_insert = pd.merge(df, existing_data, on=primary_key_column, how='outer', indicator=True)
            df_to_insert = df_to_insert[df_to_insert['_merge'] == 'left_only']

            # 打印出将要插入的行数
            print('Number of rows to be inserted: ', df_to_insert.shape[0])


            # 插入新行到表格中（不使用inplace=True，将结果赋值回给df_to_insert）
            df_to_insert = df_to_insert.drop(columns=['_merge'])
            # df_to_insert = df_to_insert.rename(columns={updated_field + '_x': updated_field})
            df_to_insert.to_sql(table_name, engine, if_exists='append', index=False)

            # 对于需要更新的每一行
            for _, row in df_to_update.iterrows():
                # 创建一个字典，其中键是列名，值是新值
                row_dict = row.to_dict()

                # 创建一个空列表以保存每个字段的更新语句和参数
                updates = []
                params = {}
                for field in updated_fields:
                    if row_dict[field] is not None:
                        updates.append(f"{field} = :{field}")
                        params[field] = row_dict[field]

                # 创建SQL UPDATE语句，只在有更新需要时执行
                if updates:
                    updates_str = ", ".join(updates)
                    params['primary_key'] = row_dict[primary_key_column]
                    update_query = sa.text(f"""
                        UPDATE {table_name} SET {updates_str} WHERE {primary_key_column} = :primary_key
                    """)
                    session.execute(update_query, params)

            update_exclude_cards = ["红雨如梭","符湘灵"]
            # 假设占位符是 %s，根据您的数据库可能需要调整此占位符
            placeholders = ",".join([f":card_name_{i}" for i in range(len(update_exclude_cards))])
            update_card_attr_query = sa.text(f"""
                UPDATE cards
                INNER JOIN cards_skill_links ON cards.id = cards_skill_links.card_id
                INNER JOIN skills ON skills.id = cards_skill_links.skill_id
                SET
                    cards.Cost = skills.Cost,
                    cards.Attack = skills.Attack,
                    cards.Health = skills.Health,
                    cards.card_class = skills.card_class,
                    cards.Type = skills.Type,
                    cards.Description = skills.effect_desc
                WHERE cards.Name NOT IN ({placeholders})
            """)

            # Mapping card names to named placeholders
            bind_parameters = {f"card_name_{i}": card for i, card in enumerate(update_exclude_cards)}
            update_card_attr_query = update_card_attr_query.bindparams(**bind_parameters)

            result = session.execute(update_card_attr_query)
            print(f"Update {result.rowcount} records on cards.")

            session.commit()

            

    except Exception as e:
        # 如果数据库操作出错，将数据存储为本地Excel文件
        print("Database operation error: ", str(e))
        session.rollback()
        save_to_local_excel(df)
    finally:
        session.close()

//minion_skills.json 

Database operation error:  (pymysql.err.OperationalError) (1054, "Unknown column 'attack_x' in 'field list'")
[SQL: INSERT INTO skills (address, attack_x, health_x, effects_x, effect_desc_x, cost_x, card_class_x, is_blocked_x, type_x, attack_y, health_y, effects_y, effect_desc_y, cost_y, card_class_y, is_blocked_y, type_y) VALUES (%(address)s, %(attack_x)s, %(health_x)s, %(effects_x)s, %(effect_desc_x)s, %(cost_x)s, %(card_class_x)s, %(is_blocked_x)s, %(type_x)s, %(attack_y)s, %(health_y)s, %(effects_y)s, %(effect_desc_y)s, %(cost_y)s, %(card_class_y)s, %(is_blocked_y)s, %(type_y)s)]
[parameters: ({'address': '410100000100', 'attack_x': 0, 'health_x': 1, 'effects_x': '[{"keyword": 15, "triggerEffects": [{"luaPath": "CGBuffOneCardAttack", "targetType": 82, "playerInputEventType": 0, "targetValue": 1, "targetDirection": 0}, {"luaPath": "CGBuffOneCardHealth", "playerInputEventType": 0, "targetType": 82, "targetValue": 0, "targetDirection": 0}]}]', 'effect_desc_x': '咒响:己方每使用一张魔法/陷阱卡，自身+1/+0', 'cost_x': 2.0, 'card_class_x': -1.0, 'is_blocked_x': 0.0, 'type_x': 0.0, 'attack_y': None, 'health_y': None, 'effects_y': None, 'effect_desc_y': None, 'cost_y': None, 'card_class_y': None, 'is_blocked_y': None, 'type_y': None}, {'address': '410101000100', 'attack_x': 0, 'health_x': 1, 'effects_x': '[{"keyword": 15, "triggerEffects": [{"luaPath": "CGBuffOneCardAttack", "targetType": 82, "playerInputEventType": 0, "targetValue": 1, "targetDirection": 0}, {"luaPath": "CGBuffOneCardHealth", "playerInputEventType": 0, "targetType": 82, "targetValue": 1, "targetDirection": 0}]}]', 'effect_desc_x': '咒响:己方每使用一张魔法/陷阱卡，自身+1/+1', 'cost_x': 2.0, 'card_class_x': -1.0, 'is_blocked_x': 0.0, 'type_x': 0.0, 'attack_y': None, 'health_y': None, 'effects_y': None, 'effect_desc_y': None, 'cost_y': None, 'card_class_y': None, 'is_blocked_y': None, 'type_y': None}, {'address': '160410001000100', 'attack_x': 0, 'health_x': 1, 'effects_x': '[{"keyword": 11, "triggerEffects": []}, {"keyword": 15, "triggerEffects": [{"luaPath": "CGBuffOneCardAttack", "targetType": 82, "playerInputEventType ... (20 characters truncated) ...  0, "targetDirection": 0}, {"luaPath": "CGBuffOneCardHealth", "playerInputEventType": 0, "targetType": 82, "targetValue": 1, "targetDirection": 0}]}]', 'effect_desc_x': '压制,咒响:己方每使用一张魔法/陷阱卡，自身+0/+1', 'cost_x': 2.0, 'card_class_x': -1.0, 'is_blocked_x': 0.0, 'type_x': 0.0, 'attack_y': None, 'health_y': None, 'effects_y': None, 'effect_desc_y': None, 'cost_y': None, 'card_class_y': None, 'is_blocked_y': None, 'type_y': None}, {'address': '160410100000100', 'attack_x': 0, 'health_x': 1, 'effects_x': '[{"keyword": 11, "triggerEffects": []}, {"keyword": 15, "triggerEffects": [{"luaPath": "CGBuffOneCardAttack", "targetType": 82, "playerInputEventType ... (20 characters truncated) ...  1, "targetDirection": 0}, {"luaPath": "CGBuffOneCardHealth", "playerInputEventType": 0, "targetType": 82, "targetValue": 0, "targetDirection": 0}]}]', 'effect_desc_x': '压制,咒响:己方每使用一张魔法/陷阱卡，自身+1/+0', 'cost_x': 2.0, 'card_class_x': -1.0, 'is_blocked_x': 0.0, 'type_x': 0.0, 'attack_y': None, 'health_y': None, 'effects_y': None, 'effect_desc_y': None, 'cost_y': None, 'card_class_y': None, 'is_blocked_y': None, 'type_y': None}, {'address': '250410001000100', 'attack_x': 0, 'health_x': 1, 'effects_x': '[{"keyword": 1, "triggerEffects": [{"luaPath": "CGMoveCardsToHandDeck", "targetType": 98, "targetValue": 0, "playerInputEventType": 11, "targetDirect ... (141 characters truncated) ...  0, "targetDirection": 0}, {"luaPath": "CGBuffOneCardHealth", "playerInputEventType": 0, "targetType": 82, "targetValue": 1, "targetDirection": 0}]}]', 'effect_desc_x': '战吼:使选定随从移回手牌,咒响:己方每使用一张魔法/陷阱卡，自身+0/+1', 'cost_x': 4.0, 'card_class_x': -1.0, 'is_blocked_x': 0.0, 'type_x': 0.0, 'attack_y': None, 'health_y': None, 'effects_y': None, 'effect_desc_y': None, 'cost_y': None, 'card_class_y': None, 'is_blocked_y': None, 'type_y': None}, {'address': '250410100000100', 'attack_x': 0, 'health_x': 1, 'effects_x': '[{"keyword": 1, "triggerEffects": [{"luaPath": "CGMoveCardsToHandDeck", "targetType": 98, "targetValue": 0, "playerInputEventType": 11, "targetDirect ... (141 characters truncated) ...  1, "targetDirection": 0}, {"luaPath": "CGBuffOneCardHealth", "playerInputEventType": 0, "targetType": 82, "targetValue": 0, "targetDirection": 0}]}]', 'effect_desc_x': '战吼:使选定随从移回手牌,咒响:己方每使用一张魔法/陷阱卡，自身+1/+0', 'cost_x': 4.0, 'card_class_x': -1.0, 'is_blocked_x': 0.0, 'type_x': 0.0, 'attack_y': None, 'health_y': None, 'effects_y': None, 'effect_desc_y': None, 'cost_y': None, 'card_class_y': None, 'is_blocked_y': None, 'type_y': None}, {'address': '152410001000100', 'attack_x': 0, 'health_x': 1, 'effects_x': '[{"keyword": 7, "triggerEffects": [{"luaPath": "CGBuffCardsShield", "targetType": 82, "targetValue": 2, "targetDirection": 2, "playerInputEventType": ... (255 characters truncated) ...  0, "targetDirection": 0}, {"luaPath": "CGBuffOneCardHealth", "playerInputEventType": 0, "targetType": 82, "targetValue": 1, "targetDirection": 0}]}]', 'effect_desc_x': '给身边的2个随从施加星护,咒响:己方每使用一张魔法/陷阱卡，自身+0/+1', 'cost_x': 5.0, 'card_class_x': -1.0, 'is_blocked_x': 0.0, 'type_x': 0.0, 'attack_y': None, 'health_y': None, 'effects_y': None, 'effect_desc_y': None, 'cost_y': None, 'card_class_y': None, 'is_blocked_y': None, 'type_y': None}, {'address': '152410100000100', 'attack_x': 0, 'health_x': 1, 'effects_x': '[{"keyword": 7, "triggerEffects": [{"luaPath": "CGBuffCardsShield", "targetType": 82, "targetValue": 2, "targetDirection": 2, "playerInputEventType": ... (255 characters truncated) ...  1, "targetDirection": 0}, {"luaPath": "CGBuffOneCardHealth", "playerInputEventType": 0, "targetType": 82, "targetValue": 0, "targetDirection": 0}]}]', 'effect_desc_x': '给身边的2个随从施加星护,咒响:己方每使用一张魔法/陷阱卡，自身+1/+0', 'cost_x': 5.0, 'card_class_x': -1.0, 'is_blocked_x': 0.0, 'type_x': 0.0, 'attack_y': None, 'health_y': None, 'effects_y': None, 'effect_desc_y': None, 'cost_y': None, 'card_class_y': None, 'is_blocked_y': None, 'type_y': None}  ... displaying 10 of 7152 total bound parameter sets ...  {'address': '041410101140500', 'attack_x': 14, 'health_x': 5, 'effects_x': '[{"keyword": 1, "triggerEffects": [{"luaPath": "CGHealCardsOrHeroes", "playerInputEventType": 11, "targetType": 594, "targetValue": 1, "targetDirecti ... (140 characters truncated) ...  1, "targetDirection": 0}, {"luaPath": "CGBuffOneCardHealth", "playerInputEventType": 0, "targetType": 82, "targetValue": 1, "targetDirection": 0}]}]', 'effect_desc_x': '战吼:治疗1点生命值,咒响:己方每使用一张魔法/陷阱卡，自身+1/+1', 'cost_x': 10.0, 'card_class_x': 1.0, 'is_blocked_x': 0.0, 'type_x': 0.0, 'attack_y': None, 'health_y': None, 'effects_y': None, 'effect_desc_y': None, 'cost_y': None, 'card_class_y': None, 'is_blocked_y': None, 'type_y': None}, {'address': '045410001140500', 'attack_x': 14, 'health_x': 5, 'effects_x': '[{"keyword": 1, "triggerEffects": [{"luaPath": "CGHealCardsOrHeroes", "playerInputEventType": 11, "targetType": 594, "targetValue": 5, "targetDirecti ... (140 characters truncated) ...  0, "targetDirection": 0}, {"luaPath": "CGBuffOneCardHealth", "playerInputEventType": 0, "targetType": 82, "targetValue": 1, "targetDirection": 0}]}]', 'effect_desc_x': '战吼:治疗5点生命值,咒响:己方每使用一张魔法/陷阱卡，自身+0/+1', 'cost_x': 10.0, 'card_class_x': 1.0, 'is_blocked_x': 0.0, 'type_x': 0.0, 'attack_y': None, 'health_y': None, 'effects_y': None, 'effect_desc_y': None, 'cost_y': None, 'card_class_y': None, 'is_blocked_y': None, 'type_y': None})]
帮我分析下什么问题