【案例】用 pandas 讀取 excel 數據並上載到 MSSQL Server


環境 & 工具

Python 3.6
Spyder
Win7

代碼

# -*- conding: utf-8 _*_
'''

'''

import os
import time
import functools
import pandas as pd
from auto_ave import getPath
from datetime import datetime, timedelta, date


now = lambda : time.perf_counter()
dat = lambda n: date.today() - timedelta(n)

D = dat(1)

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print("Call %s():" % func.__name__)
        return func(*args, **kw)
    return wrapper

@log
def readAve(shtName):
    path = getPath()
    df = pd.read_excel(path, sheet_name=shtName, skiprows=1)
    dataCleaning(df)
    return df.loc[:, :'Notes'], df.loc[:, 'Notes':]

def getQ(dat):
    # 返回給定日期所在季度
    # 目的:剔除非本季度的消費
    if isinstance(dat, date):
        m = dat.month
        if m in (1, 2, 3):
            return 'Q1'
        elif m in (4, 5, 6):
            return 'Q2'
        elif m in (7, 8, 9):
            return 'Q3'
        else:
            return 'Q4'
    else:
        raise

def dataCleaning(df):
    
    def dropBracket(lis):
        # 去除字段中的 圓括號 ()
        return tuple(map(
            lambda s: s.replace('(', ' ').replace(')', '')
            , lis))
    
    def dropDat(df, col):
        # 刪除非本季度消費字段
        return df.drop(columns=col, inplace=True)
    
    def transferDat():
        # '字段日期格式轉換: datetime/str -> %Y%m%d'
        # 剔除非本季度的消費
        newCols = []
        for col in df.columns:
            n = 1
            try:
                if getQ(col) == getQ(D):
                    col_ = col.strftime('%Y%m%d')
                    newCols.append(col_)
                else:
                    # 刪除非本季度消費
                    dropDat(df, col)
            except:
                try:
                    col_ = datetime.strptime(col, '%Y-%m-%d %H:%M:%S.%f'
                                             ).strftime('%Y%m%d')
                    if col_ in newCols:
                        col_1 = col_ + '_' + str(n)
                        n += 1
                        if col_1 in newCols:
                            col_2 = col_ + '_' + str(n)
                            newCols.append(col_2)
                        else:
                            newCols.append(col_1)
                    else:
                        newCols.append(col_)
                except ValueError:
                    newCols.append(col)
        return dropBracket(newCols)
    
    def consistentType(df):
        # 統一數據類型爲 datetime.datetime
        # datetime.time(0,0) -> None
        df['首次消費日_1'] = df['首次消費日']
        df['Campaign Start Date'] = df['首次消費日']
        df['Campaign Start Date_1'] = df['首次消費日']
        
    def dropZeroSpending(df):
        # 當前年度 2020
        # 前一年度 2019
        index = df[df['2019YTD'] + df['2020YTD'] == 0].index
        df.drop(index=index, inplace=True)
    
    consistentType(df)
    #dropZeroSpending(df)  # 降低時間有限;且影響 output結果帶來不確定
    # 增加標識列,拆爲兩部分上載,便於再次合併
    df['用戶名1'] = df['用戶名']
    # 日期轉換
    df.columns = transferDat()

@log
def upLoad(getTable, shtName):
    def dropNotes(df):
        df.drop(columns='Notes', inplace=True)  # 多一列 Notes
        
    def getDate(ver):
        return D.strftime('%Y%m%d') + ver
        
    def getName():
        if '搜' in shtName:
            return 'P4P_' + getDate('_1'), 'P4P_' + getDate('_2')
        elif '新' in shtName:
            return 'NP_' + getDate('_1'), 'NP_' + getDate('_2')
        elif '原' in shtName:
            return 'Infeeds_' + getDate('_1'), 'Infeeds_' + getDate('_2')
    
    t, t1 = getTable(shtName)
    table1, table2 = getName()
    dropNotes(t)
    # 寫入
    with connect().begin() as en:
        t.to_sql(table1, con=en, if_exists='replace', index=False
                 , chunksize=1000)
        t1.to_sql(table2, con=en, if_exists='replace', index=False
                  , chunksize=1000)

def connect():
    def loginInfo(section):
        from configparser import ConfigParser
        path = r'H:\SZ_數據\Python\c.s.conf'
        #path = r'C:\users\chen.huaiyu\Chinasearch\c.s.conf'
        conf = ConfigParser()
        conf.read(path)
        return (conf.get(section, 'acc'), conf.get(section, 'pw')
                , conf.get(section, 'ip'), conf.get(section, 'port')
                , conf.get(section, 'dbname'))
    
    from sqlalchemy import create_engine
    try:
        engine = create_engine(
            'mssql+pymssql://%s:%s@%s:%s/%s' % loginInfo('Output'))
    except Exception as e:
        print('連接失敗: %s' % e)
        raise
    else:
        return engine

if __name__ == '__main__':
	for shtName in'搜索', '原生'):
		upLoad(readAve, shtName)
		

Q/A

  1. spyder中調試正常,cmd - R 命令行下運行異常
    小結

    • 命令行動行模式下,當 df 中無對應數據時,報錯;
    • 注意:數據審查;
    # excel -> mssql
    # 日期列中的 '-' -> None
    datList = ['開戶日期', '首次消費日', '收取年服務費時間', '主體資質到期日',
               '加V繳費到期日']
    for colName in datList:
        df.loc[df[colName] == '-', colName] = None
    ********
    # ValueError: could not convert string to Timestamp
    
  2. 上載時,字段名中有英文小括號時引發異常:ProgrammingError: (102, b"Incorrect syntax near '('.DB-Lib error message 20018, severity 15:\nGeneral SQL Server error: Check messages from the SQL Server\n")

    ProgrammingError
    因編程錯誤而引發的異常,例如,找不到或已經存在表,SQL語句中的語法錯誤,指定的參數數量錯誤等。
    此錯誤是DBAPI錯誤,起源於數據庫驅動程序(DBAPI),而不是SQLAlchemy本身。
    該ProgrammingError有時在數據庫連接的情況下驅動程序引發的被丟棄,或者不能夠連接到數據庫。

    小結: pandas 上載數據至 MSSQL注意事項(to_sql):

    1. 列數 < 256
    2. 標準化字段命名
      應符合 MSSQL 對常規標識符的使用規則:a-z,A-Z,_,@,#,$等;
      不能有特殊符號,如'(</'等;
    3. 統一各列的數據類型
      每一列只能存儲一種數據類型的數據;
  3. for 循環的流程圖
    在這裏插入圖片描述

  4. 文本操作

with open('t.txt', 'r', encoding='utf-8') as f:
	f.read([size])  # 讀取1個字符串,儘可能多的讀取,可能一次性讀入文件
	# f.readlines()  # 1.將每行作爲一個字符,一次性讀入全部文件
	# f.readline()  # 一次讀取一行
	# f.seek(偏移量, [起始位置])  # 1.偏移量:單位爲比特,可正可負;2.起始位置:0-文件頭,默認;1-當前位置;2-文件尾;
	# f.tell()  # 獲取當前指針位置
	# f.write()  # 寫入文件,必須是字符串
	# f.close()
  1. 文件複製
# shutil.copy(source, destination)  需os.chdir()配合下完成複製
# shutil.copytree()  不論何種路徑下均可完成複製
# os.rename(name, newname)  修改文件名
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章