孤荷凌寒自學python第四十八天通用同一資料庫中複製資料表函式最終完成
孤荷凌寒自學python第四十八天通用同一資料庫中複製資料表函式最終完成
(完整學習過程螢幕記錄視訊地址在文末)
今天繼續建構自感覺用起來順手些的自定義模組和類的程式碼。
今天經過反覆折騰,最終基本上算是比較好地完成了這個在同一資料庫中複製資料表的函式,且同一函式支援目前研究的四種資料庫(mysql,mssql,access,sqlite).
一、首先是到今天完成為止的_mdb.py檔案中的全部內容:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pypyodbc
import pymysql
import pymssql
import sqlite3
import os.path
import _mty
mdbErrString='' #供其它模組使用的全域性變量了,實時儲存了各函式執行時的錯誤資訊
def msgbox(info,titletext='孤荷凌寒的DB模組對話方塊QQ578652607',style=0,isShowErrMsg=False):
return _mty.msgboxGhlh(info,titletext,style,isShowErrMsg)
#連線網路資料庫,目前支援mssql,mysql
def conNetdbGhlh(serveraddress,usr,pw,dbname,dbtype='mssql',isShowMsg=False):
'''
用於連線網路資料庫,目前支援連線mssql,mysql兩種網路關係型資料庫,
dbtype可選形參預設值是操作mssql,如果要連線Mysql則通過此可選形參指定:為mysql
,此函式返回一個connect資料庫連線物件
'''
global mdbErrString
mdbErrString=''
try:
if dbtype=='mssql':
con=pymssql.connect(serveraddress,usr,pw,dbname,charset='utf8')
return con
elif dbtype=='mysql':
con=pymysql.connect(serveraddress,usr,pw,dbname)
return con
else:
return None
except Exception as e:
mdbErrString='連線網路資料庫【' + serveraddress + '】【' + dbname + '】時出錯:' + str(e) + '\n此函式由【孤荷凌寒】建立,QQ578652607'
if isShowMsg==True:
msgbox(mdbErrString)
return None
else:
pass
finally:
pass
#連線本地資料庫檔案,目前支援db,mdb,accdb,s3db
def conLocaldbGhlh(dbfilepath,strPass='',isShowMsg=False):
'''
連線本地資料庫檔案,目前支援mdb,accdb,以及sqlite資料庫檔案,識別方法是,如果有後綴mdb或accdb,則說明是access資料庫檔案,否則都認為是sqlite資料庫檔案。
如果連線成功,將返回一個con資料庫連線物件
'''
global mdbErrString
mdbErrString=''
try:
strhznm=_mty.getFilehzGhlh(dbfilepath)
if strhznm.find('mdb')>-1 or strhznm.find('accdb')>-1:
#---連線access資料庫----
if strPass=='':
strname='Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ=' + dbfilepath
else:
strname='Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ=' + dbfilepath + ';Pwd=' + strPass
con=pypyodbc.connect(strname)
return con
else:
#----連線sqlite資料庫-----
con=sqlite3.connect(dbfilepath)
return con
except Exception as e:
mdbErrString='連線網路資料庫檔案【' + dbfilepath + '】時出錯:' + str(e) + '\n此函式由【孤荷凌寒】建立qq號是578652607'
if isShowMsg==True:
msgbox(mdbErrString)
return None
else:
pass
finally:
pass
#刪除資料庫中的表
def delTableGhlh(con,strtablenm,isShowMsg=False):
'''
此方法將刪除指定conn中的table,不管table中是否有資料,因此 操作要謹慎
,成功返回True 失敗返回False
'''
global mdbErrString
mdbErrString=''
try:
if strtablenm is not None and strtablenm != '':
sql = 'DROP TABLE ' + strtablenm
cur=con.cursor()
cur.execute(sql)
con.commit()
cur.close()
if isShowMsg==True:
msgbox('刪除資料庫表[{}]成功!'.format(strtablenm))
return True
else:
if isShowMsg==True:
msgbox('the [{}] is empty or equal None!'.format(sql))
return False
except Exception as e:
mdbErrString='刪除資料庫中表【' + strtablenm + '】時出錯:' + str(e) + '\n此函式由【孤荷凌寒】建立qq號是578652607'
if isShowMsg==True:
msgbox(mdbErrString)
return False
else:
pass
finally:
pass
#建立一個新表
def newTableGhlh(con,strTableNm,lstnm:'list. 將所有要新建的欄位都放在一個列表中',lsttype,lstlength,dbtype='acc',lstNull=None,isDelExitsTable = False ,isAutoSetIDfieldAutoNumber=True,strSetFieldAutoNumberName='id',isShowMsg= False):
'''
傳遞有關表的每個欄位的三大屬性的分別的三個列表,
並可以指定此表的PRIMARY key 欄位,
及指定是否自動識別ID欄位為PRIMARY key 欄位,
如果要建立的表名是否存在,約定是否刪除舊錶
如果設定為不刪除舊錶,則放棄新建表;
'''
global mdbErrString
mdbErrString=''
try:
cur=con.cursor()
dbtype=dbtype.lower()
if dbtype=='access':
dbtype='acc'
except:
pass
#--------------------------------------------------------
try:
if strTableNm == "" or strTableNm.lower() == "select" or strTableNm.lower() == "from" or strTableNm.lower() == "where" or strTableNm.lower() == "order" or strTableNm.lower() == "insert" or strTableNm.lower() == "delete" or strTableNm.lower() == "in" or strTableNm.lower() == "with" or strTableNm.find("[") >-1 or strTableNm.find("]") >-1 :
mdbErrString = "要建立的資料表名為空或為不合法的保留關鍵字,請重新確認資料表名。" + '\n此函式由【孤荷凌寒】建立qq是578652607'
if isShowMsg == True:
msgbox(mdbErrString)
return False
if len(lstnm) != len(lsttype) or len(lsttype) != len(lstlength):
mdbErrString = "在新建一個數據表時,接收到的四個關於表中欄位屬性的列表引數中元素總數不相同,無法執行。" + '\n此函式由【孤荷凌寒】建立qq號:578652607'
if isShowMsg == True:
msgbox(mdbErrString)
return False
#現在先檢查表是否存在,如果存在,根據設定是刪除舊錶然後新建表呢,還是保留舊錶而不新建表
if isTableExistGhlh(con,strTableNm,isShowMsg)==True:
#--如果舊錶存在,就看是否要刪除舊錶---
if isDelExitsTable==True:
if delTableGhlh(con,strTableNm,isShowMsg)==False:
#--舊錶存在,但是卻刪除失敗的情況----
mdbErrString = "在新建一個數據表時,因為同名的舊錶已經存在了,但嘗試刪除舊錶失敗,所以無法新增一個表。" + '\n此函式由【孤荷凌寒】建立qq:578652607'
if isShowMsg == True:
msgbox(mdbErrString)
return False
else:
#成功刪除了舊錶,那麼就新增新表,直接順序到後面執行程式碼即可。
pass
else:
#如果舊錶存在,但又指定不刪除舊錶,那麼只好結束 本函式 過程了
mdbErrString = "在新建一個數據表時,因為同名的舊錶已經存在了,而又指定不能刪除舊錶,所以無法新增一個表。" + '\n此函式由【孤荷凌寒】建立qq是578652607'
if isShowMsg == True:
msgbox(mdbErrString)
return False
#現在準備開始新增新的表-----
intC=len(lstnm)
rals=range(intC)
strR=""
strRls=""
strNm=""
strLs=""
intL=0
strL=""
strN=""
for i in rals:
strNm=lstnm[i]
strLs =lsttype[i]
strLs = getStandardFieldTypeGhlh(strLs,dbtype,isShowMsg)
strLs=' ' + strLs
#-----------------------
intL=lstlength[i]
if intL<=0:
strL=''
else:
strL="(" + str(intL) + ")"
#----------------
strN=""
if lstNull != None:
try:
strN=lstNull[i]
except:
pass
#---------------
if strN=="" or strN==None:
strN=""
else:
strN=" " + strN
#----------
if strLs.find('NULL')>=0:
#-----如果已經在得到類別時,已經在字串中出現了null關鍵字,此處就不要再處理了
strN=""
#---------------
if dbtype!='mysql':
#上一條件式是因為,Mysql不允許在sql語句中出現 []括號
strNm='[' + strNm + ']'
strRls=strNm + strLs + strL + strN # 此時已經構建了類似於 【name varchar(20)】 這樣的內容了
#檢查是否主鍵--
if isAutoSetIDfieldAutoNumber==True:
#如果強制將欄位名稱為“id”的欄位作為主鍵,則
if strNm.lower()==strSetFieldAutoNumberName.lower():
if strR.find("PRIMARY KEY")<0:
#上一條件式是為了避免有多個primary key
if strRls.find("PRIMARY KEY")<0:
#上一條件式是為了防止在取得可用欄位型別時已新增過Primary key 了
strRls=strRls+" PRIMARY KEY"
#現在拼合 strR
if strR=="":
strR=strRls
else:
strR=strR + "," + strRls
#開始生成sql語句
strSql='CREATE TABLE ' + strTableNm + '(' + strR + ');'
#執行--
cur.execute(strSql)
con.commit() #提交所作的修改
#如果沒有出錯,就返回成功
return True
except Exception as e:
mdbErrString='嘗試建立表【' + strTableNm + '】時出錯:' + str(e) + '\n此函式由【孤荷凌寒】建立qq號是578652607'
if isShowMsg==True:
msgbox(mdbErrString)
return False
else:
pass
finally:
try:
cur.close()
except:
pass
#判斷一個表在資料庫中是否存在
def isTableExistGhlh(con,strtablenm,isShowMsg=False):
'''
判斷一張表是否在資料庫中存在
,需要傳入con資料庫連線物件
'''
global mdbErrString
mdbErrString=''
try:
cura=con.cursor()
return isTableExist2Ghlh(cura,strtablenm,isShowMsg)
except Exception as e:
mdbErrString='檢查表【' + strtablenm + '】是否存在時出錯(此錯誤一般說明表不存在):' + str(e) + '\n此函式由【孤荷凌寒】建立qq號是578652607'
if isShowMsg==True:
msgbox(mdbErrString)
return False
else:
pass
finally:
try:
cura.close
#pass
except:
pass
#判斷一個表在資料庫中是否存在2
def isTableExist2Ghlh(cur,strtablenm,isShowMsg=False):
'''
判斷一張表是否在資料庫中存在
,需要傳入資料庫操作指標物件
'''
global mdbErrString
mdbErrString=''
try:
strsql='SELECT * FROM ' + strtablenm + ';'
cur.execute(strsql)
return True
except Exception as e:
mdbErrString='檢查表【' + strtablenm + '】是否存在時出錯(此錯誤一般說明表不存在):' + str(e) + '\n此函式由【孤荷凌寒】建立qq號是578652607'
if isShowMsg==True:
msgbox(mdbErrString)
return False
else:
pass
finally:
pass
#將各種複雜的對資料庫型別的描述,如3,8等數值表示的欄位型別與,windows系統中的system.string,之類的描述,統一修改為資料庫能夠在定義欄位型別時直接使用的描述字串
def getStandardFieldTypeGhlh(strin,dbtype='acc',isShowMsg=False):
'''
將各種複雜的對資料庫型別的描述,如3,8等數值表示的欄位型別與,windows系統中的system.string,之類的描述,統一修改為資料庫能夠在定義欄位型別時直接使用的描述字串
'''
global mdbErrString
mdbErrString=''
strI=""
try:
strI=str(strin)
strI.lower()
strI=strI.replace('system.','') #windows系統中,以及其它一些語言中對資料型別的描述的字串中,可以包含有system.字首
strI=strI.replace('.','') #去掉多餘的點
dbtype=dbtype.lower()
if dbtype=='access':
dbtype='acc'
except:
pass
#--------------------------------------------------------
try:
if strI=='':
mdbErrString = "因為傳入的要識別的資料庫的欄位型別為空,因此無法識別,只能識別成【文字型別】【text】。" + '\n此函式由【孤荷凌寒】建立qq:578652607'
if isShowMsg == True:
msgbox(mdbErrString)
if dbtype!='acc' and dbtype!='mysql':
return 'ntext'
else:
return 'text'
#---正式識別開始---------------------
if strI in ("int32", "3", "int","int16", "integer", "long","smallint","tinyint","mediumint"):
if dbtype=='acc':
return 'long'
else:
return "int" #多數資料庫在這種情況下要額外指定長度
#----------------------
if strI=='bigint':
if dbtype=='acc' or dbtype=='sqlite':
return 'int'
else:
return 'bigint'
#-----------------
elif strI in ("memo","longtext","mediumtext"):
if dbtype=='acc':
return "memo"
elif dbtype=='mysql':
return "longtext"
else:
return 'ntext'
#------------------
elif strI in ("str","string","8","varchar","char","text","nvarchar","tinytext"):
if dbtype=='mysql' or dbtype=='acc':
return "varchar" #在這種情況下都需要指定長度
else:
return "nvarchar" #在這種情況下都需要指定長度
#------------------
elif strI in ("datetime","7"):
if dbtype=='sqlite':
return "date"
else:
return "datetime"
#----------------
elif strI=="date":
if dbtype!='acc':
return "date"
else:
return "datetime"
#-----------------
elif strI=="time":
if dbtype!='acc':
return "time"
else:
return "datetime"
#-----------------
elif strI in ("single", "4", "real"):
return "real"
#----------------
elif strI in ("double", "5", "float"):
return "float"
#----------------
elif strI in ("boolean", "11", "bit","bool"):
if dbtype=='mssql' or dbtype=='acc':
return "bit"
else:
return 'boolean'
#-----------------
elif strI in ("byte[]", "8209", "image", "binary", "ole"):
#---image為微軟專用的OLE,"Binary" 為 二進位制,在sqlite中使用blob,表示二進位制大資料
if dbtype=='acc' or dbtype=='mssql':
return "Image"
elif dbtype=='sqlite':
return 'blob'
else:
return 'binary'
#-------這是真正的全精度資料
elif strI in ("decimal", "14", "money","numeric"):
if dbtype=='sqlite':
return 'numeric'
elif dbtype=='acc':
return 'money'
else:
return 'decimal'
#--------------
elif strI=="timestamp":
if dbtype=='acc':
return 'double'
else:
return 'timestamp'
#------自動編號------
elif strI in ("auto", "autocount", "autonumber", "autono", "autoincrement","auto_increment"):
if dbtype=='mysql':
return 'int NOT NULL auto_increment'
elif dbtype=='acc':
return 'counter NOT NULL PRIMARY KEY'
elif dbtype=='mssql':
return 'int identity(1,1)'
else:
#--sqlite-----------------
return "integer PRIMARY KEY AUTOINCREMENT NOT NULL"
#--------
else:
#其餘情況,全部識別為 text
if dbtype!='acc' and dbtype!='mysql':
return 'ntext'
else:
return 'text'
except Exception as e:
mdbErrString='嘗試將各種不同的對資料庫欄位型別的描述轉換為標準欄位型別描述時出錯:' + str(e) + '\n此函式由【孤荷凌寒】建立qq號是578652607'
if isShowMsg==True:
msgbox(mdbErrString)
#------------------------------------------
if dbtype!='acc' and dbtype!='mysql':
return 'ntext'
else:
return 'text'
else:
pass
finally:
pass
#-----複製表的操作----------------
def copyDbTableGhlh(con,strst,strtt,dbtype='acc',strfieldlist='*',strprimarykey='id',strprimarykey2='',isstrprimarykeyautoaccount=False,isstrprimarykey2autoaccount=False,strprimarykeytype='integer',strprimarykey2type='str',iscopydata=False,isDelExitsTable=False,isShowMsg=False):
'''
此函式在同一資料庫內,進行表的複製,可以指定只複製表的結構還是連同表中資料也一起復制。不完美,部分情況下不能成功,特別是要同時複製表中的資料的情況下,有時不會成功。
con:資料庫連線物件;
dbtype:指定資料為的標識:acc(access),mssql,mysql,sqlite;
strfieldlist:要從源表中複製哪些列,預設複製全部列。此引數對mysql資料庫無效,mysql資料庫始終為*即全部欄位。
strprimarykey:指定複製表完成後,要指定哪個列名為 主鍵。此引數對Mysql資料庫無效。
strprimarykey2:指定複製表完成後,要指定的第二個為主鍵的列名,此引數只對Mssql資料庫有效,對其它資料庫沒有作用。但Mssql資料庫也只能在純複製表結構的情況下使用兩個主鍵列。
isstrprimarykeyautoaccount:與上兩個引數相關,指定要被 修改為主鍵的列,是否是一個自動計數 的資料型別的列。對Mysql資料庫和mssql資料庫無效。
isstrprimarykey2autoaccount:與strprimarykey2一樣,只對純複製表結構時的Mssql資料庫有效,其它情況下都沒有作用。
strprimarykeytype:要指定的主鍵列的資料型別,此引數對mysql和mssql資料庫無效;
strprimarykey2type:與strprimarykey2一樣,只對純複製表結構時的mssq資料庫有效。
iscopydata:是否要複製表中的資料,預設不復制,則只複製表的結構。如果此值為True則要複製表資料。
isDelExitsTable::如果要複製的目標表已經存在,則是否要刪除舊錶,重新複製產生新表,預設是不刪除舊錶。
isShowMsg:是否將錯誤資訊以對話方塊的形式彈出。
'''
try:
cur=con.cursor()
strOnlyTargetTableNm=strtt
strOnlySourceTableNm=strst
strTemp=''
if dbtype!='mysql': #mysql 不支援在sql語句中書寫方括號
strst='[' + strst + ']'
strtt='[' + strtt + ']'
strprimarykey=strprimarykey.strip()
strprimarykey2=strprimarykey2.strip()
#如果要複製到的目標 表已經存在 ,應當作一定的處理--------
if isTableExistGhlh(con,strtt,isShowMsg)==True:
#--如果舊錶存在,就看是否要刪除舊錶---
if isDelExitsTable==True:
if delTableGhlh(con,strtt,isShowMsg)==False:
#--舊錶存在,但是卻刪除失敗的情況----
mdbErrString = "在複製一個數據表時,因為同名的目標表的舊錶已經存在了,但嘗試刪除舊錶失敗,所以無法複製一個新的目標表。" + '\n此函式由【孤荷凌寒】建立qq:578652607'
if isShowMsg == True:
msgbox(mdbErrString)
return False
else:
#成功刪除了舊錶,那麼就新增新表,直接順序到後面執行程式碼即可。
pass
else:
#如果舊錶存在,但又指定不刪除舊錶,那麼只好結束 本函式 過程了
mdbErrString = "在複製一個數據表時,因為同名的目標表的舊錶已經存在了,而又指定不能刪除舊錶,所以無法複製一個新的目標表。" + '\n此函式由【孤荷凌寒】建立qq是578652607'
if isShowMsg == True:
msgbox(mdbErrString)
return False
#--------------------------
strSql='SELECT ' + strfieldlist + ' INTO ' + strtt + ' FROM ' + strst
if dbtype=='sqlite':
strSql='CREATE TABLE ' + strtt + ' AS SELECT ' + strfieldlist + ' FROM ' + strst
if iscopydata==False:
strSql=strSql + ' WHERE (1=2);'
else:
if dbtype!='mssql':
strSql=strSql + ';'
else:
if strprimarykey2!='':
#如果mssql資料庫要增加兩個Key欄位,那麼,只能先複製結構,再匯入資料
strSql=strSql + ' WHERE (1=2);'
else:
#如果只增加一個key欄位,則不受影響
strSql=strSql + ';'
if dbtype!='mysql':
#非Mysql的情況下,開始一步執行到位
if dbtype!='sqlite':
#在非sqlite資料庫的情況下,執行下面的操作
cur.execute(strSql) #除Mysql之外的資料庫的複製結果都不能得到完整的資料表資訊,至少 資料表的 主鍵資訊不會複製過來
con.commit()
#因此下面必須手動再指定主鍵資訊
if strprimarykey!='':
if strprimarykey2=='':
if dbtype=='acc':
#access資料庫第一步先刪除失敗的primary key 欄位,
cur.execute('alter table ' + strtt + ' drop COLUMN ' + strprimarykey + ';')
#然後新增一個欄位:
strautoaccounter=''
if isstrprimarykeyautoaccount==True:
strautoaccounter=' counter'
else:
strprimarykeytype=getStandardFieldTypeGhlh(strprimarykeytype)
strautoaccounter=' ' + strprimarykeytype #如果不是自動增加欄位,就使用預置的欄位型別
cur.execute('alter table ' + strtt + ' add COLUMN ' + strprimarykey + strautoaccounter + ' NOT NULL PRIMARY KEY;')
if dbtype=='mssql':
#mssql的處理也有兩步:
#第一步感覺可以取消----
#cur.execute('alter table ' + strtt + ' alter column ' + strprimarykey + ' int not null')
cur.execute('alter table ' + strtt + ' add CONSTRAINT PK_' + strOnlyTargetTableNm + ' PRIMARY KEY NONCLUSTERED ( ' + strprimarykey + ' )')
#----------------------------------------
else:
#---------------------------------------------
#如果有第二個Primarykey
if dbtype=='acc':
#---!!!!!!----access資料庫並不適用此方法------------
#access資料庫第一步先刪除失敗的primary key 欄位,
cur.execute('alter table ' + strtt + ' drop COLUMN ' + strprimarykey + ',' + strprimarykey2 + ';')
#然後新增一個欄位:
strautoaccounter=''
if isstrprimarykeyautoaccount==True:
strautoaccounter=' counter'
else:
strprimarykeytype=getStandardFieldTypeGhlh(strprimarykeytype)
strautoaccounter=' ' + strprimarykeytype #如果不是自動增加欄位,就使用預置的欄位型別
#然後增加另一個欄位:
strautoaccounter2=''
if isstrprimarykey2autoaccount==True:
strautoaccounter2=' counter'
else:
strprimarykey2type=getStandardFieldTypeGhlh(strprimarykey2type)
strautoaccounter2=' ' + strprimarykey2type #如果不是自動增加欄位,就使用預置的欄位型別
#執行---
cur.execute('alter table ' + strtt + ' add COLUMN ' + strprimarykey + strautoaccounter + ' NOT NULL;') #只能一次設定一個主鍵,因此預設讓第二個欄位為主鍵了
cur.execute('alter table ' + strtt + ' add COLUMN ' + strprimarykey2 + strautoaccounter2 + ' NOT NULL PRIMARY KEY;')
#---------------------------------------------------------
if dbtype=='mssql':
#-!!!!---插入SQL語句失敗---------
#mssql的處理也有兩步:
strprimarykeytype=getStandardFieldTypeGhlh(strprimarykeytype,'mssql')
strprimarykey2type=getStandardFieldTypeGhlh(strprimarykey2type,'mssql')
#第一步感覺可以取消----
cur.execute('alter table ' + strtt + ' alter column ' + strprimarykey + ' ' + strprimarykeytype + ' not null')
cur.execute('alter table ' + strtt + ' alter column ' + strprimarykey2 + ' ' + strprimarykey2type + ' not null')
cur.execute('alter table ' + strtt + ' add CONSTRAINT PK_' + strOnlyTargetTableNm + ' PRIMARY KEY NONCLUSTERED ( ' + strprimarykey + ',' + strprimarykey2 + ' )')
con.commit()
#如果要求匯入資料,此時再匯入資料
if iscopydata==True:
#應當先修改源表中指定欄位的所有行的值都不為Null 再匯入
#嘗試取出所有列資訊:
if strfieldlist=="" or strfieldlist=='*':
lstTemp=getTableAllFieldToAListGhlh(cur,strOnlySourceTableNm,'mssql')
else:
lstTemp=strfieldlist.split(',')
#---------------------
lstTemp2=[]
if isstrprimarykeyautoaccount==True:
lstTemp2.append(strprimarykey)
if isstrprimarykey2autoaccount==True:
lstTemp2.append(strprimarykey2)
#------------------------
lstTemp=_mty.delListSelYuanSu(lstTemp,lstTemp2)
strT=','.join(lstTemp)
#然後匯入 #注意myssql的自增加欄位不能賦值進去
&n