Python学习之 --- 数据库编程之 ORM+sqlalchemy 库使用

ORM

ORM,对象关系映射,对象和关系之间的映射,使用面向对象的方式来操作数据库。

关系模型和Python对象之间的映射
table => class ,表映射为类
row => object ,行映射为实例
column => property ,字段映射为属性

SQLAlchemy


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 
Session = sessionmaker(bind = engine)
session = Session() # 实例化

第三步: 数据库操作

1> 查询

students = session.query(Student) # 并没有真的查询,只是返回个查询集,是懒查询
print(students) 
print(students.count())#  可以查询个数,但这是个子查询
for x in students:
    print(x)

2> 改数据

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, '~~~~~~~~~~~~~~~~')

Python学习之 --- 数据库编程之 ORM+sqlalchemy 库使用_第1张图片

复杂查询


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来定义关系。

你可能感兴趣的:(数据库入门,Mysql)