ORM
- table => class ,表映射为类
- row => object ,行映射为实例
- column => property ,字段映射为属性
from sqlalchemy.ext.declarative import declarative_base # 创建基类,便于实体类继承。SQLAlchemy大量使用了元编程 Base = declarative_base()
# 创建实体类 class Student(Base): # 指定表名 __tablename__ = 'student' # 定义类属性对应字段 id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(64), nullable=False) age = Column(Integer) # 第一参数是字段名,如果和属性名不一致,一定要指定 # age = Column('age', Integer) def __repr__(self): return "{} id={} name={} age={}".format( self.__class__.__name__, self.id, self.name, self.age)
s = Student(name='tom') print(s.name) s.age = 20 print(s.age)
# 删除继承自Base的所有表 Base.metadata.drop_all(engine) # 创建继承自Base的所有表 Base.metadata.create_all(engine)
metadata 记录的是继承自Base的表,
创建会话session
# 创建session Session = sessionmaker(bind=engine) # 返回类 session = Session() # 实例化 # 依然在第一次使用时连接数据库
session对象线程不安全。所以不同线程应该使用不用的session对象。
import sqlalchemy from sqlalchemy import create_engine,Column,String,Integer from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker USER = "root" PWD = "root" HOST = "172.16.34.34" PORT = "3306" DB = "test" conn_str = 'mysql+pymysql://{}:{}@{}:{}/{}'.format( USER,PWD,HOST,PORT,DB ) engine = create_engine(conn_str, echo=True) Base = declarative_base() # 创建实体类 class Student(Base): # 指定表名,必须指定 __tablename__ = 'student' # 定义类属性对应字段 id = Column(Integer,autoincrement=True,primary_key=True) name = Column(String(64),nullable=False) age = Column("age",Integer,nullable=False) gen = Column("gender",String(20)) # 第一参数是字段名,如果和属性名不一致,一定要指定 # age = Column('age', Integer) def __repr__(self): return "{} id={} name={} age={} gender={}".format( self.__class__.__name__, self.id, self.name, self.age, self.gen) print(Student) print(repr(Student.__table__)) s = Student(name='tom',age=20) print(s.name) s.gender = "M" print(s.gender) # Base.metadata.drop_all(bind=engine) # Base.metadata.create_all(bind=engine) # metadata 记录的是继承自Base的表,删除也是 Session = sessionmaker(bind=engine) # 不做操作不会处理连接 session =Session() # 创建session对象,也不会在此时连接数据库
CRUD操作
增
add():增加一个对象
add_all():可迭代对象,元素是对象
session.add(s) #添加一次对象s,未提交 print(s) session.commit() #提交一次对象s print(s) try: session.add_all([s]) print(s) session.commit() #在同一个线程内,再次提交同一个未修改过的对象不会执行 except: session.rollback() # 使用要加日志记录 raise
add_all()方法不会提交成功的,不是因为它不对,而是s,s成功提交后,s的主键就有了值,所以,只要s没有修改过,就认为没有改动。如下,s变化了,就可以提交修改了。
s.name = 'jerry' # 修改 session.add_all([s])
s主键没有值,就是新增;主键有值,就是找到主键对应的记录修改。
简单查询
使用query()方法,返回一个Query对象
students = session.query(Student) # 无条件,相当于select * from student for student in students: print(student) print('~~~~~~~~~~~~~') student = session.query(Student).get(3) # 通过主键查询,相当于select * from student where id=3 print(student)
query方法将实体类传入,返回类的对象可迭代对象,这时候并不查询。迭代它就执行SQL来查询数据库,封装数据到指定类的实例。
get方法使用主键查询,返回一条传入类的一个实例。对象不存在,返回None
改
先查后改
student = session.query(Student).get(3) print(student) student.name = 'sam' student.age = 30 print(student) session.add(student) session.commit()
删
try: student = Student(id=2, name="serry", age=10) session.delete(student) session.commit() except Exception as e: session.rollback() print('~~~~~~~~') print(e)
Instance '<Student at 0x26edf10b438>' is not persisted
数据库查询数据慢且忙,最好在容器中就记录下来,不要重复查询相同的数据
状态
每一个实体,都有一个状态属性_sa_instance_state,其类型是sqlalchemy.orm.state.InstanceState,可以使用sqlalchemy.inspect(entity)函数查看状态。
常见的状态值有transient、pending、persistent、deleted、detached。
状态 | 说明 |
transient | 实体类尚未加入到session中,同时并没有保存到数据库中 |
pending | transient的实体被add()到session中,状态切换到pending,但它还没有flush到数据库中 |
persistent |
session中的实体对象对应着数据库中的真实记录。pending状态在提交成功后可以变成persistent状态,或者查询成功返回的实体也是persistent状态 |
deleted |
实体被删除且已经flush但未commit完成。事务提交成功了,实体变成detached,事务失败, |
detached | 删除成功的实体进入这个状态 |
新建一个实体,状态是transient临时的。
一旦add()后从transient变成pending状态。
成功commit()后从pending变成persistent状态。
成功查询返回的实体对象,也是persistent状态。
persistent状态的实体,修改依然是persistent状态。
persistent状态的实体,删除后,flush后但没有commit,就变成deteled状态,成功提交,变为detached状态,提交失败,还原到persistent状态。flush方法,主动把改变应用到数据库中去。
删除、修改操作,需要对应一个真实的记录,所以要求实体对象是persistent状态。
import sqlalchemy from sqlalchemy import create_engine,Column,String,Integer from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker USER = "root" PWD = "root" HOST = "172.16.34.34" PORT = "3306" DB = "test" conn_str = 'mysql+pymysql://{}:{}@{}:{}/{}'.format( USER,PWD,HOST,PORT,DB ) engine = create_engine(conn_str, echo=True) Base = declarative_base() # 创建实体类 class Student(Base): # 指定表名,必须指定 __tablename__ = 'student' # 定义类属性对应字段 id = Column(Integer,autoincrement=True,primary_key=True) name = Column(String(64),nullable=False) age = Column("age",Integer,nullable=False) gen = Column("gender",String(20)) # 第一参数是字段名,如果和属性名不一致,一定要指定 # age = Column('age', Integer) def __repr__(self): return "{} id={} name={} age={} gender={}".format( self.__class__.__name__, self.id, self.name, self.age, self.gen) print(Student) print(repr(Student.__table__)) Session = sessionmaker(bind=engine) session =Session() from sqlalchemy.orm.state import InstanceState def getstate(entity,i): insp = sqlalchemy.inspect(entity) state = "session={}, attached={}, transient={},persistent={}, pending={},deleted={}.detached={}".format( insp.session_id, insp._attached, insp.transient, insp.persistent, insp.pending, insp.deleted, insp.detached ) print(i,state) print(insp.key) print("_"*30) # student = session.query(Student).get(3) # getstate(student,1) try: student = Student(name="Tony", age=30) getstate(student, 2) # transit student = Student(name="sammy", age=30) getstate(student, 3) # transit session.add(student) # add后变成pending getstate(student, 4) # pending # session.delete(student) # 删除的前提是persistent,否则抛异常 # getstate(student, 5) session.commit() getstate(student, 6) # persistent session.delete(student) # 删除的前提是persistent,否则抛异常 getstate(student, 7) session.flush() getstate(student,8) session.commit() getstate(student, 9) except Exception as e: session.rollback() print('~~~~~~~~') print(e)
返回结果
2 session=None, attached=False, transient=True,persistent=False, pending=False,deleted=False.detached=False None ______________________________ 3 session=None, attached=False, transient=True,persistent=False, pending=False,deleted=False.detached=False None ______________________________ 4 session=1, attached=True, transient=False,persistent=False, pending=True,deleted=False.detached=False None ______________________________ 2018-11-12 18:04:27,682 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'sql_mode' 2018-11-12 18:04:27,682 INFO sqlalchemy.engine.base.Engine {} 2018-11-12 18:04:27,683 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'lower_case_table_names' 2018-11-12 18:04:27,683 INFO sqlalchemy.engine.base.Engine {} 2018-11-12 18:04:27,685 INFO sqlalchemy.engine.base.Engine SELECT DATABASE() 2018-11-12 18:04:27,685 INFO sqlalchemy.engine.base.Engine {} 2018-11-12 18:04:27,686 INFO sqlalchemy.engine.base.Engine show collation where `Charset` = 'utf8mb4' and `Collation` = 'utf8mb4_bin' 2018-11-12 18:04:27,686 INFO sqlalchemy.engine.base.Engine {} 2018-11-12 18:04:27,687 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS CHAR(60)) AS anon_1 2018-11-12 18:04:27,687 INFO sqlalchemy.engine.base.Engine {} 2018-11-12 18:04:27,688 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS CHAR(60)) AS anon_1 2018-11-12 18:04:27,688 INFO sqlalchemy.engine.base.Engine {} 2018-11-12 18:04:27,689 INFO sqlalchemy.engine.base.Engine SELECT CAST('test collated returns' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_bin AS anon_1 2018-11-12 18:04:27,689 INFO sqlalchemy.engine.base.Engine {} 2018-11-12 18:04:27,690 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) 2018-11-12 18:04:27,691 INFO sqlalchemy.engine.base.Engine INSERT INTO student (name, age, gender) VALUES (%(name)s, %(age)s, %(gender)s) 2018-11-12 18:04:27,691 INFO sqlalchemy.engine.base.Engine {'name': 'sammy', 'age': 30, 'gender': None} 2018-11-12 18:04:27,692 INFO sqlalchemy.engine.base.Engine COMMIT 6 session=1, attached=True, transient=False,persistent=True, pending=False,deleted=False.detached=False (<class '__main__.Student'>, (11,), None) ______________________________ 7 session=1, attached=True, transient=False,persistent=True, pending=False,deleted=False.detached=False (<class '__main__.Student'>, (11,), None) ______________________________ 2018-11-12 18:04:27,693 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) 2018-11-12 18:04:27,694 INFO sqlalchemy.engine.base.Engine SELECT student.gender AS student_gender, student.id AS student_id, student.name AS student_name, student.age AS student_age FROM student WHERE student.id = %(param_1)s 2018-11-12 18:04:27,694 INFO sqlalchemy.engine.base.Engine {'param_1': 11} 2018-11-12 18:04:27,695 INFO sqlalchemy.engine.base.Engine DELETE FROM student WHERE student.id = %(id)s 2018-11-12 18:04:27,695 INFO sqlalchemy.engine.base.Engine {'id': 11} 8 session=1, attached=True, transient=False,persistent=False, pending=False,deleted=True.detached=False (<class '__main__.Student'>, (11,), None) ______________________________ 2018-11-12 18:04:27,696 INFO sqlalchemy.engine.base.Engine COMMIT 9 session=None, attached=False, transient=False,persistent=False, pending=False,deleted=False.detached=True (<class '__main__.Student'>, (11,), None) ______________________________
复杂查询
实体类
import enum class GenderEnum(enum.Enum): M = "M" F = "F" class Employee(Base): __tablename__ = "employees" emp_no = Column(Integer, primary_key=True) birth_date = Column(Date, nullable=False) first_name = Column(String(14), nullable=False) last_name = Column(String(16), nullable=False) gender = Column(Enum(GenderEnum), nullable=False) hire_date = Column(Date, nullable=False) def __repr__(self): return "{} no={} name={} {} gender={}".format( self.__class__.__name__, self.emp_no, self.first_name, self.last_name, self.gender.value )
# 返回的迭代器,查看内容 def show(emps): for x in emps: print(x)
以下语句为条件
最简单的查询:
emps = session.query(Employee).filter(Employee.emp_no > 10015)
show(emps)
与,或,非
and条件可以使用两个filter实现,也可以使用and_,也可使用运算符重载 &
emps = session.query(Employee).filter(Employee.emp_no > 10015).filter(Employee.gender ==GenderEnum.F) show(emps) emps = session.query(Employee).filter(and_(Employee.emp_no > 10015, Employee.gender ==GenderEnum.M)) show(emps) #运算符重载注意表达式要加括号 emps = session.query(Employee).filter((Employee.emp_no > 10015) & (Employee.gender == GenderEnum.M)) show(emps)
or 条件可以使用or_ 或者运算符 |
emps = session.query(Employee).filter(or_(Employee.emp_no > 10018, Employee.emp_no < 10003)) show(emps) #加括号 emps = session.query(Employee).filter((Employee.emp_no > 10018) | (Employee.emp_no < 10003)) show(emps)
not 条件使用not_ 或者运算符 ~
emps = session.query(Employee).filter(not_(Employee.emp_no < 10018)) show(emps) #加括号 emps = session.query(Employee).filter(~(Employee.emp_no < 10018)) show(emps)
总之,与或非的运算符&、|、~,一定要在表达式上加上括号
in
emplist = [10010, 10015, 10018] emps = session.query(Employee).filter(Employee.emp_no.in_(emplist)) show(emps)
not in
emplist = [10010, 10015, 10018] emps = session.query(Employee).filter(~Employee.emp_no.in_(emplist)) show(emps) emps = session.query(Employee).filter(Employee.emp_no.notin_(emplist)) show(emps)
like ,少用
emps = session.query(Employee).filter(Employee.last_name.like('P%')) show(emps)
not like ,少用
emps = session.query(Employee).filter(Employee.last_name.notlike('P%'))
ilike 忽略大小写
emps = session.query(Employee).filter(Employee.last_name.ilike('P%')) show(emps)
排序
升序
emps = session.query(Employee).filter(Employee.emp_no > 10010).order_by(Employee.emp_no) emps = session.query(Employee).filter(Employee.emp_no > 10010).order_by(Employee.emp_no.asc()) show(emps)
降序
emps = session.query(Employee).filter(Employee.emp_no > 10010).order_by(Employee.emp_no.desc())
show(emps)
多列排序
emps = session.query(Employee).filter(Employee.emp_no > 10010).order_by(Employee.last_name).order_by(Employee.emp_no.desc()) show(emps) emps = session.query(Employee).filter(Employee.emp_no > 10010).order_by(Employee.last_name.desc(), Employee.emp_no.asc()) show(emps)
分页
emps = session.query(Employee).limit(4) show(emps)
emps = session.query(Employee).limit(4).offset(18) show(emps)
消费者方法
总共的行数
emps = session.query(Employee) print(len(list(emps))) # 返回大量的结果集,然后转换list print(emps.count()) # 聚合函数count(*)的查询
取所有数据
emps = session.query(Employee) print(emps.all()) # 返回列表,查不到返回空列表
取首行 : first方法本质上就是limit语句
emps = session.query(Employee) print(emps.limit(1).one()) #返回一行 print(emps.one()) #如果查询结果是多行抛异常
删除
# 删除 delete by query session.query(Employee).filter(Employee.emp_no > 10018).delete() #session.commit() # 提交则删除
聚合,分组
聚合
from sqlalchemy import func query = session.query(func.count(Employee.emp_no)) print(query.one()) # 只能有一行结果 print(query.scalar()) # 取one()返回元组的第一个元素
max,min,avg
print(session.query(func.max(Employee.emp_no)).scalar()) print(session.query(func.min(Employee.emp_no)).scalar()) print(session.query(func.avg(Employee.emp_no)).scalar())
分组
print(session.query(Employee.gender,func.count(Employee.emp_no)).group_by(Employee.gender).all())
关联查询
有两张表,其中的对象多对多关联,就要建立第三张表。
如果是一对多关联,就在多的一端建立外键。
有一个员工,即属于A部门,又属于B部门,同时每个部门都有许多员工,这就是多对多
先把这些表的Model类和字段属性建立起来。
class Employee(Base): # 指定表名 __tablename__ = 'employees' # 定义属性对应字段 emp_no = Column(Integer, primary_key=True) birth_date = Column(Date, nullable=False) first_name = Column(String(14), nullable=False) last_name = Column(String(16), nullable=False) gender = Column(Enum(GenderEnum), nullable=False) hire_date = Column(Date, nullable=False) # 第一参数是字段名,如果和属性名不一致,一定要指定 # age = Column('age', Integer) def __repr__(self): return "{} no={} name={} {} gender={}".format( self.__class__.__name__, self.emp_no, self.first_name, self.last_name, self.gender.value ) class Department(Base): __tablename__ = 'departments' dept_no = Column(String(4), primary_key=True) dept_name = Column(String(40), nullable=False, unique=True) def __repr__(self): return "{} no={} name={}".format(type(self).__name__, self.dept_no, self.dept_name) class Dept_emp(Base): __tablename__ = "dept_emp" emp_no = Column(Integer, ForeignKey('employees.emp_no', ondelete='CASCADE'), primary_key=True) dept_no = Column(String(4), ForeignKey('departments.dept_no', ondelete='CASCADE'), primary_key=True) from_date = Column(Date, nullable=False) to_date = Column(Date, nullable=False) def __repr__(self): return "{} empno={} deptno={}".format(type(self).__name__, self.emp_no, self.dept_no)
查询10010员工的所在部门和标号信息
results = session.query(Employee,Dept_emp).filter((Employee.emp_no == Dept_emp.emp_no) & (Employee.emp_no == 10010)).all()
show(results)
这种方式产生隐式连接的语句
使用join
results = session.query(Employee).join(Dept_emp).filter(Employee.emp_no == 10010).all()
results = session.query(Employee).join(Dept_emp, Employee.emp_no == Dept_emp.emp_no).filter(Employee.emp_no == 10010).all() print(results)
返回的都只有一行数据。它们生成的SQL语句是一样的,执行该SQL语句返回确实是2行记录,可以Python中的返回值列表中只有一个元素。
原因在于 query(Employee) 这个只能返回一个实体对象中去,为了解决这个问题,需要修改实体类Employee,增加属性用来存放部门信息
sqlalchemy.orm.relationship(实体类名字符串)
from sqlachemy import relationship class Employee(Base): # 指定表名 __tablename__ = 'employees' # 定义属性对应字段 emp_no = Column(Integer, primary_key=True) birth_date = Column(Date, nullable=False) first_name = Column(String(14), nullable=False) last_name = Column(String(16), nullable=False) gender = Column(Enum(GenderEnum), nullable=False) hire_date = Column(Date, nullable=False) # 第一参数是字段名,如果和属性名不一致,一定要指定 # age = Column('age', Integer) departments = relationship("Dept_emp") def __repr__(self): return "{} no={} name={} {} gender={} depts={}".format( self.__class__.__name__, self.emp_no, self.first_name, self.last_name, self.gender.value,self.departments )
查询
# 第一种 # results = session.query(Employee).join(Dept_emp).filter(Employee.emp_no == Dept_emp.emp_no).filter(Employee.emp_no == 10010) # 第二种 # results = session.query(Employee).join(Dept_emp, Employee.emp_no == Dept_emp.emp_no).filter(Employee.emp_no == 10010) # 第三种 results = session.query(Employee).join(Dept_emp, (Employee.emp_no == Dept_emp.emp_no) & (Employee.emp_no == 10010)) show(results.all())
第一种方法join(Dept_emp)中没有等值条件,会自动生成一个等值条件,如果后面有filter,哪怕是filter(Employee.emp_no == Dept_emp.emp_no),这个条件会在where中出现。第一种这种自动增加join的等值条件的方式不好,不要这么写
第二种方法在join中增加等值条件,阻止了自动的等值条件的生成。这种方式推荐
第三种方法就是第二种,这种方式也可以
只要不访问departments属性,就不会查dept_emp这张表。
总结
在开发中,一般都会采用ORM框架,这样就可以使用对象操作表了。
定义表映射的类,使用Column的描述器定义类属性,使用ForeignKey来定义外键约束。
如果在一个对象中,想查看其它表对应的对象的内容,就要使用relationship来定义关系。
是否使用外键约束?
1、力挺派
能使数据保证完整性一致性
2、嫌弃派
开发难度增加,大数据的时候影响插入、修改、删除的效率。
在业务层保证数据的一致性。