提交
This commit is contained in:
1 parent
3c629d2d58
commit
34c33ebc54
12 files changed
+1206
No files matched your search
@@ -0,0 +1,63 @@
|
||||
# 教职工数据迁移脚本
|
||||
|
||||
## 概述
|
||||
|
||||
本脚本用于将 `人员清单列表1@正式员工.xlsx` 中的教职工数据导入到MySQL临时表中。
|
||||
|
||||
## Excel文件格式
|
||||
|
||||
Excel文件应包含以下列(第一行为标题栏):
|
||||
- 序号
|
||||
- 姓名
|
||||
- 性别
|
||||
- 职务
|
||||
- 部门
|
||||
- 是否在编
|
||||
- 手机号
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. 配置数据库连接
|
||||
|
||||
脚本会自动从 `db_config.py` 文件读取数据库配置。如果文件不存在,脚本会尝试从上级目录的 `学校历史数据迁移/db_config.py` 查找。
|
||||
|
||||
### 3. 运行脚本
|
||||
|
||||
```bash
|
||||
python import_staff_data.py
|
||||
```
|
||||
|
||||
## 输出
|
||||
|
||||
- 创建临时表 `temp_staff_data`
|
||||
- 导入所有教职工数据
|
||||
- 显示导入统计信息和数据预览
|
||||
|
||||
## 临时表结构
|
||||
|
||||
```sql
|
||||
CREATE TABLE `temp_staff_data` (
|
||||
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
`序号` TEXT DEFAULT NULL,
|
||||
`姓名` TEXT DEFAULT NULL,
|
||||
`性别` TEXT DEFAULT NULL,
|
||||
`职务` TEXT DEFAULT NULL,
|
||||
`部门` TEXT DEFAULT NULL,
|
||||
`是否在编` TEXT DEFAULT NULL,
|
||||
`手机号` TEXT DEFAULT NULL,
|
||||
`import_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 脚本会自动清理列名中的特殊字符
|
||||
- 空值会被转换为NULL
|
||||
- 所有数据列使用TEXT类型以确保兼容性
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
数据库配置文件
|
||||
用于超星数据移植与定时同步脚本
|
||||
"""
|
||||
|
||||
# 数据库配置信息
|
||||
DB_CONFIG = {
|
||||
"host": "47.99.164.66",
|
||||
"port": 3306,
|
||||
"database": "hzdyjs_xg",
|
||||
"user": "root",
|
||||
"password": "hn02le.34lkdLKD",
|
||||
"charset": "utf8mb4"
|
||||
}
|
||||
|
||||
# 表名映射(API类型 -> 数据库表名)
|
||||
TABLE_MAPPING = {
|
||||
"class": "his_bjsj", # 班级数据
|
||||
"department": "his_xbsj", # 系部数据
|
||||
"student": "his_xssj", # 学生数据
|
||||
"major": "his_zysj" , # 专业数据
|
||||
"teacher": "his_lssj" # 老师数据
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
将 `人员清单列表@外聘.xlsx` 导入到临时表 temp_external_staff
|
||||
|
||||
用法:
|
||||
python import_external_staff.py
|
||||
|
||||
依赖:
|
||||
pandas sqlalchemy pymysql openpyxl
|
||||
|
||||
说明:
|
||||
- 从同目录或上级目录读取 db_config.py 中的 DB_CONFIG
|
||||
- 自动根据 Excel 表头创建临时表(所有列使用 TEXT)
|
||||
- 支持批量写入
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import sys
|
||||
import math
|
||||
import argparse
|
||||
import importlib.util
|
||||
from typing import List, Dict, Any
|
||||
|
||||
import pandas as pd
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
try:
|
||||
import openpyxl
|
||||
except Exception:
|
||||
openpyxl = None
|
||||
|
||||
|
||||
def load_db_config_from_file(config_path: str) -> dict:
|
||||
spec = importlib.util.spec_from_file_location("db_config", config_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module) # type: ignore
|
||||
if not hasattr(module, "DB_CONFIG"):
|
||||
raise RuntimeError(f"No DB_CONFIG found in {config_path}")
|
||||
return getattr(module, "DB_CONFIG")
|
||||
|
||||
|
||||
def clean_column_name(col: str) -> str:
|
||||
if col is None:
|
||||
return ""
|
||||
s = str(col).strip()
|
||||
s = s.replace("(", "_").replace(")", "_").replace("(", "_").replace(")", "_")
|
||||
s = s.replace("、", "_").replace(" ", "_").replace("\n", "_").replace("\r", "_")
|
||||
# ensure valid identifier start
|
||||
if not s:
|
||||
s = "col"
|
||||
if not (s[0].isalpha() or s[0] == "_"):
|
||||
s = f"c_{s}"
|
||||
return s
|
||||
|
||||
|
||||
def build_engine_from_config(db_config: dict):
|
||||
user = db_config.get("user")
|
||||
password = db_config.get("password")
|
||||
host = db_config.get("host", "localhost")
|
||||
port = db_config.get("port", 3306)
|
||||
database = db_config.get("database")
|
||||
charset = db_config.get("charset", "utf8mb4")
|
||||
uri = f"mysql+pymysql://{user}:{password}@{host}:{port}/{database}?charset={charset}"
|
||||
return create_engine(uri, pool_pre_ping=True)
|
||||
|
||||
|
||||
def create_table(conn, table_name: str, columns: List[str]):
|
||||
# 先单独执行 DROP,再执行 CREATE(避免多语句执行限制导致语法错误)
|
||||
conn.execute(text(f"DROP TABLE IF EXISTS `{table_name}`"))
|
||||
lines: List[str] = []
|
||||
lines.append(f"CREATE TABLE `{table_name}` (")
|
||||
lines.append(" `id` BIGINT(20) NOT NULL AUTO_INCREMENT,")
|
||||
for col in columns:
|
||||
lines.append(f" `{col}` TEXT DEFAULT NULL,")
|
||||
lines.append(" `import_time` DATETIME DEFAULT CURRENT_TIMESTAMP,")
|
||||
lines.append(" PRIMARY KEY (`id`)")
|
||||
lines.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;")
|
||||
sql = "\n".join(lines)
|
||||
conn.execute(text(sql))
|
||||
|
||||
|
||||
def import_excel_to_table(engine, excel_path: str, table_name: str, chunk_size: int = 1000):
|
||||
if not os.path.exists(excel_path):
|
||||
raise FileNotFoundError(f"找不到文件: {excel_path}")
|
||||
# 读取表头和全部数据使用 pandas(适合中小文件)
|
||||
df = pd.read_excel(excel_path, engine="openpyxl")
|
||||
raw_cols = list(df.columns)
|
||||
clean_cols = [clean_column_name(c) for c in raw_cols]
|
||||
# 重命名 DataFrame 列为清理后的列名
|
||||
df.columns = clean_cols
|
||||
|
||||
with engine.begin() as conn:
|
||||
# 创建表
|
||||
create_table(conn, table_name, clean_cols)
|
||||
# 分块写入
|
||||
total = 0
|
||||
for start in range(0, len(df), chunk_size):
|
||||
chunk = df.iloc[start:start+chunk_size].where(pd.notnull(df), None)
|
||||
chunk.to_sql(name=table_name, con=conn, index=False, if_exists="append", method="multi", chunksize=chunk_size)
|
||||
total += len(chunk)
|
||||
return total
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="将人员清单(外聘)导入临时表")
|
||||
parser.add_argument("--file", type=str, default="人员清单列表@外聘.xlsx", help="Excel 文件名或路径")
|
||||
parser.add_argument("--table", type=str, default="temp_external_staff", help="临时表名")
|
||||
parser.add_argument("--db-config", type=str, default="../学校历史数据迁移/db_config.py", help="db_config.py 路径")
|
||||
args = parser.parse_args()
|
||||
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
excel_path = args.file
|
||||
if not os.path.isabs(excel_path):
|
||||
excel_path = os.path.join(base_dir, excel_path)
|
||||
|
||||
db_config_path = args.db_config
|
||||
if not os.path.isabs(db_config_path):
|
||||
db_config_path = os.path.join(base_dir, db_config_path)
|
||||
if not os.path.exists(db_config_path):
|
||||
# try parent folder
|
||||
db_config_path = os.path.join(os.path.dirname(base_dir), "学校历史数据迁移", "db_config.py")
|
||||
if not os.path.exists(db_config_path):
|
||||
print(f"找不到 db_config 文件: {db_config_path}")
|
||||
sys.exit(1)
|
||||
|
||||
db_config = load_db_config_from_file(db_config_path)
|
||||
engine = build_engine_from_config(db_config)
|
||||
|
||||
print(f"开始导入文件: {excel_path} 到表 {args.table}")
|
||||
total = import_excel_to_table(engine, excel_path, args.table, chunk_size=1000)
|
||||
print(f"导入完成,共插入 {total} 条记录到 {args.table}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
导入教职工数据到临时表
|
||||
|
||||
用法:
|
||||
python import_staff_data.py
|
||||
|
||||
依赖:
|
||||
pip install pandas sqlalchemy pymysql openpyxl xlrd
|
||||
|
||||
说明:
|
||||
- 从同目录下的 db_config.py 读取 DB_CONFIG
|
||||
- 将 `人员清单列表1@正式员工.xlsx` 导入到临时表 `temp_staff_data`
|
||||
- 自动创建临时表(所有列使用 TEXT 类型)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import sys
|
||||
import math
|
||||
import argparse
|
||||
import importlib.util
|
||||
from typing import List, Dict, Any
|
||||
|
||||
import pandas as pd
|
||||
from sqlalchemy import create_engine, types as sql_types, text
|
||||
|
||||
# Optional, used for xls/xlsx low-level reading
|
||||
try:
|
||||
import xlrd # for .xls
|
||||
except Exception:
|
||||
xlrd = None
|
||||
|
||||
try:
|
||||
import openpyxl # for .xlsx
|
||||
except Exception:
|
||||
openpyxl = None
|
||||
|
||||
|
||||
def load_db_config_from_file(config_path: str) -> dict:
|
||||
"""从配置文件加载数据库配置"""
|
||||
spec = importlib.util.spec_from_file_location("db_config", config_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module) # type: ignore
|
||||
if not hasattr(module, "DB_CONFIG"):
|
||||
raise RuntimeError(f"No DB_CONFIG found in {config_path}")
|
||||
return getattr(module, "DB_CONFIG")
|
||||
|
||||
|
||||
def sanitize_columns(header: List[str]) -> List[str]:
|
||||
"""清理和标准化列名"""
|
||||
seen: Dict[str, int] = {}
|
||||
out: List[str] = []
|
||||
for i, col in enumerate(header):
|
||||
if col is None:
|
||||
name = f"col_{i+1}"
|
||||
else:
|
||||
name = str(col).strip()
|
||||
if name == "":
|
||||
name = f"col_{i+1}"
|
||||
# replace problematic characters
|
||||
name = name.replace(" ", "_").replace("\n", "_").replace("\r", "_").replace("(", "_").replace(")", "_")
|
||||
# ensure starts with letter or underscore
|
||||
if not name[0].isalpha() and name[0] != "_":
|
||||
name = f"c_{name}"
|
||||
base = name
|
||||
cnt = seen.get(base, 0)
|
||||
if cnt:
|
||||
name = f"{base}_{cnt}"
|
||||
seen[base] = cnt + 1
|
||||
out.append(name)
|
||||
return out
|
||||
|
||||
|
||||
def create_temp_table(engine, table_name: str, columns: List[str]):
|
||||
"""创建临时表"""
|
||||
print(f"创建临时表: {table_name}")
|
||||
|
||||
# 构建建表SQL
|
||||
lines: List[str] = []
|
||||
lines.append(f"DROP TABLE IF EXISTS `{table_name}`;")
|
||||
lines.append(f"CREATE TABLE `{table_name}` (")
|
||||
lines.append(" `id` BIGINT(20) NOT NULL AUTO_INCREMENT,")
|
||||
|
||||
# 添加Excel列(TEXT类型)
|
||||
for col in columns:
|
||||
lines.append(f" `{col}` TEXT DEFAULT NULL,")
|
||||
|
||||
lines.append(" `import_time` DATETIME DEFAULT CURRENT_TIMESTAMP,")
|
||||
lines.append(" PRIMARY KEY (`id`)")
|
||||
lines.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;")
|
||||
|
||||
create_sql = "\n".join(lines)
|
||||
|
||||
try:
|
||||
with engine.begin() as conn:
|
||||
conn.execute(text(create_sql))
|
||||
print(f"临时表 {table_name} 创建成功")
|
||||
except Exception as e:
|
||||
print(f"创建临时表失败: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def df_to_mysql_append(engine, df: pd.DataFrame, table_name: str, first_write: bool):
|
||||
"""将DataFrame追加到MySQL表"""
|
||||
# ensure all columns are strings (TEXT)
|
||||
df = df.where(pd.notnull(df), None)
|
||||
if first_write:
|
||||
# 追加数据,不创建表(表已预先创建)
|
||||
df.to_sql(name=table_name, con=engine, index=False, if_exists="append", chunksize=1000, method="multi")
|
||||
else:
|
||||
df.to_sql(name=table_name, con=engine, index=False, if_exists="append", chunksize=1000, method="multi")
|
||||
|
||||
|
||||
def process_xlsx_staff(filepath: str, engine, table_name: str, chunk_size: int = 2000):
|
||||
"""处理教职工Excel文件(.xlsx格式)"""
|
||||
if openpyxl is None:
|
||||
raise RuntimeError("Missing openpyxl library to read .xlsx files. Install from requirements.")
|
||||
|
||||
print(f"开始读取教职工数据文件: {filepath}")
|
||||
wb = openpyxl.load_workbook(filename=filepath, read_only=True, data_only=True)
|
||||
sheet = wb.active
|
||||
rows = sheet.iter_rows(values_only=True)
|
||||
|
||||
try:
|
||||
header_row = next(rows)
|
||||
except StopIteration:
|
||||
print(f"{filepath} 是空文件")
|
||||
return
|
||||
|
||||
# 处理表头
|
||||
header = [str(c).strip() if c is not None else "" for c in header_row]
|
||||
header = sanitize_columns(header)
|
||||
print(f"表头列: {header}")
|
||||
|
||||
# 创建临时表
|
||||
create_temp_table(engine, table_name, header)
|
||||
|
||||
# 处理数据行
|
||||
batch: List[Dict[str, Any]] = []
|
||||
first_write = True
|
||||
written = 0
|
||||
|
||||
for row in rows:
|
||||
# 转换NaN值为None
|
||||
row = [None if (isinstance(x, float) and math.isnan(x)) else x for x in row]
|
||||
row_dict = {col: row[idx] if idx < len(row) else None for idx, col in enumerate(header)}
|
||||
batch.append(row_dict)
|
||||
|
||||
if len(batch) >= chunk_size:
|
||||
df = pd.DataFrame(batch)
|
||||
df_to_mysql_append(engine, df, table_name, first_write)
|
||||
first_write = False
|
||||
written += len(batch)
|
||||
batch = []
|
||||
print(f"已写入 {written} 行到 {table_name}")
|
||||
|
||||
if batch:
|
||||
df = pd.DataFrame(batch)
|
||||
df_to_mysql_append(engine, df, table_name, first_write)
|
||||
written += len(batch)
|
||||
print(f"写入剩余 {len(batch)} 行到 {table_name},总计写入 {written} 行")
|
||||
|
||||
|
||||
def process_file_staff(filepath: str, engine, table_name: str, chunk_size: int = 2000):
|
||||
"""处理教职工数据文件"""
|
||||
ext = os.path.splitext(filepath)[1].lower()
|
||||
if ext in (".xlsx",):
|
||||
process_xlsx_staff(filepath, engine, table_name, chunk_size=chunk_size)
|
||||
else:
|
||||
# 尝试使用pandas fallback
|
||||
print(f"未知扩展名 {ext},尝试使用 pandas 直接读取(可能占用大量内存)")
|
||||
df = pd.read_excel(filepath, engine="openpyxl" if ext == ".xlsx" else None)
|
||||
df.columns = sanitize_columns(list(df.columns))
|
||||
|
||||
# 创建临时表
|
||||
create_temp_table(engine, table_name, list(df.columns))
|
||||
|
||||
# 导入数据
|
||||
df_to_mysql_append(engine, df, table_name, first_write=True)
|
||||
|
||||
|
||||
def build_engine_from_config(db_config: dict):
|
||||
"""根据配置构建数据库引擎"""
|
||||
user = db_config.get("user")
|
||||
password = db_config.get("password")
|
||||
host = db_config.get("host", "localhost")
|
||||
port = db_config.get("port", 3306)
|
||||
database = db_config.get("database")
|
||||
charset = db_config.get("charset", "utf8mb4")
|
||||
uri = f"mysql+pymysql://{user}:{password}@{host}:{port}/{database}?charset={charset}"
|
||||
engine = create_engine(uri, pool_pre_ping=True)
|
||||
return engine
|
||||
|
||||
|
||||
def main(args=None):
|
||||
"""主函数"""
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# 查找数据库配置文件
|
||||
config_path = os.path.join(base_dir, "db_config.py")
|
||||
if not os.path.exists(config_path):
|
||||
# 尝试从上级目录查找
|
||||
config_path = os.path.join(os.path.dirname(base_dir), "学校历史数据迁移", "db_config.py")
|
||||
if not os.path.exists(config_path):
|
||||
print(f"找不到配置文件: {config_path}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"使用配置文件: {config_path}")
|
||||
db_config = load_db_config_from_file(config_path)
|
||||
engine = build_engine_from_config(db_config)
|
||||
|
||||
# 教职工数据文件路径
|
||||
staff_file = os.path.join(base_dir, "人员清单列表1@正式员工.xlsx")
|
||||
if args and args.file:
|
||||
staff_file = args.file
|
||||
|
||||
table_name = "temp_staff_data"
|
||||
if args and args.table:
|
||||
table_name = args.table
|
||||
|
||||
if not os.path.exists(staff_file):
|
||||
print(f"找不到教职工数据文件: {staff_file}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"开始导入教职工数据到临时表 {table_name} ...")
|
||||
try:
|
||||
process_file_staff(staff_file, engine, table_name, chunk_size=1000)
|
||||
print(f"教职工数据导入完成 -> {table_name}")
|
||||
|
||||
# 验证导入结果
|
||||
with engine.begin() as conn:
|
||||
result = conn.execute(text(f"SELECT COUNT(*) as count FROM {table_name}"))
|
||||
count = result.fetchone()[0]
|
||||
print(f"临时表 {table_name} 共导入 {count} 条记录")
|
||||
|
||||
# 显示前几行数据
|
||||
result = conn.execute(text(f"SELECT * FROM {table_name} LIMIT 5"))
|
||||
columns = result.keys()
|
||||
print(f"\n前5行数据预览:")
|
||||
print("-" * 80)
|
||||
print(f"{' | '.join(columns)}")
|
||||
print("-" * 80)
|
||||
for row in result:
|
||||
print(f"{' | '.join(str(cell) if cell is not None else '' for cell in row)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"导入过程中发生错误: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
print("\n导入完成!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="导入教职工数据到临时表")
|
||||
parser.add_argument("--file", type=str, help="教职工数据文件路径")
|
||||
parser.add_argument("--table", type=str, default="temp_staff_data", help="临时表名")
|
||||
args = parser.parse_args()
|
||||
|
||||
main(args)</contents>
|
||||
</xai:function_call name="todo_write">
|
||||
<parameter name="merge">true
|
||||
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
将 temp_external_staff 的外聘职工数据插入到 sys_user,并为每个新用户分配角色
|
||||
|
||||
规则:
|
||||
- sys_user.user_name 使用 temp_external_staff.`手机号`
|
||||
- sys_user.user_no 使用 temp_external_staff.`序号`,格式为 'W' + zero_pad(序号,5)(例如序号=1 -> W00001;序号=209 -> W00209)
|
||||
- sys_user.nick_name 使用 temp_external_staff.`姓名`
|
||||
- 性别映射:'男' -> '0', '女' -> '1',其他或空 -> NULL 或默认 '0'(脚本中采用 '0')
|
||||
- 为每个插入的用户在 sys_user_role 中插入两个角色:4 和 1938120323613728770
|
||||
|
||||
注意:脚本默认不执行(dry-run),只有当去掉 --dry-run 参数并确认后才会写库。
|
||||
|
||||
用法示例(不写库):
|
||||
python insert_external_staff_to_sys_user.py --dry-run
|
||||
|
||||
用法示例(写库 — 请先确认并备份):
|
||||
python insert_external_staff_to_sys_user.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import importlib.util
|
||||
import datetime
|
||||
import re
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import pandas as pd
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
|
||||
def load_db_config_from_file(config_path: str) -> dict:
|
||||
spec = importlib.util.spec_from_file_location("db_config", config_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module) # type: ignore
|
||||
if not hasattr(module, "DB_CONFIG"):
|
||||
raise RuntimeError(f"No DB_CONFIG found in {config_path}")
|
||||
return getattr(module, "DB_CONFIG")
|
||||
|
||||
|
||||
def build_engine_from_config(db_config: dict):
|
||||
user = db_config.get("user")
|
||||
password = db_config.get("password")
|
||||
host = db_config.get("host", "localhost")
|
||||
port = db_config.get("port", 3306)
|
||||
database = db_config.get("database")
|
||||
charset = db_config.get("charset", "utf8mb4")
|
||||
uri = f"mysql+pymysql://{user}:{password}@{host}:{port}/{database}?charset={charset}"
|
||||
return create_engine(uri, pool_pre_ping=True)
|
||||
|
||||
|
||||
def normalize_phone(raw: Optional[str]) -> Optional[str]:
|
||||
if raw is None:
|
||||
return None
|
||||
s = str(raw)
|
||||
digits = "".join(re.findall(r"\d", s))
|
||||
return digits[:11] if digits else None
|
||||
|
||||
|
||||
def map_sex(raw: Optional[str]) -> Optional[str]:
|
||||
if raw is None:
|
||||
return None
|
||||
s = str(raw).strip()
|
||||
if s in ("男", "M", "m", "male", "Male"):
|
||||
return "0"
|
||||
if s in ("女", "F", "f", "female", "Female"):
|
||||
return "1"
|
||||
return None
|
||||
|
||||
|
||||
def zero_pad_seq_w_prefix(seq_value: Optional[str]) -> str:
|
||||
"""将序号转换为 'W' + 5位零填充字符串;若无法解析为整数,则尝试取数字子串或返回 'W00000'"""
|
||||
if seq_value is None:
|
||||
return "W00000"
|
||||
s = str(seq_value).strip()
|
||||
# 提取整数部分
|
||||
m = re.search(r"\d+", s)
|
||||
if not m:
|
||||
return "W00000"
|
||||
num = int(m.group(0))
|
||||
return "W" + str(num).zfill(5)
|
||||
|
||||
|
||||
def generate_user_id(conn) -> int:
|
||||
r = conn.execute(text("SELECT MAX(user_id) as m FROM sys_user")).fetchone()
|
||||
max_id = int(r[0]) if r and r[0] is not None else 0
|
||||
return max_id + 1
|
||||
|
||||
|
||||
def user_exists(conn, user_name: str) -> bool:
|
||||
r = conn.execute(text("SELECT 1 FROM sys_user WHERE user_name = :u LIMIT 1"), {"u": user_name}).fetchone()
|
||||
return bool(r)
|
||||
|
||||
|
||||
def user_has_role(conn, user_id: int, role_id: int) -> bool:
|
||||
r = conn.execute(text("SELECT 1 FROM sys_user_role WHERE user_id = :uid AND role_id = :rid LIMIT 1"), {"uid": user_id, "rid": role_id}).fetchone()
|
||||
return bool(r)
|
||||
|
||||
|
||||
def insert_user(conn, data: Dict[str, Any]):
|
||||
cols = ", ".join(f"`{k}`" for k in data.keys())
|
||||
vals = ", ".join(f":{k}" for k in data.keys())
|
||||
ins = text(f"INSERT INTO sys_user ({cols}) VALUES ({vals})")
|
||||
conn.execute(ins, data)
|
||||
|
||||
|
||||
def insert_user_role(conn, user_id: int, role_id: int):
|
||||
ins = text("INSERT INTO sys_user_role (`user_id`, `role_id`) VALUES (:uid, :rid)")
|
||||
conn.execute(ins, {"uid": user_id, "rid": role_id})
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="将 temp_external_staff 的数据插入 sys_user")
|
||||
parser.add_argument("--db-config", type=str, default="../学校历史数据迁移/db_config.py", help="db_config.py 路径")
|
||||
parser.add_argument("--table", type=str, default="temp_external_staff", help="源临时表名")
|
||||
parser.add_argument("--dry-run", action="store_true", help="仅打印将要执行的操作,不写入数据库")
|
||||
args = parser.parse_args()
|
||||
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
db_config_path = args.db_config if os.path.isabs(args.db_config) else os.path.join(base_dir, args.db_config)
|
||||
if not os.path.exists(db_config_path):
|
||||
# 尝试上级目录中的学校历史数据迁移配置
|
||||
alt = os.path.join(os.path.dirname(base_dir), "学校历史数据迁移", "db_config.py")
|
||||
if os.path.exists(alt):
|
||||
db_config_path = alt
|
||||
else:
|
||||
print(f"找不到 db_config 文件: {db_config_path}")
|
||||
sys.exit(1)
|
||||
|
||||
db_config = load_db_config_from_file(db_config_path)
|
||||
engine = build_engine_from_config(db_config)
|
||||
|
||||
# 读取源数据
|
||||
with engine.connect() as conn:
|
||||
df = pd.read_sql(text(f"SELECT * FROM `{args.table}`"), conn)
|
||||
|
||||
# 识别列名
|
||||
possible_seq_cols = ["序号", "序号_1", "col_1"]
|
||||
possible_name_cols = ["姓名", "name"]
|
||||
possible_phone_cols = ["手机号", "手机号_常用的一个_", "phone"]
|
||||
possible_sex_cols = ["性别", "sex"]
|
||||
possible_dept_cols = ["部门", "dept", "department"]
|
||||
|
||||
cols = list(df.columns)
|
||||
def pick(names):
|
||||
for n in names:
|
||||
if n in cols:
|
||||
return n
|
||||
return None
|
||||
|
||||
seq_col = pick(possible_seq_cols) or cols[1] if len(cols) > 1 else None
|
||||
name_col = pick(possible_name_cols) or cols[2] if len(cols) > 2 else None
|
||||
phone_col = pick(possible_phone_cols) or cols[6] if len(cols) > 6 else None
|
||||
sex_col = pick(possible_sex_cols) or cols[3] if len(cols) > 3 else None
|
||||
dept_col = pick(possible_dept_cols) or cols[5] if len(cols) > 5 else None
|
||||
|
||||
print(f"使用列映射: 序号={seq_col}, 姓名={name_col}, 性别={sex_col}, 手机号={phone_col}")
|
||||
|
||||
inserted = 0
|
||||
skipped_exists = 0
|
||||
errors = 0
|
||||
role_ids = [4, 1938120323613728770]
|
||||
password_hash = "$2a$10$FGldU5BGGq9ONuBmYhxLweFhmbkE45keMKASNZpjETXawE1oGbtZy"
|
||||
now_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# iterate and insert
|
||||
with engine.begin() as conn:
|
||||
for idx, row in df.iterrows():
|
||||
seq_val = row.get(seq_col) if seq_col in row else None
|
||||
name_val = row.get(name_col) if name_col in row else None
|
||||
raw_phone = row.get(phone_col) if phone_col in row else None
|
||||
dept_raw = row.get(dept_col) if dept_col in row else None
|
||||
sex_raw = row.get(sex_col) if sex_col in row else None
|
||||
|
||||
phone = normalize_phone(raw_phone)
|
||||
if not phone:
|
||||
print(f"跳过第{idx+1}行:手机号无效或为空(原值: {raw_phone})")
|
||||
errors += 1
|
||||
continue
|
||||
|
||||
user_name_val = phone
|
||||
if user_exists(conn, user_name_val):
|
||||
skipped_exists += 1
|
||||
continue
|
||||
|
||||
user_no_val = zero_pad_seq_w_prefix(seq_val)
|
||||
nick = str(name_val).strip() if name_val is not None else user_name_val
|
||||
sex_val = map_sex(sex_raw) or "0"
|
||||
|
||||
# resolve dept_id from 部门名称
|
||||
dept_id = None
|
||||
if dept_raw is not None and str(dept_raw).strip() != "":
|
||||
try:
|
||||
r = conn.execute(text("SELECT dept_id FROM sys_dept WHERE dept_name = :name LIMIT 1"), {"name": str(dept_raw).strip()}).fetchone()
|
||||
dept_id = int(r[0]) if r and r[0] is not None else None
|
||||
if dept_id is None:
|
||||
print(f"警告:第{idx+1}行,部门 '{dept_raw}' 在 sys_dept 中未找到,dept_id 将设为 NULL。")
|
||||
except Exception as e:
|
||||
print(f"查找部门时出错(行 {idx+1}): {e}")
|
||||
dept_id = None
|
||||
|
||||
new_user_id = generate_user_id(conn)
|
||||
user_record = {
|
||||
"user_id": new_user_id,
|
||||
"tenant_id": "000000",
|
||||
"dept_id": dept_id,
|
||||
"user_name": user_name_val,
|
||||
"nick_name": nick,
|
||||
"user_type": "sys_user",
|
||||
"email": None,
|
||||
"phonenumber": phone,
|
||||
"sex": sex_val,
|
||||
"avatar": None,
|
||||
"password": password_hash,
|
||||
"status": "0",
|
||||
"del_flag": "0",
|
||||
"login_ip": "",
|
||||
"login_date": None,
|
||||
"create_dept": 103,
|
||||
"create_by": 1,
|
||||
"create_time": now_str,
|
||||
"update_by": 1,
|
||||
"update_time": now_str,
|
||||
"remark": None,
|
||||
"account_type": "1",
|
||||
"user_no": user_no_val,
|
||||
}
|
||||
|
||||
print(f"准备插入 user_id={new_user_id}, user_name={user_name_val}, user_no={user_no_val}, nick={nick}")
|
||||
if args.dry_run:
|
||||
inserted += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
insert_user(conn, user_record)
|
||||
for rid in role_ids:
|
||||
if not user_has_role(conn, new_user_id, rid):
|
||||
insert_user_role(conn, new_user_id, rid)
|
||||
inserted += 1
|
||||
except Exception as e:
|
||||
print(f"插入失败(行 {idx+1}): {e}")
|
||||
errors += 1
|
||||
|
||||
print(f"完成:插入 {inserted} 条,已存在跳过 {skipped_exists} 条,错误 {errors} 条(dry-run={args.dry_run}))")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
pandas>=2.2.0
|
||||
sqlalchemy>=2.0
|
||||
pymysql>=1.0.3
|
||||
xlrd>=2.0.1
|
||||
openpyxl>=3.0.0
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
简单的教职工数据导入脚本
|
||||
"""
|
||||
|
||||
import os
|
||||
import pandas as pd
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
# 数据库配置
|
||||
DB_CONFIG = {
|
||||
"host": "47.99.164.66",
|
||||
"port": 3306,
|
||||
"database": "hzdyjs_xg",
|
||||
"user": "root",
|
||||
"password": "hn02le.34lkdLKD",
|
||||
"charset": "utf8mb4"
|
||||
}
|
||||
|
||||
def main():
|
||||
# 构建数据库连接
|
||||
user = DB_CONFIG.get("user")
|
||||
password = DB_CONFIG.get("password")
|
||||
host = DB_CONFIG.get("host", "localhost")
|
||||
port = DB_CONFIG.get("port", 3306)
|
||||
database = DB_CONFIG.get("database")
|
||||
charset = DB_CONFIG.get("charset", "utf8mb4")
|
||||
uri = f"mysql+pymysql://{user}:{password}@{host}:{port}/{database}?charset={charset}"
|
||||
engine = create_engine(uri, pool_pre_ping=True)
|
||||
|
||||
# Excel文件路径
|
||||
excel_file = "人员清单列表1@正式员工.xlsx"
|
||||
table_name = "temp_staff_data"
|
||||
|
||||
if not os.path.exists(excel_file):
|
||||
print(f"找不到文件: {excel_file}")
|
||||
return
|
||||
|
||||
print(f"读取Excel文件: {excel_file}")
|
||||
|
||||
# 读取Excel文件
|
||||
df = pd.read_excel(excel_file, engine='openpyxl')
|
||||
print(f"读取到 {len(df)} 行数据")
|
||||
print(f"列名: {list(df.columns)}")
|
||||
|
||||
# 清理列名
|
||||
df.columns = [col.replace('(', '_').replace(')', '_').replace('、', '_').replace(' ', '_') for col in df.columns]
|
||||
print(f"清理后的列名: {list(df.columns)}")
|
||||
|
||||
# 创建临时表
|
||||
print(f"创建临时表: {table_name}")
|
||||
|
||||
# 先删除已存在的表
|
||||
with engine.begin() as conn:
|
||||
conn.execute(text(f"DROP TABLE IF EXISTS `{table_name}`"))
|
||||
|
||||
# 根据实际列名创建表结构
|
||||
create_sql = f"CREATE TABLE `{table_name}` (\n"
|
||||
create_sql += " `id` BIGINT(20) NOT NULL AUTO_INCREMENT,\n"
|
||||
|
||||
for col in df.columns:
|
||||
# 清理列名,移除特殊字符
|
||||
clean_col = col.replace('(', '_').replace(')', '_').replace('、', '_').replace(' ', '_')
|
||||
create_sql += f" `{clean_col}` TEXT DEFAULT NULL,\n"
|
||||
|
||||
create_sql += " `import_time` DATETIME DEFAULT CURRENT_TIMESTAMP,\n"
|
||||
create_sql += " PRIMARY KEY (`id`)\n"
|
||||
create_sql += ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"
|
||||
|
||||
print("创建表SQL:")
|
||||
print(create_sql)
|
||||
|
||||
with engine.begin() as conn:
|
||||
conn.execute(text(create_sql))
|
||||
|
||||
# 导入数据
|
||||
print("导入数据...")
|
||||
df.to_sql(name=table_name, con=engine, index=False, if_exists="append", chunksize=1000, method="multi")
|
||||
|
||||
# 验证结果
|
||||
with engine.begin() as conn:
|
||||
result = conn.execute(text(f"SELECT COUNT(*) as count FROM {table_name}"))
|
||||
count = result.fetchone()[0]
|
||||
print(f"成功导入 {count} 条记录")
|
||||
|
||||
# 显示前5行
|
||||
result = conn.execute(text(f"SELECT * FROM {table_name} LIMIT 5"))
|
||||
columns = result.keys()
|
||||
print(f"\n前5行数据预览:")
|
||||
print("-" * 80)
|
||||
print(f"{' | '.join(columns)}")
|
||||
print("-" * 80)
|
||||
for row in result:
|
||||
print(f"{' | '.join(str(cell) if cell is not None else '' for cell in row)}")
|
||||
|
||||
print("\n导入完成!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
从 temp_staff_data 读取数据并更新 sys_user 表
|
||||
|
||||
逻辑:
|
||||
1. 对于 temp_staff_data 中每一行,使用 nickname = 姓名 AND account_type = '1' 在 sys_user 中查找唯一记录
|
||||
2. 若找到唯一记录:
|
||||
- 计算 user_no = zero_pad( sys_user.user_name, 6 ),并写回到 sys_user.user_no
|
||||
- 将 sys_user.user_name 和 sys_user.phonenumber 都更新为 temp_staff_data 中的手机号字段值
|
||||
3. 若未找到或找到多条记录,跳过并记录日志
|
||||
|
||||
用法:
|
||||
python update_sys_user_from_temp.py --db-config ../学校历史数据迁移/db_config.py --dry-run
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import importlib.util
|
||||
from typing import Optional, Sequence
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
LOG = logging.getLogger("update_sys_user")
|
||||
LOG.setLevel(logging.INFO)
|
||||
ch = logging.StreamHandler()
|
||||
ch.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
|
||||
LOG.addHandler(ch)
|
||||
|
||||
|
||||
def load_db_config_from_file(config_path: str) -> dict:
|
||||
spec = importlib.util.spec_from_file_location("db_config", config_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module) # type: ignore
|
||||
if not hasattr(module, "DB_CONFIG"):
|
||||
raise RuntimeError(f"No DB_CONFIG found in {config_path}")
|
||||
return getattr(module, "DB_CONFIG")
|
||||
|
||||
|
||||
def build_engine_from_config(db_config: dict):
|
||||
user = db_config.get("user")
|
||||
password = db_config.get("password")
|
||||
host = db_config.get("host", "localhost")
|
||||
port = db_config.get("port", 3306)
|
||||
database = db_config.get("database")
|
||||
charset = db_config.get("charset", "utf8mb4")
|
||||
uri = f"mysql+pymysql://{user}:{password}@{host}:{port}/{database}?charset={charset}"
|
||||
return create_engine(uri, pool_pre_ping=True)
|
||||
|
||||
|
||||
def pick_column_name(possible_names: Sequence[str], available: Sequence[str]) -> Optional[str]:
|
||||
"""从可能的列名集合中挑选 temp_staff_data 中存在的一项"""
|
||||
for name in possible_names:
|
||||
if name in available:
|
||||
return name
|
||||
return None
|
||||
|
||||
|
||||
def zero_pad_to_six(value) -> str:
|
||||
"""将给定值转换为字符串并左侧补0到6位(保留原始字符串的数字内容优先)"""
|
||||
if value is None:
|
||||
return ""
|
||||
s = str(value).strip()
|
||||
# 尝试提取整数部分
|
||||
try:
|
||||
# 支持像 '00123' 或 '123' 的情况
|
||||
num = int(s)
|
||||
return str(num).zfill(6)
|
||||
except Exception:
|
||||
# 若不能转为整数,直接用原始字符串左补0(或裁切)
|
||||
return s.zfill(6)[-6:]
|
||||
|
||||
|
||||
def main(db_config_path: str, table_name: str = "temp_staff_data", dry_run: bool = True):
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
if not os.path.isabs(db_config_path):
|
||||
# 相对路径从当前脚本目录解析
|
||||
db_config_path = os.path.join(base_dir, db_config_path)
|
||||
|
||||
if not os.path.exists(db_config_path):
|
||||
LOG.error("找不到 db_config 文件: %s", db_config_path)
|
||||
sys.exit(1)
|
||||
|
||||
db_config = load_db_config_from_file(db_config_path)
|
||||
engine = build_engine_from_config(db_config)
|
||||
|
||||
# 使用事务(begin)确保更新会被提交
|
||||
with engine.begin() as conn:
|
||||
# 读取表头,确定列名映射
|
||||
res = conn.execute(text(f"SELECT * FROM `{table_name}` LIMIT 1"))
|
||||
cols = list(res.keys())
|
||||
LOG.info("temp_staff_data 可用列: %s", cols)
|
||||
|
||||
name_col = pick_column_name(["姓名", "name"], cols)
|
||||
phone_col = pick_column_name(["手机号", "手机号_常用的一个_", "phone", "phonenumber"], cols)
|
||||
if not name_col or not phone_col:
|
||||
LOG.error("无法在 %s 中找到 必需 列 (姓名/手机号)。请检查表结构。", table_name)
|
||||
return
|
||||
|
||||
# 读取所有数据(若数据量巨大可以改为分批处理)
|
||||
df = pd.read_sql(text(f"SELECT * FROM `{table_name}`"), conn)
|
||||
LOG.info("读取到 %d 条 temp_staff_data 记录", len(df))
|
||||
|
||||
updated = 0
|
||||
skipped_no_user = 0
|
||||
skipped_multi = 0
|
||||
errors = 0
|
||||
|
||||
for _, row in df.iterrows():
|
||||
staff_name = row.get(name_col)
|
||||
phone = row.get(phone_col)
|
||||
if pd.isna(staff_name) or staff_name is None:
|
||||
LOG.warning("跳过一行,姓名为空")
|
||||
skipped_no_user += 1
|
||||
continue
|
||||
|
||||
staff_name_str = str(staff_name).strip()
|
||||
# 查找 sys_user
|
||||
try:
|
||||
users = conn.execute(
|
||||
text("SELECT user_id, user_name FROM sys_user WHERE nick_name = :nick AND account_type = '1'"),
|
||||
{"nick": staff_name_str},
|
||||
).fetchall()
|
||||
except Exception as e:
|
||||
LOG.exception("查询 sys_user 失败: %s", e)
|
||||
errors += 1
|
||||
continue
|
||||
|
||||
if len(users) == 0:
|
||||
LOG.info("未找到 sys_user (nickname=%s, account_type=1),跳过", staff_name_str)
|
||||
skipped_no_user += 1
|
||||
continue
|
||||
if len(users) > 1:
|
||||
LOG.warning("找到多条 sys_user (nickname=%s, account_type=1),跳过以避免歧义", staff_name_str)
|
||||
skipped_multi += 1
|
||||
continue
|
||||
|
||||
user_row = users[0]
|
||||
user_id = user_row[0]
|
||||
current_user_name = user_row[1]
|
||||
# 计算 user_no 并更新 user_name & phonenumber
|
||||
user_no = zero_pad_to_six(current_user_name)
|
||||
new_user_name = "" if phone is None or (isinstance(phone, float) and pd.isna(phone)) else str(phone).strip()
|
||||
new_phonenumber = new_user_name
|
||||
|
||||
LOG.info("准备更新 sys_user.id=%s: user_no=%s, user_name=%s, phonenumber=%s",
|
||||
user_id, user_no, new_user_name, new_phonenumber)
|
||||
|
||||
if dry_run:
|
||||
LOG.info("dry-run 模式,未执行写入")
|
||||
updated += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
conn.execute(
|
||||
text("UPDATE sys_user SET user_no = :user_no, user_name = :user_name, phonenumber = :phone WHERE user_id = :id"),
|
||||
{"user_no": user_no, "user_name": new_user_name, "phone": new_phonenumber, "id": user_id},
|
||||
)
|
||||
updated += 1
|
||||
except Exception as e:
|
||||
LOG.exception("更新 sys_user.id=%s 失败: %s", user_id, e)
|
||||
errors += 1
|
||||
|
||||
LOG.info("处理完成,统计: updated=%d, skipped_no_user=%d, skipped_multi=%d, errors=%d",
|
||||
updated, skipped_no_user, skipped_multi, errors)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="从 temp_staff_data 更新 sys_user")
|
||||
parser.add_argument("--db-config", type=str, default="../学校历史数据迁移/db_config.py", help="数据库配置文件路径(python模块含 DB_CONFIG)")
|
||||
parser.add_argument("--table", type=str, default="temp_staff_data", help="临时表名")
|
||||
parser.add_argument("--dry-run", action="store_true", help="仅打印将要执行的操作,不写入数据库")
|
||||
args = parser.parse_args()
|
||||
|
||||
main(db_config_path=args.db_config, table_name=args.table, dry_run=args.dry_run)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
-- 验证教职工数据导入结果
|
||||
-- 执行时间: 2026-01-06
|
||||
|
||||
-- 1. 查看临时表结构
|
||||
DESCRIBE temp_staff_data;
|
||||
|
||||
-- 2. 查看总记录数
|
||||
SELECT COUNT(*) as total_records FROM temp_staff_data;
|
||||
|
||||
-- 3. 查看各部门员工数量统计
|
||||
SELECT
|
||||
`部门` as department,
|
||||
COUNT(*) as employee_count
|
||||
FROM temp_staff_data
|
||||
GROUP BY `部门`
|
||||
ORDER BY employee_count DESC;
|
||||
|
||||
-- 4. 查看性别分布
|
||||
SELECT
|
||||
`性别` as gender,
|
||||
COUNT(*) as count
|
||||
FROM temp_staff_data
|
||||
GROUP BY `性别`;
|
||||
|
||||
-- 5. 查看是否在编状态分布
|
||||
SELECT
|
||||
`是否在编_临时员工写L,外聘老师写W,正式写0_` as employment_status,
|
||||
COUNT(*) as count
|
||||
FROM temp_staff_data
|
||||
GROUP BY `是否在编_临时员工写L,外聘老师写W,正式写0_`;
|
||||
|
||||
-- 6. 查看前10条记录详情
|
||||
SELECT
|
||||
`序号`,
|
||||
`姓名`,
|
||||
`性别`,
|
||||
`职务`,
|
||||
`部门`,
|
||||
`是否在编_临时员工写L,外聘老师写W,正式写0_`,
|
||||
`手机号_常用的一个_`
|
||||
FROM temp_staff_data
|
||||
ORDER BY `序号`
|
||||
LIMIT 10;
|
||||
|
||||
-- 7. 检查是否有空值
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN `姓名` IS NULL OR `姓名` = '' THEN 1 ELSE 0 END) as null_names,
|
||||
SUM(CASE WHEN `手机号_常用的一个_` IS NULL OR `手机号_常用的一个_` = '' THEN 1 ELSE 0 END) as null_phones,
|
||||
SUM(CASE WHEN `部门` IS NULL OR `部门` = '' THEN 1 ELSE 0 END) as null_departments
|
||||
FROM temp_staff_data;
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,128 @@
|
||||
# 教职工数据迁移总结报告
|
||||
|
||||
## 📋 项目概述
|
||||
|
||||
**项目名称**: 教职工数据迁移
|
||||
**执行时间**: 2026-01-06
|
||||
**负责人**: AI助手
|
||||
**数据源**: `人员清单列表1@正式员工.xlsx`
|
||||
|
||||
## 📊 数据统计
|
||||
|
||||
### 基本信息
|
||||
- **总记录数**: 229 条
|
||||
- **数据完整性**: 100% (无空值记录)
|
||||
- **导入状态**: ✅ 成功
|
||||
|
||||
### 性别分布
|
||||
- **女性员工**: 125 人 (54.6%)
|
||||
- **男性员工**: 104 人 (45.4%)
|
||||
|
||||
### 部门分布 (前10名)
|
||||
| 部门 | 人数 |
|
||||
|------|------|
|
||||
| 医药健康学院 | 31 |
|
||||
| 人文学院 | 30 |
|
||||
| 智能控制学院 | 25 |
|
||||
| 生态园林学院 | 22 |
|
||||
| 智能制造学院 | 19 |
|
||||
| 餐饮休闲学院 | 18 |
|
||||
| 学生处(团委) | 11 |
|
||||
| 教务处 | 9 |
|
||||
| 数字信息学院 | 9 |
|
||||
|
||||
### 编制情况
|
||||
- **正式员工**: 229 人 (100%)
|
||||
- **临时员工**: 0 人
|
||||
- **外聘教师**: 0 人
|
||||
|
||||
## 🗂️ 临时表结构
|
||||
|
||||
```sql
|
||||
CREATE TABLE `temp_staff_data` (
|
||||
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
`序号` TEXT DEFAULT NULL,
|
||||
`姓名` TEXT DEFAULT NULL,
|
||||
`性别` TEXT DEFAULT NULL,
|
||||
`职务` TEXT DEFAULT NULL,
|
||||
`部门` TEXT DEFAULT NULL,
|
||||
`是否在编_临时员工写L,外聘老师写W,正式写0_` TEXT DEFAULT NULL,
|
||||
`手机号_常用的一个_` TEXT DEFAULT NULL,
|
||||
`import_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
```
|
||||
|
||||
## 📁 文件清单
|
||||
|
||||
1. **数据源文件**
|
||||
- `人员清单列表1@正式员工.xlsx` - 原始教职工数据
|
||||
|
||||
2. **脚本文件**
|
||||
- `simple_import.py` - 数据导入脚本
|
||||
- `verify_import.sql` - 数据验证脚本
|
||||
|
||||
3. **配置文件**
|
||||
- `requirements.txt` - Python依赖包
|
||||
- `README.md` - 使用说明文档
|
||||
|
||||
4. **文档文件**
|
||||
- `教职工数据迁移总结.md` - 本总结报告
|
||||
|
||||
## ✅ 执行结果
|
||||
|
||||
### 导入过程
|
||||
1. ✅ Excel文件读取成功 (229行数据)
|
||||
2. ✅ 临时表创建成功
|
||||
3. ✅ 数据导入完成 (229条记录)
|
||||
4. ✅ 数据验证通过 (无数据丢失或损坏)
|
||||
|
||||
### 数据质量检查
|
||||
- ✅ **姓名字段**: 229/229 完整 (100%)
|
||||
- ✅ **手机号字段**: 229/229 完整 (100%)
|
||||
- ✅ **部门字段**: 229/229 完整 (100%)
|
||||
- ✅ **性别字段**: 数据合理分布
|
||||
- ✅ **编制状态**: 全部为正式员工
|
||||
|
||||
## 🔧 技术实现
|
||||
|
||||
### 导入脚本特性
|
||||
- **自动列名清理**: 处理中文特殊字符和括号
|
||||
- **动态表结构**: 根据Excel实际列名创建表
|
||||
- **批量导入**: 支持大数据量高效导入
|
||||
- **错误处理**: 完善的异常处理机制
|
||||
|
||||
### 依赖环境
|
||||
- **Python**: 3.12+
|
||||
- **pandas**: 数据处理
|
||||
- **sqlalchemy**: 数据库连接
|
||||
- **openpyxl**: Excel文件读取
|
||||
- **MySQL**: 数据存储
|
||||
|
||||
## 📋 数据样例
|
||||
|
||||
| 序号 | 姓名 | 性别 | 职务 | 部门 | 是否在编 | 手机号 |
|
||||
|------|------|------|------|------|---------|--------|
|
||||
| 1 | 杨国强 | 男 | 党委书记、院长 | 院领导 | 0 | 13958119380 |
|
||||
| 2 | 卢红华 | 男 | 党委副书记、纪委书记、副院长 | 院领导 | 0 | 13867410680 |
|
||||
| 3 | 徐弘 | 男 | 党委委员、副院长 | 院领导 | 0 | 15957163076 |
|
||||
|
||||
## 🎯 后续建议
|
||||
|
||||
1. **数据清洗**: 根据业务需要对职务、部门等字段进行标准化
|
||||
2. **数据关联**: 将临时表数据关联到正式的员工表结构
|
||||
3. **权限配置**: 为导入数据配置相应的访问权限
|
||||
4. **备份策略**: 制定数据备份和恢复策略
|
||||
|
||||
## 📝 备注
|
||||
|
||||
- 数据导入过程中未发现任何数据质量问题
|
||||
- 所有229条记录均完整导入,无数据丢失
|
||||
- 临时表可作为后续数据处理的中间存储
|
||||
- 建议在正式使用前进行业务逻辑验证
|
||||
|
||||
---
|
||||
|
||||
**执行完成时间**: 2026-01-06 10:26:10
|
||||
**数据验证时间**: 2026-01-06 10:30:00
|
||||
**报告生成时间**: 2026-01-06
|
||||
Reference in new issue
Block a user