ORM,对象关系映射,对象和关系之间的映射,使用面向对象的方式来操作数据库。
关系模型和Python对象之间的映射
table => class ,表映射为类
row => object ,行映射为实例
column => property ,字段映射为属性
SQLAlchemy是一个ORM框架
pip install sqlalchemy
官方文档
(官方文档http://docs.sqlalchemy.org/en/latest/)[http://docs.sqlalchemy.org/en/latest/]
SQLAlchemy内部使用了连接池
dialect+driver://username:password@host:port/database
mysqldb的连接
mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
engine = sqlalchemy.create_engine("mysql+mysqldb://wayne:[email protected]:3306/magedu")
pymysql的连接
mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
engine = sqlalchemy.create_engine("mysql+pymysql://wayne:[email protected]:3306/magedu")
engine = sqlalchemy.create_engine("mysql+pymysql://wayne:[email protected]:3306/magedu",
echo=True)
echo=True
引擎是否打印执行的语句,调试的时候打开很方便。
lazy connecting:懒连接。创建引擎并不会马上连接数据库,直到让数据库执行任务时才连接。
举例:
import sqlalchemy
from sqlalchemy import create_engine, Column, String, Integer,Enum,Date,ForeignKey # 导入外键
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker,relationships
HOST = '172.22.141.122'
USERNAME = 'eric01'
PASSWORD = 'eric01'
DBNAME = 'test'
PORT = 3306
engine = create_engine("mysql+pymysql://{}:{}@{}:{}/{}".format(USERNAME, PASSWORD, HOST, PORT, DBNAME),
echo=True) # echo =True ,后期会将语句报保存到日志
print(engine)
# ORM Maping
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)
def __repr__(self):
return "<{} id = {}, name = {},age = {}>" .format(
__class__.__name__, self.id, self.name,self.age)
# 创建后将不需要在执行下面的语句
# Base.metadata.drop_all(engine) # 删除base 所管理的所有mapping类
# Base.metadata.create_all(engine)
from sqlalchemy.orm.session import Session
session:Session = sessionmaker(bind = engine)() # 实例化
print(session,type(session))
student = Student(name= 'tom' )
student.name= 'jerry'
student.age = 20
print( student)
session.add(student) # 将student 加入到session 中
session.commit() #提交
# ##########方法二:
try:
try:
session.add_all([student])
session.commit()
print(' ~~~~~~~~~~')
except:
session.rollback()
print('++++++++++++')
### add_all()方法不会提交成功的,不是因为它不对,而是student,student成功提交后,sudent的主键就有了值,所以,只要student没有修改过,就认为没有改动。如下,student变化了,就可以提交修改了。
生产环境很少这样创建表,都是系统上线的时候由脚本生成。
生产环境很少删除表,宁可废弃都不能删除。
在一个会话中操作数据库,会话建立在连接上,连接被引擎管理。
当第一次使用数据库时,从引擎维护的连接池中获取一个连接使用。
# 创建session
Session = sessionmaker(bind = engine)
session = Session() # 实例化
students = session.query(Student) # 并没有真的查询,只是返回个查询集,是懒查询
print(students)
print(students.count())# 可以查询个数,但这是个子查询
for x in students:
print(x)
student = session.query(Student).get(2) # 改的方式是先查数据库
print(student)
student.name = 'sam'
student.age = 30
print(student)
session.add(student)
session.commit()
先查回来,修改后,在提交更改
student = Student(id=2, name="sam", age=30)
session.delete(student)
# 不能直接删除,哪怕是一样是数据
所以得用先查后删
状态**
每一个实体,都有一个状态属性_sa_instance_state,其类型是sqlalchemy.orm.state.InstanceState,可以使用
sqlalchemy.inspect(entity)函数查看状态。
常见的状态值有transient、pending、persistent、deleted、detached
# 查看状态信息
from sqlalchemy.orm.state import InstanceState
def getstate(instance,i):
state:InstanceState = sqlalchemy.inspect(instance)
output = "{}:{} {}\n" \
"attached = {},transient= {},dateched = {}\n" \
"persistent= {},deleted = {},detached = {}\n".format(
i,state.key,state.session_id,
state._attached,state.transient,state.pending,
state.persistent,state.deleted,state.detached)
print(output,end = '~~~~~~~~~~~~~\n')
student = session.query(Student).get(2)
getstate(student, 1) # persistent
try:
student = Student(id=2, name='sam', age=30)
getstate(student, 2) # transit
student = Student(name='sammy', age=30)
getstate(student, 3) # transient
session.add(student) # add后变成pending
getstate(student, 4) # pending
# session.delete(student) # 异常,删除的前提必须是persistent,也就是说先查后删
# getstate(student, 5)
session.commit() # 提交后,变成persistent
getstate(student, 6) # persistent
except Exception as e:
session.rollback()
print(e, '~~~~~~~~~~~~~~~~')
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Date, Enum, ForeignKey, create_engine
from sqlalchemy.orm import sessionmaker
import enum
Base = declarative_base()
HOST = '172.22.141.122'
USERNAME = 'eric01'
PASSWORD = 'eric01'
DBNAME = 'test'
PORT = 3306
engine = create_engine("mysql+pymysql://{}:{}@{}:{}/{}".format(USERNAME, PASSWORD, HOST, PORT, DBNAME),
echo=True) # echo =True ,后期会将语句报保存到日志
Session = sessionmaker(bind=engine)
session = Session()
class MyEnum(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(MyEnum), nullable=False)
hire_date = Column(Date, nullable=False)
def __repr__(self):
return "{} no={} name={} {} ".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)
print('~~~~~~~~~~~~~~~\n')
emps = session.query(Employee)
show(emps)
print(emps.count())
# 简单查询
emps = session.query(Employee).filter(Employee.emp_no > 10015)
简单查询
与或非
# 与方法 ##############################
# 方法一:
emps = session.query(Employee).filter(Employee.emp_no > 10015 and Employee.emp_no< 10020)
# 方法二:
emps = session.query(Employee).filter(Employee.emp_no > 10015).filter( Employee.emp_no< 10020)
# 方法三:
from sqlalchemy import or_, and_, not_
emps = session.query(Employee).filter(and_ (Employee.emp_no > 10015,Employee.emp_no< 10018))
# 方法四:
emps = session.query(Employee).filter((Employee.emp_no > 10015) & (Employee.emp_no< 10020))
# 或方法##############################:
# 方法一:
emps = session.query(Employee).filter(or_ (Employee.emp_no == 10015,Employee.emp_no ==10018))
# 方法二
emps = session.query(Employee).filter((Employee.emp_no > 10015) | (Employee.emp_no< 10020))
# 非操作################################
emps = session.query(Employee).filter(~(Employee.emp_no > 10015)) # 非操作
emps = session.query(Employee).filter(not(Employee.emp_no > 10015))
# in 操作
emps = session.query(Employee).filter(Employee.emp_no in [10001,10002,10003])
# 模糊匹配
emps = session.query(Employee).filter(Employee.last_name.like('M%''))
排序
# 排序
# 升序
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()) # 默认都是升序
# 降序
emps = session.query(Employee).filter(Employee.emp_no > 10010).order_by(Employee.emp_no.desc()) # 降序
# 多列排序
emps = session.query(Employee).filter(Employee.emp_no >
10010).order_by(Employee.last_name).order_by(Employee.emp_no.desc()) # 先按last_name 排名,若相同按员工号排序
分页 --Limit
emps = session.query(Employee).limit(4)
emps = session.query(Employee).limit(4).offset(18)
消费者方法
消费者方法调用后,Query对象(可迭代)就转换成了一个容器
# 总行数
emps = session.query(Employee)
print(len(list(emps))) # 查询得到结果集,转成list,然后取长度
print(emps.count()) # 聚合函数coun让tt(*)的查询
# 取所有数据
print(emps.all()) # 返回列表,查不到返回空列表
# 取首行
print(emps.first()) # 返回首行,查不到返回None,等价limit
# 有且只能有一行
#print(emps.one()) #如果查询结果是多行抛异常,少于一行也抛异常
# 所以只要是一行的时候才可以使用
print(emps.limit(1).one())
# 删除 delete by query
session.query(Employee).filter(Employee.emp_no > 10018).delete()
#session.commit() # 提交则删除
消费者方法查找时建议使用emps.first(),不建议使用emps.one() ,因为first 不会报错,查不到只是返回None,但是 one()查不到会报错
first 方法本质上就是limit语句
聚合,分组
# 方法一.
print(emps.count()) # 这是用的子查询得到的个数
# 方法二
from sqlalchemy import func
query = session.query(func.count(Employee.emp_no))
print (query)
>>>
# SELECT count(employees.emp_no) AS count_1
# FROM employees
# 可以看出sql 语句是真发count ,不存在子查询
print(query.all()) # 列表中一个元素,该元素是元组
print(query.first()) # 一个只有一个元素的元组
print(query.one()) # 只能有一行返回,一个元组
print(query.scalar()) # 取one()的第一个元素
# 与 query.one()[0]
# 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())
# 分组
query = session.query(Employee.gender,
func.count(Employee.emp_no)).group_by(Employee.gender).all()
for g,y in query:
print(g.value, y)
关联查询
import sqlalchemy
from sqlalchemy import create_engine, Column, String, Integer,Enum,Date,ForeignKey # 导入外键
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker,relationships
import enum # 枚举类
HOST = '172.22.141.122'
USERNAME = 'eric01'
PASSWORD = 'eric01'
DBNAME = 'test'
PORT = 3306
engine = create_engine("mysql+pymysql://{}:{}@{}:{}/{}".format(USERNAME, PASSWORD, HOST, PORT, DBNAME),
echo=True) # echo =True ,后期会将语句报保存到日志
# print(engine)
# ORM Maping
Base = declarative_base()
# from sqlalchemy.orm.session import Session
# session:Session = sessionmaker(bind = engine)()
class GenderEnum(enum.Enum): # 麻烦,直接使用0,1,2数字类型代替即可
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
)
class Department(Base):
__tablename__ = 'departments'
dept_no = Column(String(4),primary_key= True)
dept_name = Column(String(4),nullable= False,unique=True)
def __repr__(self):
return "<{},{},{}>".format(
__class__.__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)
# ondelete = 'CASCADE ' 设置级联,确定主副表之间删除的规则
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(__class__.__name__, self.emp_no, self.dept_no)
需求
查询10010员工的所在的部门编号及员工信息
1、使用隐式内连接
sql 语句
SELECT
*
FROM
employees,
dept_emp
WHERE
employees.emp_no = dept_emp.emp_no
AND employees.emp_no = 10010
python 代码:
emps = session.query(Employee,Dept_emp).filter(Employee.emp_no==Dept_emp.emp_no).filter(Employee.emp_no == 10010)
# 查询结果2行
(Employee no=10010 name=Duangkaew Piveteau gender=F, Dept_emp empno=10010 deptno=d004)
(Employee no=10010 name=Duangkaew Piveteau gender=F, Dept_emp empno=10010 deptno=d006)
2,使用Join
# emps = 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).filter(Employee.emp_no == 10010)
emps= session.query(Employee).join(Dept_emp, (Employee.emp_no == Dept_emp.emp_no) & (Employee.emp_no == 10010))
这两种写法,返回都只有一行数据,为什么?
它们生成的SQL语句是一样的,执行该SQL语句返回确实是2行记录,可是Python中的返回值列表中只有一个元素?
原因在于 query(Employee) 这个只能返回一个实体对象中去,为了解决这个问题,需要修改实体类Employee,
增加属性用来存放部门信息
# emps= session.query(Employee.emp_no,Dept_emp.emp_no).join(Dept_emp, (Employee.emp_no == Dept_emp.emp_no) & (Employee.emp_no == 10010))
# 将两个表插入查询,这里就能返回员工号和 部门号的二元组
x = emps.first()
print(type(x),x.emp_no) # 这里得到的是一个employees的对象,通过实例操作就可以得到员工号
sqlalchemy.orm.relationship(实体类名字符串)
# 修改Employees类的代码;
departments = relationship('Dept_emp') # 增加关联信息
def __repr__(self):
return "<{} no={} name={} {} gender={} dept = {}>".format(
self.__class__.__name__, self.emp_no, self.first_name, self.last_name,
self.gender.value,self.departments # 实例调用
)
emps= session.query(Employee).filter(Employee.emp_no == 10010)
x = emps.first()
print(x)
# 返回结果:
<Employee no=10010 name=Duangkaew Piveteau gender=F dept = [<Dept_emp empno=10010 deptno = d004>, <Dept_emp empno=10010 deptno = d006>]>
总结:
在开发中一般都会采用ORM 框架,这样就可以使用对象操作了.
定义表映射的类,使用Columm的描述器定义类属性,使用ForeignKey来定义外键约束。
如果在一个对象中,想查看其它表对应的对象的内容,就要使用relationship来定义关系。