mdbq 2.9.4__py3-none-any.whl → 2.9.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mdbq/aggregation/aggregation.py +15 -12
- mdbq/aggregation/query_data.py +40 -17
- mdbq/config/products.py +1 -11
- mdbq/mysql/mysql.py +112 -167
- mdbq/mysql/recheck_mysql.py +1 -1
- mdbq/req_post/req_tb.py +1 -1
- {mdbq-2.9.4.dist-info → mdbq-2.9.6.dist-info}/METADATA +1 -1
- {mdbq-2.9.4.dist-info → mdbq-2.9.6.dist-info}/RECORD +10 -12
- mdbq/company/copysh_bak.py +0 -417
- mdbq/company/home_sh.py +0 -386
- {mdbq-2.9.4.dist-info → mdbq-2.9.6.dist-info}/WHEEL +0 -0
- {mdbq-2.9.4.dist-info → mdbq-2.9.6.dist-info}/top_level.txt +0 -0
mdbq/aggregation/aggregation.py
CHANGED
@@ -919,8 +919,8 @@ class DatabaseUpdateBak:
|
|
919
919
|
move_insert=True, # 先删除,再插入
|
920
920
|
df_sql=False, # 值为 True 时使用 df.to_sql 函数上传整个表, 不会排重
|
921
921
|
drop_duplicates=False, # 值为 True 时检查重复数据再插入,反之直接上传,会比较慢
|
922
|
+
count=None,
|
922
923
|
filename=rt_filename, # 用来追踪处理进度
|
923
|
-
service_database=service_database, # 字典
|
924
924
|
)
|
925
925
|
df_to_json.as_json_file() # 写入 json 文件, 包含数据的 dtypes 信息
|
926
926
|
|
@@ -1083,8 +1083,8 @@ class DatabaseUpdateBak:
|
|
1083
1083
|
move_insert=True, # 先删除,再插入
|
1084
1084
|
df_sql=False, # 值为 True 时使用 df.to_sql 函数上传整个表, 不会排重
|
1085
1085
|
drop_duplicates=False, # 值为 True 时检查重复数据再插入,反之直接上传,会比较慢
|
1086
|
+
count=None,
|
1086
1087
|
filename=None, # 用来追踪处理进度
|
1087
|
-
service_database=service_database, # 用来追踪处理进度
|
1088
1088
|
)
|
1089
1089
|
# return df
|
1090
1090
|
|
@@ -1116,8 +1116,8 @@ class DatabaseUpdateBak:
|
|
1116
1116
|
move_insert=False, # 先删除,再插入
|
1117
1117
|
df_sql=False, # 值为 True 时使用 df.to_sql 函数上传整个表, 不会排重
|
1118
1118
|
drop_duplicates=True, # 值为 True 时检查重复数据再插入,反之直接上传,会比较慢
|
1119
|
+
count=None,
|
1119
1120
|
filename=None, # 用来追踪处理进度
|
1120
|
-
service_database=service_database, # 用来追踪处理进度
|
1121
1121
|
)
|
1122
1122
|
|
1123
1123
|
|
@@ -1189,7 +1189,8 @@ def upload_dir(path, db_name, collection_name, json_path=None):
|
|
1189
1189
|
move_insert=False, # 先删除,再插入
|
1190
1190
|
df_sql = True,
|
1191
1191
|
drop_duplicates=False,
|
1192
|
-
|
1192
|
+
count=f'{i}/{count}',
|
1193
|
+
filename=name,
|
1193
1194
|
)
|
1194
1195
|
# nas.df_to_mysql(df=df, db_name=db_name, table_name=collection_name, drop_duplicates=True,)
|
1195
1196
|
except Exception as e:
|
@@ -1210,15 +1211,16 @@ def one_file_to_mysql(file, db_name, table_name):
|
|
1210
1211
|
df=df,
|
1211
1212
|
db_name=db_name,
|
1212
1213
|
table_name=table_name,
|
1213
|
-
filename=filename,
|
1214
1214
|
move_insert=False,
|
1215
1215
|
df_sql=True,
|
1216
1216
|
drop_duplicates=False,
|
1217
|
+
count=None,
|
1218
|
+
filename=filename,
|
1217
1219
|
)
|
1218
1220
|
|
1219
1221
|
|
1220
1222
|
def test():
|
1221
|
-
path = r'/Users/xigua
|
1223
|
+
path = r'/Users/xigua/数据中心/原始文件3/达摩盘/dmp人群报表'
|
1222
1224
|
|
1223
1225
|
results = []
|
1224
1226
|
for root, dirs, files in os.walk(path, topdown=False):
|
@@ -1231,8 +1233,10 @@ def test():
|
|
1231
1233
|
if len(df) == 0:
|
1232
1234
|
continue
|
1233
1235
|
if '达摩盘消耗占比' in df.columns.tolist():
|
1234
|
-
print(name)
|
1235
1236
|
df.pop('达摩盘消耗占比')
|
1237
|
+
if '更新时间' not in df.columns.tolist():
|
1238
|
+
print(name)
|
1239
|
+
df['更新时间'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
1236
1240
|
# df.insert(loc=1, column='店铺名称', value='万里马官方旗舰店')
|
1237
1241
|
# df['更新时间'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
1238
1242
|
df.to_csv(os.path.join(root, name), encoding='utf-8_sig', index=False, header=True)
|
@@ -1278,14 +1282,13 @@ if __name__ == '__main__':
|
|
1278
1282
|
# )
|
1279
1283
|
|
1280
1284
|
# test()
|
1281
|
-
|
1282
|
-
col = 1
|
1285
|
+
col = 0
|
1283
1286
|
if col:
|
1284
1287
|
# 上传一个目录到指定数据库
|
1285
|
-
db_name = '
|
1286
|
-
table_name = '
|
1288
|
+
db_name = '生意参谋3'
|
1289
|
+
table_name = '店铺流量来源构成'
|
1287
1290
|
upload_dir(
|
1288
|
-
path=r'/Users/xigua
|
1291
|
+
path=r'/Users/xigua/数据中心/原始文件3/生意参谋/店铺流量来源',
|
1289
1292
|
db_name=db_name,
|
1290
1293
|
collection_name=table_name,
|
1291
1294
|
)
|
mdbq/aggregation/query_data.py
CHANGED
@@ -2119,8 +2119,9 @@ def date_table():
|
|
2119
2119
|
move_insert=True, # 先删除,再插入
|
2120
2120
|
df_sql=False, # 值为 True 时使用 df.to_sql 函数上传整个表, 不会排重
|
2121
2121
|
drop_duplicates=False, # 值为 True 时检查重复数据再插入,反之直接上传,会比较慢
|
2122
|
+
count=None,
|
2122
2123
|
filename=None, # 用来追踪处理进度
|
2123
|
-
|
2124
|
+
set_typ={},
|
2124
2125
|
)
|
2125
2126
|
|
2126
2127
|
|
@@ -2284,7 +2285,9 @@ def data_aggregation(months=1, is_juhe=True, less_dict=[]):
|
|
2284
2285
|
# df_sql=True,
|
2285
2286
|
drop_duplicates=False,
|
2286
2287
|
icm_update=['商品id'],
|
2287
|
-
|
2288
|
+
count=None,
|
2289
|
+
filename=None,
|
2290
|
+
set_typ={},
|
2288
2291
|
)
|
2289
2292
|
g.sp_index_datas = pd.DataFrame() # 重置,不然下个循环会继续刷入数据库
|
2290
2293
|
# g.as_csv(df=df, filename=table_name + '.csv') # 导出 csv
|
@@ -2297,7 +2300,9 @@ def data_aggregation(months=1, is_juhe=True, less_dict=[]):
|
|
2297
2300
|
# df_sql=True,
|
2298
2301
|
# drop_duplicates=False,
|
2299
2302
|
# icm_update=unique_key_list,
|
2300
|
-
|
2303
|
+
count=None,
|
2304
|
+
filename=None,
|
2305
|
+
set_typ={},
|
2301
2306
|
) # 3. 回传数据库
|
2302
2307
|
else: # 没有日期列的就用主键排重
|
2303
2308
|
m.df_to_mysql(
|
@@ -2308,7 +2313,9 @@ def data_aggregation(months=1, is_juhe=True, less_dict=[]):
|
|
2308
2313
|
# df_sql=True,
|
2309
2314
|
drop_duplicates=False,
|
2310
2315
|
icm_update=unique_key_list,
|
2311
|
-
|
2316
|
+
count=None,
|
2317
|
+
filename=None,
|
2318
|
+
set_typ={},
|
2312
2319
|
) # 3. 回传数据库
|
2313
2320
|
if is_juhe:
|
2314
2321
|
res = g.performance(bb_tg=True) # 盈亏表,依赖其他表,单独做
|
@@ -2320,7 +2327,9 @@ def data_aggregation(months=1, is_juhe=True, less_dict=[]):
|
|
2320
2327
|
# df_sql=True,
|
2321
2328
|
# drop_duplicates=False,
|
2322
2329
|
# icm_update=['日期', '商品id'], # 设置唯一主键
|
2323
|
-
|
2330
|
+
count=None,
|
2331
|
+
filename=None,
|
2332
|
+
set_typ={},
|
2324
2333
|
)
|
2325
2334
|
res = g.performance(bb_tg=False) # 盈亏表,依赖其他表,单独做
|
2326
2335
|
m.df_to_mysql(
|
@@ -2331,7 +2340,9 @@ def data_aggregation(months=1, is_juhe=True, less_dict=[]):
|
|
2331
2340
|
# df_sql=True,
|
2332
2341
|
# drop_duplicates=False,
|
2333
2342
|
# icm_update=['日期', '商品id'], # 设置唯一主键
|
2334
|
-
|
2343
|
+
count=None,
|
2344
|
+
filename=None,
|
2345
|
+
set_typ={},
|
2335
2346
|
)
|
2336
2347
|
res = g.performance_concat(bb_tg=False) # 推广主体合并直播表,依赖其他表,单独做
|
2337
2348
|
m.df_to_mysql(
|
@@ -2342,7 +2353,9 @@ def data_aggregation(months=1, is_juhe=True, less_dict=[]):
|
|
2342
2353
|
# df_sql=True,
|
2343
2354
|
# drop_duplicates=False,
|
2344
2355
|
# icm_update=['日期', '推广渠道', '营销场景', '商品id', '花费', '展现量', '点击量'], # 设置唯一主键
|
2345
|
-
|
2356
|
+
count=None,
|
2357
|
+
filename=None,
|
2358
|
+
set_typ={},
|
2346
2359
|
)
|
2347
2360
|
res = g.performance_jd(jd_tg=False) # 盈亏表,依赖其他表,单独做
|
2348
2361
|
m.df_to_mysql(
|
@@ -2353,18 +2366,32 @@ def data_aggregation(months=1, is_juhe=True, less_dict=[]):
|
|
2353
2366
|
# df_sql=True,
|
2354
2367
|
# drop_duplicates=False,
|
2355
2368
|
# icm_update=['日期', '跟单sku_id', '货号', '花费'], # 设置唯一主键
|
2356
|
-
|
2369
|
+
count=None,
|
2370
|
+
filename=None,
|
2371
|
+
set_typ={},
|
2357
2372
|
)
|
2358
2373
|
|
2359
2374
|
|
2360
2375
|
def main(days=100, months=3):
|
2361
|
-
# 更新日期表
|
2376
|
+
# 1. 更新日期表 更新货品年份基准表, 属性设置 3 - 货品年份基准
|
2362
2377
|
date_table()
|
2363
|
-
# 更新货品年份基准表, 属性设置 3 - 货品年份基准
|
2364
2378
|
p = products.Products()
|
2365
2379
|
p.to_mysql()
|
2366
2380
|
|
2367
|
-
#
|
2381
|
+
# 2. 清理非聚合数据库
|
2382
|
+
system = platform.system() # 本机系统
|
2383
|
+
host_name = socket.gethostname() # 本机名
|
2384
|
+
conf = myconfig.main()
|
2385
|
+
db_list = conf[system][host_name]['mysql']['数据库集']
|
2386
|
+
not_juhe_db_list = [item for item in db_list if item != '聚合数据']
|
2387
|
+
optimize_data.op_data(
|
2388
|
+
db_name_lists=not_juhe_db_list,
|
2389
|
+
days=31, # 原始数据不需要设置清理太长
|
2390
|
+
is_mongo=False,
|
2391
|
+
is_mysql=True,
|
2392
|
+
)
|
2393
|
+
|
2394
|
+
# 3. 数据聚合
|
2368
2395
|
data_aggregation(
|
2369
2396
|
months=months,
|
2370
2397
|
is_juhe=True, # 生成聚合表
|
@@ -2372,13 +2399,9 @@ def main(days=100, months=3):
|
|
2372
2399
|
)
|
2373
2400
|
time.sleep(60)
|
2374
2401
|
|
2375
|
-
|
2376
|
-
host_name = socket.gethostname() # 本机名
|
2377
|
-
conf = myconfig.main()
|
2378
|
-
db_list = conf[system][host_name]['mysql']['数据库集']
|
2379
|
-
# 清理所有库
|
2402
|
+
# 4. 清理聚合数据
|
2380
2403
|
optimize_data.op_data(
|
2381
|
-
db_name_lists=
|
2404
|
+
db_name_lists=['聚合数据'],
|
2382
2405
|
days=days,
|
2383
2406
|
is_mongo=False,
|
2384
2407
|
is_mysql=True,
|
mdbq/config/products.py
CHANGED
@@ -141,22 +141,12 @@ class Products:
|
|
141
141
|
dict_data=dict_data,
|
142
142
|
# icm_update=['日期', '店铺名称', '宝贝id'], # 唯一组合键
|
143
143
|
unique_main_key=['商品id'],
|
144
|
-
|
144
|
+
set_typ={
|
145
145
|
'商品id': 'mediumtext',
|
146
146
|
'平台': 'mediumtext',
|
147
147
|
'上市年份': 'mediumtext',
|
148
148
|
},
|
149
149
|
)
|
150
|
-
# m.df_to_mysql(
|
151
|
-
# df=df,
|
152
|
-
# db_name='属性设置3',
|
153
|
-
# table_name='货品年份基准',
|
154
|
-
# move_insert = False,
|
155
|
-
# df_sql=False, # 值为 True 时使用 df.to_sql 函数上传整个表, 不会排重
|
156
|
-
# drop_duplicates=True, # 值为 True 时检查重复数据再插入,反之直接上传,会比较慢
|
157
|
-
# icm_update=[],
|
158
|
-
# service_database=service_database, # 用来追踪处理进度
|
159
|
-
# )
|
160
150
|
|
161
151
|
def market_date(self, product_id: int):
|
162
152
|
try:
|
mdbq/mysql/mysql.py
CHANGED
@@ -133,7 +133,7 @@ class MysqlUpload:
|
|
133
133
|
return wrapper
|
134
134
|
|
135
135
|
@try_except
|
136
|
-
def dict_to_mysql(self, db_name, table_name, dict_data, icm_update=None, main_key=None, unique_main_key=None, index_length=100,
|
136
|
+
def dict_to_mysql(self, db_name, table_name, dict_data, icm_update=None, main_key=None, unique_main_key=None, index_length=100, set_typ=None):
|
137
137
|
"""
|
138
138
|
插入字典数据
|
139
139
|
dict_data: 字典
|
@@ -141,7 +141,7 @@ class MysqlUpload:
|
|
141
141
|
unique_main_key: 指定唯一索引列
|
142
142
|
index_length: 索引长度
|
143
143
|
icm_update: 增量更正,指定后 main_key 只用于检查/创建列,不能更新数据
|
144
|
-
|
144
|
+
set_typ: {}
|
145
145
|
"""
|
146
146
|
if not main_key:
|
147
147
|
main_key = []
|
@@ -177,8 +177,11 @@ class MysqlUpload:
|
|
177
177
|
|
178
178
|
# 根据 dict_data 的值添加指定的数据类型
|
179
179
|
dtypes, dict_data = self.cover_dict_dtypes(dict_data=dict_data) # {'店铺名称': 'mediumtext',...}
|
180
|
-
if
|
181
|
-
|
180
|
+
if set_typ:
|
181
|
+
# 更新自定义的列数据类型
|
182
|
+
for k, v in dtypes.items():
|
183
|
+
# 确保传进来的 set_typ 键存在于实际的 df 列才 update
|
184
|
+
[dtypes.update({k: inside_v}) for inside_k, inside_v in set_typ.items() if k == inside_k]
|
182
185
|
|
183
186
|
# 检查列
|
184
187
|
sql = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s;"
|
@@ -306,6 +309,7 @@ class MysqlUpload:
|
|
306
309
|
connection.close()
|
307
310
|
|
308
311
|
def cover_dict_dtypes(self, dict_data):
|
312
|
+
""" 清理字典键值 并转换数据类型 """
|
309
313
|
if not dict_data:
|
310
314
|
print(f'mysql.py -> MysqlUpload -> cover_dict_dtypes -> 传入的字典不能为空')
|
311
315
|
return
|
@@ -339,7 +343,7 @@ class MysqlUpload:
|
|
339
343
|
elif str(v) == '':
|
340
344
|
__res_dict.update({k: 'mediumtext'})
|
341
345
|
elif result2: # 小数
|
342
|
-
__res_dict.update({k: 'decimal(
|
346
|
+
__res_dict.update({k: 'decimal(12,4)'})
|
343
347
|
elif date_type == 1: # 纯日期
|
344
348
|
__res_dict.update({k: 'DATE'})
|
345
349
|
elif date_type == 2: # 日期+时间
|
@@ -352,7 +356,7 @@ class MysqlUpload:
|
|
352
356
|
v = round(float(v), 4)
|
353
357
|
__res_dict.update({k: 'decimal(12,4)'})
|
354
358
|
elif count_float >= 6:
|
355
|
-
__res_dict.update({k: 'decimal(
|
359
|
+
__res_dict.update({k: 'decimal(14,6)'})
|
356
360
|
elif count_float >= 4:
|
357
361
|
__res_dict.update({k: 'decimal(10,4)'})
|
358
362
|
else:
|
@@ -362,12 +366,78 @@ class MysqlUpload:
|
|
362
366
|
new_dict_data.update({k: v})
|
363
367
|
return __res_dict, new_dict_data
|
364
368
|
|
369
|
+
def cover_df(self, df):
|
370
|
+
""" 清理 df 的值和列名 """
|
371
|
+
df.replace([np.inf, -np.inf], 0, inplace=True) # 清理一些非法值
|
372
|
+
df.replace(to_replace=['\\N', '-', '--', '', 'nan', 'NAN'], value=0, regex=False, inplace=True) # 替换掉特殊字符
|
373
|
+
df.replace(to_replace=[','], value='', regex=True, inplace=True)
|
374
|
+
df.replace(to_replace=['="'], value='', regex=True, inplace=True) # ="和"不可以放在一起清洗, 因为有: id=86785565
|
375
|
+
df.replace(to_replace=['"'], value='', regex=True, inplace=True)
|
376
|
+
cols = df.columns.tolist()
|
377
|
+
for col in cols:
|
378
|
+
df[col] = df[col].apply(lambda x: float(re.sub(r'%$', '', str(x))) / 100 if (
|
379
|
+
str(x) != '' and str(x).endswith('%')) else '0.0' if str(x) == '0%' else x)
|
380
|
+
try:
|
381
|
+
df[col] = df[col].apply(
|
382
|
+
lambda x: int(x) if '_' not in str(x) and '.' not in str(x) else x) # 不含小数点尝试转整数
|
383
|
+
except:
|
384
|
+
pass
|
385
|
+
if df[col].dtype == 'object':
|
386
|
+
try:
|
387
|
+
df[col] = df[col].apply(lambda x: float(x) if '.' in str(x) and '_' not in str(x) else x)
|
388
|
+
except:
|
389
|
+
pass
|
390
|
+
new_col = col.lower()
|
391
|
+
new_col = re.sub(r'[()\-,,&~^、 ()\"\'“”=·/。》《><!!`]', '_', new_col, re.IGNORECASE)
|
392
|
+
new_col = new_col.replace(')', '')
|
393
|
+
new_col = re.sub(r'_{2,}', '_', new_col)
|
394
|
+
new_col = re.sub(r'_+$', '', new_col)
|
395
|
+
df.rename(columns={col: new_col}, inplace=True)
|
396
|
+
df.fillna(0, inplace=True)
|
397
|
+
return df
|
398
|
+
|
399
|
+
def convert_df_dtypes(self, df: pd.DataFrame):
|
400
|
+
""" 清理 df 的值和列名,并转换数据类型 """
|
401
|
+
df = self.cover_df(df=df) # 清理 df 的值和列名
|
402
|
+
[pd.to_numeric(df[col], errors='ignore') for col in df.columns.tolist()]
|
403
|
+
dtypes = df.dtypes.to_dict()
|
404
|
+
__res_dict = {}
|
405
|
+
for k, v in dtypes.items():
|
406
|
+
result1 = re.findall(r'编码|_?id|货号|款号|文件大小', k, re.IGNORECASE)
|
407
|
+
result2 = re.findall(r'占比$|投产$|产出$|roi$|率$', k, re.IGNORECASE)
|
408
|
+
result3 = re.findall(r'同比$|环比$', k, re.IGNORECASE)
|
409
|
+
result4 = re.findall(r'花费$|消耗$|金额$', k, re.IGNORECASE)
|
410
|
+
|
411
|
+
if result1: # id/sku/spu商品信息
|
412
|
+
__res_dict.update({k: 'varchar(50)'})
|
413
|
+
elif result2: # 小数
|
414
|
+
__res_dict.update({k: 'decimal(10,4)'})
|
415
|
+
elif result3: # 小数
|
416
|
+
__res_dict.update({k: 'decimal(12,4)'})
|
417
|
+
elif result4: # 小数
|
418
|
+
__res_dict.update({k: 'decimal(12,2)'})
|
419
|
+
elif k == '日期':
|
420
|
+
__res_dict.update({k: 'DATE'})
|
421
|
+
elif k == '更新时间':
|
422
|
+
__res_dict.update({k: 'TIMESTAMP'})
|
423
|
+
elif v == 'int64':
|
424
|
+
__res_dict.update({k: 'int'})
|
425
|
+
elif v == 'float64':
|
426
|
+
__res_dict.update({k: 'decimal(10,4)'})
|
427
|
+
elif v == 'bool':
|
428
|
+
__res_dict.update({k: 'BOOLEAN'})
|
429
|
+
elif v == 'datetime64[ns]':
|
430
|
+
__res_dict.update({k: 'datetime'})
|
431
|
+
else:
|
432
|
+
__res_dict.update({k: 'varchar(255)'})
|
433
|
+
return __res_dict, df
|
434
|
+
|
365
435
|
@try_except
|
366
|
-
def df_to_mysql(self, df, table_name,
|
436
|
+
def df_to_mysql(self, df, db_name, table_name, set_typ=None, icm_update=[], move_insert=False, df_sql=False, drop_duplicates=False,
|
437
|
+
filename=None, count=None, reset_id=False):
|
367
438
|
"""
|
368
|
-
|
369
|
-
|
370
|
-
table_name: 集合/表名称
|
439
|
+
db_name: 数据库名
|
440
|
+
table_name: 表名
|
371
441
|
move_insert: 根据df 的日期,先移除数据库数据,再插入, df_sql, drop_duplicates, icm_update 都要设置为 False
|
372
442
|
原则上只限于聚合数据使用,原始数据插入时不要设置
|
373
443
|
|
@@ -376,8 +446,6 @@ class MysqlUpload:
|
|
376
446
|
icm_update: 增量更新, 在聚合数据中使用,原始文件不要使用,设置此参数时需将 drop_duplicates 改为 False
|
377
447
|
使用增量更新: 必须确保 icm_update 传进来的列必须是数据表中唯一主键,值不会发生变化,不会重复,否则可能产生错乱覆盖情况
|
378
448
|
filename: 用来追踪处理进度,传这个参数是方便定位产生错误的文件
|
379
|
-
service_database: 这个参数是用来设置更新哪台服务器的 types 信息到本地 json 文件
|
380
|
-
json_path: 这个参数同样也是是用来设置更新 json 文件
|
381
449
|
"""
|
382
450
|
self.filename = filename
|
383
451
|
if isinstance(df, pd.DataFrame):
|
@@ -391,8 +459,13 @@ class MysqlUpload:
|
|
391
459
|
print(f'{db_name} 不能为 None')
|
392
460
|
return
|
393
461
|
|
394
|
-
|
395
|
-
df =
|
462
|
+
# 清理 dataframe 非法值,并转换获取数据类型
|
463
|
+
dtypes, df = self.convert_df_dtypes(df)
|
464
|
+
if set_typ:
|
465
|
+
# 更新自定义的列数据类型
|
466
|
+
for k, v in dtypes.items():
|
467
|
+
# 确保传进来的 set_typ 键存在于实际的 df 列才 update
|
468
|
+
[dtypes.update({k: inside_v}) for inside_k, inside_v in set_typ.items() if k == inside_k]
|
396
469
|
|
397
470
|
connection = pymysql.connect(**self.config) # 连接数据库
|
398
471
|
with connection.cursor() as cursor:
|
@@ -422,15 +495,6 @@ class MysqlUpload:
|
|
422
495
|
cursor.execute(sql)
|
423
496
|
print(f'创建 mysql 表: {table_name}')
|
424
497
|
|
425
|
-
for service_name, database in service_database.items():
|
426
|
-
# 2. 列数据类型转换,将 df 数据类型转换为 mysql 的数据类型
|
427
|
-
dtypes, cl, db_n, tb_n = self.convert_dtypes(df=df, db_name=db_name, table_name=table_name, path=json_path, service_name=service_name)
|
428
|
-
for dy in dtypes.keys():
|
429
|
-
if '日期' == dy:
|
430
|
-
dtypes.update({'日期': 'DATE'})
|
431
|
-
if '更新时间' == dy:
|
432
|
-
dtypes.update({'更新时间': 'TIMESTAMP'})
|
433
|
-
|
434
498
|
# 有特殊字符不需转义
|
435
499
|
sql = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s;"
|
436
500
|
cursor.execute(sql, (db_name, table_name))
|
@@ -456,7 +520,8 @@ class MysqlUpload:
|
|
456
520
|
|
457
521
|
if df_sql:
|
458
522
|
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S ")
|
459
|
-
print(
|
523
|
+
print(
|
524
|
+
f'{now}正在更新: mysql ({self.host}:{self.port}) {db_name}/{table_name}, {count}, {self.filename}')
|
460
525
|
engine = create_engine(
|
461
526
|
f"mysql+pymysql://{self.username}:{self.password}@{self.host}:{self.port}/{db_name}") # 创建数据库引擎
|
462
527
|
df.to_sql(
|
@@ -466,45 +531,23 @@ class MysqlUpload:
|
|
466
531
|
index=False,
|
467
532
|
chunksize=1000
|
468
533
|
)
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
if cl and db_n and tb_n:
|
484
|
-
mysql_types.mysql_all_dtypes(db_name=db_name, table_name=table_name) # 更新一个表的 dtypes
|
485
|
-
elif cl and db_n:
|
486
|
-
mysql_types.mysql_all_dtypes(db_name=db_name) # 更新一个数据库的 dtypes
|
487
|
-
elif cl:
|
488
|
-
mysql_types.mysql_all_dtypes() # 更新所有数据库所有数据表的 dtypes 信息到本地 json
|
489
|
-
|
534
|
+
if reset_id:
|
535
|
+
# 6. 重置自增列
|
536
|
+
try:
|
537
|
+
cursor.execute(f"SHOW COLUMNS FROM {table_name} LIKE 'id'")
|
538
|
+
result = cursor.fetchone()
|
539
|
+
if result:
|
540
|
+
cursor.execute(f"ALTER TABLE {table_name} DROP COLUMN id;") # 删除 id 列
|
541
|
+
cursor.execute(
|
542
|
+
f"ALTER TABLE {table_name} ADD column id INT AUTO_INCREMENT PRIMARY KEY FIRST;")
|
543
|
+
cursor.execute(f"ALTER TABLE {table_name} AUTO_INCREMENT = 1") # 设置自增从 1 开始
|
544
|
+
except Exception as e:
|
545
|
+
print(f'{e}')
|
546
|
+
connection.rollback()
|
547
|
+
connection.commit() # 提交事务
|
490
548
|
connection.close()
|
491
549
|
return
|
492
550
|
|
493
|
-
# print(cl, db_n, tb_n)
|
494
|
-
# 返回这些结果的目的是等添加完列再写 json 文件才能读到 types 信息
|
495
|
-
# ⚠️ mysql_all_dtypes 函数默认只读取 xigua_lx 的数据库信息,不会读取其他系统
|
496
|
-
if cl and db_n and tb_n:
|
497
|
-
mysql_types.mysql_all_dtypes(db_name=db_name, table_name=table_name) # 更新一个表的 dtypes
|
498
|
-
elif cl and db_n:
|
499
|
-
mysql_types.mysql_all_dtypes(db_name=db_name) # 更新一个数据库的 dtypes
|
500
|
-
elif cl:
|
501
|
-
mysql_types.mysql_all_dtypes() # 更新所有数据库所有数据表的 dtypes 信息到本地 json
|
502
|
-
|
503
|
-
# 4. 更新插入数据
|
504
|
-
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S ")
|
505
|
-
for service_name, database in service_database.items():
|
506
|
-
print(f'{now}正在更新 mysql ({self.host}:{self.port}) {db_name}/{table_name}, {count}, {service_name}, {self.filename}')
|
507
|
-
|
508
551
|
# 5. 移除指定日期范围内的数据,原则上只限于聚合数据使用,原始数据插入时不要设置
|
509
552
|
if move_insert and '日期' in df.columns.tolist():
|
510
553
|
# 移除数据
|
@@ -546,7 +589,6 @@ class MysqlUpload:
|
|
546
589
|
# data 是传进来待处理的数据, 不是数据库数据
|
547
590
|
# data 示例: {'日期': Timestamp('2024-08-27 00:00:00'), '推广费余额': 33299, '品销宝余额': 2930.73, '短信剩余': 67471}
|
548
591
|
try:
|
549
|
-
|
550
592
|
condition = []
|
551
593
|
for k, v in data.items():
|
552
594
|
condition += [f'`{k}` = "{v}"']
|
@@ -571,7 +613,8 @@ class MysqlUpload:
|
|
571
613
|
cursor.execute(sql, (db_name, {table_name}))
|
572
614
|
columns = cursor.fetchall()
|
573
615
|
cols_exist = [col['COLUMN_NAME'] for col in columns] # 数据表的所有列, 返回 list
|
574
|
-
update_col = [item for item in cols_exist if
|
616
|
+
update_col = [item for item in cols_exist if
|
617
|
+
item not in icm_update and item != 'id'] # 除了主键外的其他列
|
575
618
|
|
576
619
|
# unique_keys 示例: `日期`, `余额`
|
577
620
|
unique_keys = ', '.join(f"`{item}`" for item in update_col) # 列名需要转义
|
@@ -607,7 +650,8 @@ class MysqlUpload:
|
|
607
650
|
if change_values: # change_values 有数据返回,表示值需要更新
|
608
651
|
if not_change_col:
|
609
652
|
not_change_values = [f'`{col}` = "{str(data[col])}"' for col in not_change_col]
|
610
|
-
not_change_values = ' AND '.join(
|
653
|
+
not_change_values = ' AND '.join(
|
654
|
+
not_change_values) # 示例: `短信剩余` = '888' AND `test1` = '93'
|
611
655
|
# print(change_values, not_change_values)
|
612
656
|
condition += f' AND {not_change_values}' # 重新构建完整的查询条件,将未发生变化的列加进查询条件
|
613
657
|
change_values = ', '.join(f"{item}" for item in change_values) # 注意这里 item 外面没有反引号
|
@@ -641,107 +685,6 @@ class MysqlUpload:
|
|
641
685
|
connection.commit() # 提交事务
|
642
686
|
connection.close()
|
643
687
|
|
644
|
-
def convert_dtypes(self, df, db_name, table_name, path=None, service_name=None):
|
645
|
-
"""
|
646
|
-
根据本地 json 转换 df 的类型为 mysql 专有的数据类型
|
647
|
-
可能不存在本地 json 文件 (函数按指定规则转换并更新 json)
|
648
|
-
允许通过 json 文件手动添加或修改列的数据类型(仅影响初创数据表)
|
649
|
-
"""
|
650
|
-
cols = df.columns.tolist()
|
651
|
-
# path = set_support.SetSupport(dirname='support').dirname
|
652
|
-
d = mysql_types.DataTypes(path=path, service_name=service_name)
|
653
|
-
# 从本地文件中读取 dtype 信息
|
654
|
-
dtypes, cl, db_n, tb_n = d.load_dtypes(cl='mysql', db_name=db_name, table_name=table_name)
|
655
|
-
# 可能会因为没有 json 文件, 返回 None
|
656
|
-
if dtypes:
|
657
|
-
# 按照文件记录更新 dtypes
|
658
|
-
dtypes.update({col: dtypes[col] for col in cols if col in dtypes.keys()})
|
659
|
-
# 可能存在部分列不在文件记录中
|
660
|
-
col_not_exist = [col for col in cols if col not in dtypes.keys()]
|
661
|
-
# 这些列不存在于 df 中, 必须移除
|
662
|
-
[dtypes.pop(col) for col in list(dtypes.keys()) if col not in cols]
|
663
|
-
else: # 没有 json 文件时
|
664
|
-
dtypes = df.dtypes.apply(str).to_dict() # 将 dataframe 数据类型转为字典形式
|
665
|
-
col_not_exist = cols
|
666
|
-
# 对文件不存在的列信息进行数据类型转换(按指定规则)
|
667
|
-
dtypes.update({col: self.convert_dtype_to_sql(df=df, col=col, dtype=df[col].dtype) for col in col_not_exist})
|
668
|
-
# print(dtypes)
|
669
|
-
# 至此 df 中全部列类型已经转换完成
|
670
|
-
# 返回结果, 示例: {'上市年份': 'mediumtext', '商品id': 'mediumtext', '平台': 'mediumtext'}
|
671
|
-
return dtypes, cl, db_n, tb_n # 返回这些结果的目的是等添加完列再写 json 文件才能读到 types 信息
|
672
|
-
|
673
|
-
def convert_dtype_to_sql(self, df, col, dtype):
|
674
|
-
""" 按照以下规则转换DataFrame列的数据类型为 MYSQL 专有的数据类型 """
|
675
|
-
|
676
|
-
def find_longest_decimal_value(number_list):
|
677
|
-
# 针对小数设置的函数, 用来获取列表中小数位数最长的值
|
678
|
-
longest_value = None
|
679
|
-
max_decimals = 0
|
680
|
-
for num in number_list:
|
681
|
-
try:
|
682
|
-
decimal_places = len(str(num).split('.')[1])
|
683
|
-
if decimal_places > max_decimals:
|
684
|
-
max_decimals = decimal_places
|
685
|
-
longest_value = num
|
686
|
-
except:
|
687
|
-
longest_value = num
|
688
|
-
continue
|
689
|
-
return longest_value
|
690
|
-
|
691
|
-
if '商品编码' in col: # 京东sku/spu商品信息
|
692
|
-
return 'mediumtext'
|
693
|
-
if '文件大小' in col: # bw 程序
|
694
|
-
return 'mediumtext'
|
695
|
-
if col.endswith('占比') and (df[col].dtype == 'float' or df[col].dtype == 'int'):
|
696
|
-
return 'decimal(10,4)'
|
697
|
-
elif dtype == 'datetime64[ns]':
|
698
|
-
return 'DATETIME' # 使用 DATE 后续排重可能会引发不能排重
|
699
|
-
elif dtype == 'int32':
|
700
|
-
max_num = str(max(df[col].tolist()))
|
701
|
-
if len(max_num) >= 10: # 数值长度超限转为 mediumtext
|
702
|
-
return 'mediumtext'
|
703
|
-
return 'INT'
|
704
|
-
elif dtype == 'int64':
|
705
|
-
max_num = str(max(df[col].tolist()))
|
706
|
-
if len(max_num) >= 10:
|
707
|
-
return 'mediumtext'
|
708
|
-
return 'INT'
|
709
|
-
elif dtype == 'float64':
|
710
|
-
res = find_longest_decimal_value(df[col].tolist()) # 取小数位数最长的值
|
711
|
-
if 'e' in str(res):
|
712
|
-
res = round(float(res), 4)
|
713
|
-
int_step = len(str(res).split('.')[0]) # 整数位数长度
|
714
|
-
f_step = len(str(res).split('.')[1]) # 小数位数长度
|
715
|
-
|
716
|
-
if int_step >= 12:
|
717
|
-
return 'mediumtext' # mysql 中不要使用 float 和 double 类型,会影响计算结果
|
718
|
-
elif int_step >= 8 and f_step >= 0:
|
719
|
-
return 'decimal(16, 2)'
|
720
|
-
elif int_step >= 6 and f_step >= 0:
|
721
|
-
return 'decimal(10, 2)'
|
722
|
-
elif int_step >= 4 and f_step >= 0:
|
723
|
-
return 'decimal(10, 2)'
|
724
|
-
elif int_step >= 2 and f_step >= 6:
|
725
|
-
return 'decimal(12, 4)'
|
726
|
-
elif int_step >= 2 and f_step > 4:
|
727
|
-
return 'decimal(12, 4)'
|
728
|
-
elif int_step >= 2 and f_step > 2:
|
729
|
-
return 'decimal(10, 4)'
|
730
|
-
elif int_step >= 2 and f_step >= 0:
|
731
|
-
return 'decimal(10, 2)'
|
732
|
-
elif int_step >= 1 and f_step >= 6:
|
733
|
-
return 'decimal(12, 4)'
|
734
|
-
elif int_step >= 1 and f_step > 4:
|
735
|
-
return 'decimal(12, 4)'
|
736
|
-
elif int_step >= 1 and f_step > 2:
|
737
|
-
return 'decimal(10, 4)'
|
738
|
-
else:
|
739
|
-
return 'decimal(10, 2)'
|
740
|
-
elif dtype == 'object':
|
741
|
-
return 'mediumtext'
|
742
|
-
else:
|
743
|
-
return 'mediumtext'
|
744
|
-
|
745
688
|
# @try_except
|
746
689
|
def read_mysql(self, table_name, start_date, end_date, db_name='远程数据源', date_name='日期'):
|
747
690
|
""" 读取指定数据表,可指定日期范围,返回结果: df """
|
@@ -1175,6 +1118,8 @@ if __name__ == '__main__':
|
|
1175
1118
|
username, password, host, port = data['username'], data['password'], data['host'], data['port']
|
1176
1119
|
print(username, password, host, port)
|
1177
1120
|
|
1178
|
-
|
1179
|
-
ss=
|
1180
|
-
|
1121
|
+
df = pd.read_excel('/Users/xigua/Downloads/66563857.xlsx')
|
1122
|
+
ss = MysqlUpload(username, password, host, port)
|
1123
|
+
res, data = ss.convert_df_dtypes(df=df)
|
1124
|
+
print(data)
|
1125
|
+
print(res)
|
mdbq/mysql/recheck_mysql.py
CHANGED
@@ -150,8 +150,8 @@ class ReCheckMysql:
|
|
150
150
|
move_insert=True, # 先删除,再插入
|
151
151
|
df_sql=False, # 值为 True 时使用 df.to_sql 函数上传整个表, 不会排重
|
152
152
|
drop_duplicates=False, # 值为 True 时检查重复数据再插入,反之直接上传,会比较慢
|
153
|
+
count=None,
|
153
154
|
filename='', # 用来追踪处理进度
|
154
|
-
service_database={'company': 'mysql'}, # 字典
|
155
155
|
)
|
156
156
|
|
157
157
|
|
mdbq/req_post/req_tb.py
CHANGED
@@ -611,8 +611,8 @@ def company_run(service_databases=[]):
|
|
611
611
|
move_insert=False, # 先删除,再插入
|
612
612
|
df_sql=False, # 值为 True 时使用 df.to_sql 函数上传整个表, 不会排重
|
613
613
|
drop_duplicates=False, # 值为 True 时检查重复数据再插入,反之直接上传,会比较慢
|
614
|
+
count=None,
|
614
615
|
filename=None, # 用来追踪处理进度
|
615
|
-
service_database=dt, # 字典
|
616
616
|
)
|
617
617
|
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S ")
|
618
618
|
print(f'{now} {db_name} -> {table_name}: 已入库')
|