429 lines
14 KiB
Python
429 lines
14 KiB
Python
|
||||
|
|
from sqlalchemy import func
|
|||
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|||
|
|
from sqlalchemy.orm import Session
|
|||
|
|
from datetime import date
|
|||
|
|
|
|||
|
|
from model import Student, Classes, Score, Employment
|
|||
|
|
from schema.student_schema import AgeQuery
|
|||
|
|
|
|||
|
|
|
|||
|
|
class BusinessException(Exception):
|
|||
|
|
"""业务异常类,DAO层捕获数据库异常后抛出,由API层处理"""
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
|
|||
|
|
class DuplicateStudentNo(BusinessException):
|
|||
|
|
"""学号已存在。继承 BusinessException,API 层可以单独挑出来返回 409。"""
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
#=============把一个学生对象变成字典形式================
|
|||
|
|
def student_dict(student):
|
|||
|
|
if student is None:
|
|||
|
|
return {}
|
|||
|
|
return {"s_id": student.sid,
|
|||
|
|
"student_no": student.student_no,
|
|||
|
|
"student_name":student.student_name,
|
|||
|
|
"class_id":student.class_id,
|
|||
|
|
"flag":student.flag,
|
|||
|
|
"advisor_id":student.advisor_id,
|
|||
|
|
"native_place":student.native_place,
|
|||
|
|
"school":student.school,
|
|||
|
|
"major":student.major,
|
|||
|
|
"enrollment_time":student.enrollment_time,
|
|||
|
|
"graduation_time":student.graduation_time,
|
|||
|
|
"education":student.education,
|
|||
|
|
"age":student.age,
|
|||
|
|
"gender":student.gender,
|
|||
|
|
"state":student.state}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def create_student(db: Session,
|
|||
|
|
student_no: str,
|
|||
|
|
student_name: str,
|
|||
|
|
class_id: int = None,
|
|||
|
|
flag: int = 1,
|
|||
|
|
advisor_id: int = None,
|
|||
|
|
native_place: str = None,
|
|||
|
|
school: str = None,
|
|||
|
|
major: str = None,
|
|||
|
|
enrollment_time: date = None,
|
|||
|
|
graduation_time: date = None,
|
|||
|
|
education: str = None,
|
|||
|
|
age: int = None,
|
|||
|
|
gender: str = None,
|
|||
|
|
state: str = None
|
|||
|
|
):
|
|||
|
|
"""
|
|||
|
|
创建学生
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
# 学号是唯一约束,先查一下,给出比 IntegrityError 更好懂的提示
|
|||
|
|
existing = db.query(Student).filter(Student.student_no == student_no).first()
|
|||
|
|
if existing is not None:
|
|||
|
|
raise DuplicateStudentNo(f"学号 {student_no} 已存在")
|
|||
|
|
|
|||
|
|
new_student = Student(
|
|||
|
|
student_no=student_no,
|
|||
|
|
student_name=student_name,
|
|||
|
|
class_id=class_id,
|
|||
|
|
flag=flag,
|
|||
|
|
advisor_id=advisor_id,
|
|||
|
|
native_place=native_place,
|
|||
|
|
school=school,
|
|||
|
|
major=major,
|
|||
|
|
enrollment_time=enrollment_time,
|
|||
|
|
graduation_time=graduation_time,
|
|||
|
|
education=education,
|
|||
|
|
age=age,
|
|||
|
|
gender=gender,
|
|||
|
|
state=state,
|
|||
|
|
)
|
|||
|
|
db.add(new_student)
|
|||
|
|
db.commit()
|
|||
|
|
db.refresh(new_student)
|
|||
|
|
return new_student
|
|||
|
|
except SQLAlchemyError as e:
|
|||
|
|
db.rollback()
|
|||
|
|
raise BusinessException(f"创建学生失败: {str(e)}")
|
|||
|
|
|
|||
|
|
#================查询学生==================
|
|||
|
|
def search_student(db: Session, student_no: str,
|
|||
|
|
student_name: str,
|
|||
|
|
class_id: int = None,
|
|||
|
|
page: int = 1,
|
|||
|
|
size:int=5):
|
|||
|
|
try:
|
|||
|
|
query = db.query(Student).filter(Student.flag == 1)
|
|||
|
|
|
|||
|
|
if student_no is not None:
|
|||
|
|
query = query.filter(Student.student_no == student_no)
|
|||
|
|
if student_name:
|
|||
|
|
query = query.filter(Student.student_name.like(f"%{student_name}%"))
|
|||
|
|
if class_id is not None:
|
|||
|
|
query = query.filter(Student.class_id == class_id)
|
|||
|
|
|
|||
|
|
total = query.count()
|
|||
|
|
offset = (page - 1) * size
|
|||
|
|
students = query.order_by(Student.sid.desc()).offset(offset).limit(size).all()
|
|||
|
|
|
|||
|
|
items = [student_dict(s) for s in students]
|
|||
|
|
pages = (total + size - 1) // size if size > 0 else 0
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"items": items,
|
|||
|
|
"total": total,
|
|||
|
|
"page": page,
|
|||
|
|
"page_size": size,
|
|||
|
|
"pages": pages,
|
|||
|
|
}
|
|||
|
|
except SQLAlchemyError as e:
|
|||
|
|
raise BusinessException(f"查询学生列表失败: {str(e)}")
|
|||
|
|
#=================ID查询学生详情====================
|
|||
|
|
def get_student(db: Session, sid: int):
|
|||
|
|
"""
|
|||
|
|
根据ID查询学生,自动过滤逻辑删除
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
student = db.query(Student).filter(
|
|||
|
|
Student.sid == sid,
|
|||
|
|
Student.flag == 1
|
|||
|
|
).first()
|
|||
|
|
return student
|
|||
|
|
except SQLAlchemyError as e:
|
|||
|
|
raise BusinessException(f"查询学生失败: {str(e)}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
#===============更新学生信息================
|
|||
|
|
def update_student(db: Session, sid: int,
|
|||
|
|
student_no: str = None,
|
|||
|
|
student_name: str = None,
|
|||
|
|
class_id: int = None,
|
|||
|
|
flag: int = None,
|
|||
|
|
advisor_id: int = None,
|
|||
|
|
native_place: str = None,
|
|||
|
|
school: str = None,
|
|||
|
|
major: str = None,
|
|||
|
|
enrollment_time: date = None,
|
|||
|
|
graduation_time: date = None,
|
|||
|
|
education: str = None,
|
|||
|
|
age: int = None,
|
|||
|
|
gender: str = None,
|
|||
|
|
state: str = None):
|
|||
|
|
"""
|
|||
|
|
更新学生信息:只改传了值的字段,没传(None)的保持原样。
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
student = db.query(Student).filter(Student.sid == sid).first()
|
|||
|
|
if student is None:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
# 判断的是"传进来的参数",是否是None
|
|||
|
|
if student_no is not None:
|
|||
|
|
student.student_no = student_no
|
|||
|
|
if student_name is not None:
|
|||
|
|
student.student_name = student_name
|
|||
|
|
if class_id is not None:
|
|||
|
|
student.class_id = class_id
|
|||
|
|
if flag is not None:
|
|||
|
|
student.flag = flag
|
|||
|
|
if advisor_id is not None:
|
|||
|
|
student.advisor_id = advisor_id
|
|||
|
|
if native_place is not None:
|
|||
|
|
student.native_place = native_place
|
|||
|
|
if school is not None:
|
|||
|
|
student.school = school
|
|||
|
|
if major is not None:
|
|||
|
|
student.major = major
|
|||
|
|
if enrollment_time is not None:
|
|||
|
|
student.enrollment_time = enrollment_time
|
|||
|
|
if graduation_time is not None:
|
|||
|
|
student.graduation_time = graduation_time
|
|||
|
|
if education is not None:
|
|||
|
|
student.education = education
|
|||
|
|
if age is not None:
|
|||
|
|
student.age = age
|
|||
|
|
if gender is not None:
|
|||
|
|
student.gender = gender
|
|||
|
|
if state is not None:
|
|||
|
|
student.state = state
|
|||
|
|
|
|||
|
|
db.commit()
|
|||
|
|
db.refresh(student)
|
|||
|
|
return student
|
|||
|
|
except SQLAlchemyError as e:
|
|||
|
|
db.rollback()
|
|||
|
|
raise BusinessException(f"学生更新信息失败: {str(e)}")
|
|||
|
|
|
|||
|
|
#==============逻辑删除学生==================
|
|||
|
|
def delete_student(db: Session, sid: int):
|
|||
|
|
try:
|
|||
|
|
student = db.query(Student).filter(Student.sid == sid).first()
|
|||
|
|
if student is None or student.flag == 0:
|
|||
|
|
return None
|
|||
|
|
student.flag = 0
|
|||
|
|
db.commit() # 进行任务提交
|
|||
|
|
db.refresh(student)
|
|||
|
|
return student
|
|||
|
|
except SQLAlchemyError as e:
|
|||
|
|
db.rollback()
|
|||
|
|
raise BusinessException(f"学生删除失败: {str(e)}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
#==============性别查询学生==================
|
|||
|
|
def get_class_gender(db: Session):
|
|||
|
|
class_list = db.query(Classes).all()
|
|||
|
|
res = []
|
|||
|
|
for cls in class_list:
|
|||
|
|
cid = cls.cid
|
|||
|
|
# 当前班级有效学生总数
|
|||
|
|
class_total = (
|
|||
|
|
db.query(func.count(Student.sid))
|
|||
|
|
.filter(Student.class_id == cid, Student.flag == 1)
|
|||
|
|
.scalar()
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 当前班级男生数量
|
|||
|
|
male_count = (
|
|||
|
|
db.query(func.count(Student.sid))
|
|||
|
|
.filter(Student.class_id == cid, Student.flag == 1, Student.gender == "男")
|
|||
|
|
.scalar()
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 当前班级女生数量
|
|||
|
|
female_count = (
|
|||
|
|
db.query(func.count(Student.sid))
|
|||
|
|
.filter(Student.class_id == cid, Student.flag == 1, Student.gender == "女")
|
|||
|
|
.scalar()
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
res.append({
|
|||
|
|
"class_name": cls.class_name,
|
|||
|
|
"class_total": class_total,
|
|||
|
|
"boys_count": male_count,
|
|||
|
|
"girls_count": female_count
|
|||
|
|
})
|
|||
|
|
return res
|
|||
|
|
|
|||
|
|
#==============查询每次考试高于自定义分数线的学生==================
|
|||
|
|
def get_higher_score(db:Session,score):
|
|||
|
|
all_students = (db.query(Student)
|
|||
|
|
.filter(Student.flag==1)
|
|||
|
|
.all())
|
|||
|
|
higherStu=[]
|
|||
|
|
for student in all_students:
|
|||
|
|
all_scores = []
|
|||
|
|
for stu_score in student.scores:
|
|||
|
|
if stu_score.score<score:
|
|||
|
|
break
|
|||
|
|
all_scores.append(stu_score.score)
|
|||
|
|
else:
|
|||
|
|
if len(all_scores) > 0:
|
|||
|
|
higherStu.append({
|
|||
|
|
'stu_id':student.sid,
|
|||
|
|
'stu_name':student.student_name,
|
|||
|
|
'stu_score':all_scores
|
|||
|
|
})
|
|||
|
|
return higherStu
|
|||
|
|
|
|||
|
|
#==============查询不及格次数大于指定次数的学生==================
|
|||
|
|
def get_fail_more_than(db: Session, fail_times: int):
|
|||
|
|
# 查询有效学生
|
|||
|
|
all_students = (db.query(Student)
|
|||
|
|
.filter(Student.flag == 1)
|
|||
|
|
.all())
|
|||
|
|
result = []
|
|||
|
|
for student in all_students:
|
|||
|
|
#不及格成绩列表
|
|||
|
|
fail_score_list = []
|
|||
|
|
for stu_score in student.scores:
|
|||
|
|
if stu_score.score < 60:
|
|||
|
|
fail_score_list.append(stu_score.score)
|
|||
|
|
if len(fail_score_list) >= fail_times:
|
|||
|
|
result.append({
|
|||
|
|
'stu_id': student.sid,
|
|||
|
|
'stu_name': student.student_name,
|
|||
|
|
'class_name': student.classes.class_name,
|
|||
|
|
'fail_scores': fail_score_list
|
|||
|
|
})
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
|
|||
|
|
#==============查询所有学生就业时长==================
|
|||
|
|
def work_time(db:Session):
|
|||
|
|
all_times = []
|
|||
|
|
#所有工作信息
|
|||
|
|
worksInfo = db.query(Employment).filter(Employment.flag == 1).all()
|
|||
|
|
#所有学生信息
|
|||
|
|
all_students = (db.query(Student).filter(Student.flag==1).all())
|
|||
|
|
for work in worksInfo:
|
|||
|
|
if work.offer_time is not None and work.employment_start_time is not None and work.offer_time >= work.employment_start_time:
|
|||
|
|
delta = work.offer_time - work.employment_start_time
|
|||
|
|
days = delta.days # 整数天数
|
|||
|
|
for i in all_students:
|
|||
|
|
if i.sid==work.student_id:
|
|||
|
|
all_times.append({'name':i.student_name,'id': work.student_id, 'work_time': days})
|
|||
|
|
return all_times
|
|||
|
|
|
|||
|
|
#==============统计平均分,可以切换排序方式==================
|
|||
|
|
def count_avg(db: Session,order_type: str = "desc"):
|
|||
|
|
avg_score = func.avg(Score.score).label("avg_score")
|
|||
|
|
query = (
|
|||
|
|
db.query(
|
|||
|
|
Score.score.label("score"),
|
|||
|
|
Classes.cid.label("class_id"),
|
|||
|
|
Classes.class_name.label("class_name"),
|
|||
|
|
avg_score,
|
|||
|
|
)
|
|||
|
|
.join(Student, Score.student_id == Student.sid)
|
|||
|
|
.join(Classes, Student.class_id == Classes.cid)
|
|||
|
|
.group_by(Score.score, Classes.cid, Classes.class_name)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 动态排序
|
|||
|
|
if order_type == "asc":
|
|||
|
|
query = query.order_by(avg_score.asc())
|
|||
|
|
else:
|
|||
|
|
query = query.order_by(avg_score.desc())
|
|||
|
|
rows = query.all()
|
|||
|
|
# Row 转 dict,方便上层使用
|
|||
|
|
return [
|
|||
|
|
{
|
|||
|
|
"score": row.score,
|
|||
|
|
"class_id": row.class_id,
|
|||
|
|
"class_name": row.class_name,
|
|||
|
|
"avg_score": float(row.avg_score) if row.avg_score is not None else 0.0,
|
|||
|
|
}
|
|||
|
|
for row in rows
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
#==============按年龄区间自定义比较==================
|
|||
|
|
def get_students_by_age(db: Session, query:AgeQuery):
|
|||
|
|
q = db.query(Student).filter(Student.flag==1)
|
|||
|
|
|
|||
|
|
op = query.operator
|
|||
|
|
if op == "gt":
|
|||
|
|
q = q.filter(Student.age > query.value)
|
|||
|
|
elif op == "lt":
|
|||
|
|
q = q.filter(Student.age < query.value)
|
|||
|
|
elif op == "eq":
|
|||
|
|
q = q.filter(Student.age == query.value)
|
|||
|
|
elif op == "ge":
|
|||
|
|
q = q.filter(Student.age >= query.value)
|
|||
|
|
elif op == "le":
|
|||
|
|
q = q.filter(Student.age <= query.value)
|
|||
|
|
elif op == "between":
|
|||
|
|
q = q.filter(Student.age.between(query.min_value, query.max_value))
|
|||
|
|
return q.all()
|
|||
|
|
|
|||
|
|
#==============按薪资排名==================
|
|||
|
|
def get_top_n_salary(db: Session, rank: int):
|
|||
|
|
rows = (
|
|||
|
|
db.query(
|
|||
|
|
Student.student_name.label("student_name"),
|
|||
|
|
Classes.class_name.label("class_name"),
|
|||
|
|
Employment.offer_time.label("offer_time"),
|
|||
|
|
Employment.company_name.label("company"),
|
|||
|
|
Employment.salary.label("salary"),
|
|||
|
|
)
|
|||
|
|
.join(Student, Employment.student_id == Student.sid)
|
|||
|
|
.join(Classes, Student.class_id == Classes.cid)
|
|||
|
|
.order_by(Employment.salary.desc())
|
|||
|
|
.limit(rank)
|
|||
|
|
.all()
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return [
|
|||
|
|
{
|
|||
|
|
"student_name": row.student_name,
|
|||
|
|
"class_name": row.class_name,
|
|||
|
|
"offer_time": row.offer_time,
|
|||
|
|
"company": row.company,
|
|||
|
|
"salary": float(row.salary) if row.salary is not None else 0.0,
|
|||
|
|
}
|
|||
|
|
for row in rows
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
#==============每个班的平均就业时长==================
|
|||
|
|
def avg_work_time_by_class(db: Session):
|
|||
|
|
rows = (
|
|||
|
|
db.query(
|
|||
|
|
Classes.cid.label("class_id"),
|
|||
|
|
Classes.class_name.label("class_name"),
|
|||
|
|
Employment.employment_start_time,
|
|||
|
|
Employment.offer_time,
|
|||
|
|
)
|
|||
|
|
.join(Student, Employment.student_id == Student.sid)
|
|||
|
|
.join(Classes, Student.class_id == Classes.cid)
|
|||
|
|
.filter(Employment.employment_start_time.isnot(None))
|
|||
|
|
.filter(Employment.offer_time.isnot(None))
|
|||
|
|
.filter(Employment.flag == 1, Student.flag == 1)
|
|||
|
|
.filter(Employment.offer_time >= Employment.employment_start_time)
|
|||
|
|
.all()
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 普通字典:{class_id: {"class_name": ..., "days_list": [...]}}
|
|||
|
|
class_data = {}
|
|||
|
|
|
|||
|
|
for row in rows:
|
|||
|
|
delta = row.offer_time - row.employment_start_time
|
|||
|
|
days = delta.total_seconds() / 86400
|
|||
|
|
if row.class_id not in class_data:
|
|||
|
|
class_data[row.class_id] = {
|
|||
|
|
"class_name": row.class_name,
|
|||
|
|
"days_list": [],
|
|||
|
|
}
|
|||
|
|
class_data[row.class_id]["days_list"].append(days)
|
|||
|
|
# 组装结果
|
|||
|
|
result = []
|
|||
|
|
for class_id, data in class_data.items():
|
|||
|
|
days_list = data["days_list"]
|
|||
|
|
avg_days = round(sum(days_list) / len(days_list), 2)
|
|||
|
|
result.append({
|
|||
|
|
"class_id": class_id,
|
|||
|
|
"class_name": data["class_name"],
|
|||
|
|
"days": avg_days,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return result
|