
美好的一天
今天我想谈谈ORM SQLAlchemy。 让我们谈谈它的功能和灵活性是什么,并考虑未总是清楚描述的情况。
这个ORM的入门门槛高于平均水平,因此我将尝试用一种简单的语言和示例来解释所有内容。 对于已经使用sqlalchemy并想要提高其技能或只是熟悉该库的人员,本文将非常有用。
使用的编程语言是python 3.6。
DB-PostgreSQL。
Github链接
那么,ORM是什么?
ORM(对象关系映射)是一项允许您映射类型不兼容的模型的技术。 例如:数据库表和编程语言对象。
换句话说,您可以访问类对象以管理数据库表中的数据。 您还可以创建,修改,删除,过滤,最重要的是,继承映射到数据库表的类对象,这大大减少了代码库的内容。
要使用SQLAlchemy,您需要了解它的工作方式。
使用Django-ORM的开发人员将不得不重新构建思维方式来创建ORM查询。 在我看来,SQLAlchemy是一个功能怪兽,您可以并且应该使用其功能,但是您需要了解ORM并不总是完美的。 因此,我们将讨论建议使用此技术的时刻。
SQLAlchemy具有声明性和非声明性模型定义的概念。
非声明性定义意味着使用了mapper(),它描述了每个数据库列和模型类的映射。
本文使用模型的声明性定义。
这里更多
数据库结构
为了实现完全的数据一致性,让我们创建下表。
基本模型用于确定数据库中的基本列。
class BaseModel(Base): __abstract__ = True id = Column(Integer, nullable=False, unique=True, primary_key=True, autoincrement=True) created_at = Column(TIMESTAMP, nullable=False) updated_at = Column(TIMESTAMP, nullable=False) def __repr__(self): return "<{0.__class__.__name__}(id={0.id!r})>".format(self)
员工-描述在办公室工作的员工的表
class Employee(BaseModel): __tablename__ = 'employees' first_name = Column(VARCHAR(255), nullable=False) last_name = Column(VARCHAR(255), nullable=False) phone = Column(VARCHAR(255), unique=True, nullable=True) description = Column(VARCHAR(255), nullable=True)
EmployeeWithSkills不是一个表。 该类继承自Employee。 分离逻辑并像使用单独的表一样使用类的绝佳机会。
class EmployeeWithSkills(Employee): skills = relation(Skill, secondary=EmployeesSkills.__tablename__, lazy='joined')
部门-该员工所在的部门。 一个人可以包含多个部门。
class Department(BaseModel): __tablename__ = 'departments' name = Column(VARCHAR(255), nullable=False) description = Column(VARCHAR(255), nullable=False)
员工及其所属单位的对应表。
class EmployeeDepartments(BaseModel): __tablename__ = 'employee_departments' employee_id = Column(Integer, ForeignKey('employees.id', ondelete='CASCADE'), nullable=False, index=True) department_id = Column(Integer, ForeignKey('departments.id', ondelete='CASCADE'), nullable=False, index=True)
员工及其技能的对应表。
class EmployeesSkills(BaseModel): __tablename__ = 'employees_skills' employee_id = Column(ForeignKey('employee.id', ondelete='CASCADE'), nullable=False, index=True) skill_id = Column(ForeignKey('skills.id', ondelete='CASCADE'), nullable=False, index=True)
我们使用Alembic软件包创建迁移,该迁移使您可以自动生成迁移。 在本课程中,自动生成迁移是完全可以接受的。
最新的迁移包含将填充数据库的测试数据。
如何配置Alembic可以在这里阅读
我们进行珍贵的紫苏升级头来进行迁移。
请求和关系
让我们发出第一个请求,并通过其ID获取有关该员工的信息。
该请求将如下所示:
第一课:
employee = session.query(Employee).filter(Employee.id == eid).one() output: ID: 2, Tony Stark
.one()
表示我们仅打算获得一个条目。 如果有多个条目,将引发适当的例外。
如果要获取所有可用部门,则可以使用.all()
使用以下查询
第二课:
emmployee = session.query(Department).all() output: ID: 2, name: Guards ID: 4, name: Legions
考虑使用聚合功能。
我们可以使用内置功能获得可用的部门数量。
.count()
或使用func.count()
。 使用第二种方法,您可以使用select
或计算中间结果来访问任何SQL函数。
第三课:
def get_departments_count(session: DBSession) -> int: count = session.query(Department).count() return count def get_departments_func_count(session: DBSession) -> int: count = session.query(func.count(Department.id)).scalar() return count
许多开发人员使用count()
函数检查请求中的数据。 这不是一个好习惯,这会导致使用额外的数据库资源并增加查询执行时间。 一个好的解决方案是使用返回一个标量值的exists()
函数:
第三课:
def check_department_exists(session: DBSession, department_name: str) -> bool: is_exists = session.query(exists().where(Department.name == department_name)).scalar() return is_exists
继续前进,我们使任务复杂化并熟悉实体relation
或relationship
。 事实是,在SQLAlchemy
除了使用foreign_key
在数据库级别,还使用对象之间的关系。
因此,我们可以获得依赖于对象中外键的数据库行。
这些对象是相互关联的数据库表上的投影。
SQLAlchemy
中的Relations
具有灵活的配置,允许您使用命名参数lazy
在不同的时间以不同的方式从数据库获取数据。
“懒惰”的主要程度:
select
是默认设置。 ORM仅在访问数据时发出请求。 它是在单独的请求中执行的。dynamic
-允许您获取请求对象,可以根据需要对其进行修改。 仅在调用all()或一个()或任何其他可用方法之后,它才从数据库接收数据。- join-使用LEFT JOIN添加到主请求中。 立即执行。
subquery
-与选择类似,但作为子查询执行。
默认值为select
。
查询中的过滤可以是静态的也可以是动态的。 动态过滤允许使用过滤器填充请求,过滤器可能会根据功能的进度而有所不同。
第4课:
def dynamic_filter(session: DBSession, filter: DFilter = None): query = session.query(Employee) if filter is not None: query = query.filter(*filter.conds) employees = query.all() return employees
DFilter过滤器类基于任何输入定义过滤器。 如果定义了过滤器类,则在请求中进一步适用条件。
.filter()函数接受接受SQLAlchemy二进制条件,因此可以使用*表示
动态过滤器的使用仅受想象力的限制。 查询结果显示哪些英雄当前处于非活动状态。
output: Inactive_heros: Name: Tony Stark Name: Scott Lang Name: Peter Parker
我建议使用多对多关系。
我们有一个Employee表,其中与EmployeesSkills对应表有关系。 它在雇员表上包含foreign_key和foreign_key
到技能表。
第5课:
def get_employee_with_skills(session: DBSession, eid: int): employee = session.query(EmployeeWithSkills).filter(EmployeeWithSkills.id == eid).one() return employee output: Employee Tony Stark has skills: Skill: Fly, Desc: I belive I can Fly. I belive I can touch the sky Skill: Light Shield, Desc: Light protect. Perfect for everything
在上面的查询中使用EmployeeWithSkills类,我们将其称为数据库表,但实际上这种表不存在。 该类不同于具有关系的Employee,后者具有多对多关系。 因此,我们可以区分类的逻辑,并用一组不同的关系填充它。 根据要求,我们将看到一名员工的技能。
由于该员工可以在多个部门中,请创建一个关系以使您可以获取此信息。
创建从Employee继承的EmployeeWithDepartments类,并添加以下内容:
class EmployeeWithDepartments(Employee): departments = relation( Department, # primaryjoin=EmployeeDepartments.employee_id == Employee.id, secondary=EmployeeDepartments.__tablename__, # secondaryjoin=EmployeeDepartments.department_id == Department.id, )
创建的类不是新的数据库表。 这仍然是相同的Employee表,仅使用relation
扩展。 这样,您可以在查询中访问Employee
或EmployeeWithDepartments
表。 区别只会在于没有/存在relation
。
第一个参数指示我们将在哪个表上创建relation
。
primaryjoin
是连接第二张表之前将其连接到对象的条件。
secondary
是包含要匹配的foreign_keys的表的名称。 在多对多的情况下使用。
secondaryjoin
将中间表与最后一个表匹配的条件。
primaryjoin
和secondaryjoin
用于明确指示复杂情况下的对应关系。
有时会出现这样的情况:需要创建其字段在关系中声明的过滤器,而关系又是原始类的关系。
EmployeeWithCadreMovements -> relation(CadreMovement) -> field
如果关系显示值列表,则必须使用.any(),如果仅提供一个值,则必须使用.has()
为了更好地理解,此构造将以SQL语言解释为exist()构造。
我们使用带有reason参数参数的get函数进行调用,例如simple
。
第6课
def has_in_relations(session: DBSession, reason: str): employees = session.query(EmployeeWithCadreMovements).filter(EmployeeWithCadreMovements.cadre_movements.any(CadreMovement.reason == reason)).all() return employees output: [Steve Rogers, Tony Stark]
课7
考虑使用聚合函数获得关系的可能性。 例如,我们得到特定用户的最后人员移动。
primaryjoin是连接表的条件(如果使用lazy ='joined')。 回想一下,默认情况下使用select。
在这种情况下,访问class属性时会生成一个单独的请求。 我们可以为此请求指定过滤条件。
如您所知,您不能在WHERE条件下以“纯”形式使用聚合函数,因此我们可以通过指定关联来实现此功能
具有以下参数:
last_cadre_movement = relation( CadreMovement, primaryjoin=and_( CadreMovement.employee == Employee.id, uselist=False, CadreMovement.id == select([func.max(CadreMovement.id)]).where(CadreMovement.employee == Employee.id) ) )
执行后,请求的编译如下:
SELECT cadre_movements.id AS cadre_movements_id, cadre_movements.created_at AS cadre_movements_created_at, cadre_movements.updated_at AS cadre_movements_updated_at, cadre_movements.employee AS cadre_movements_employee, cadre_movements.old_department AS cadre_movements_old_department, cadre_movements.new_department AS cadre_movements_new_department, cadre_movements.reason AS cadre_movements_reason FROM cadre_movements WHERE cadre_movements.employee = %(param_1)s AND cadre_movements.id = ( SELECT max(cadre_movements.id) AS max_1 FROM cadre_movements WHERE cadre_movements.employee = %(param_1)s )
Github链接
总结
SQLAlchemy是功能强大的查询构建工具,可通过支持继承来缩短开发时间。
但是您应该在使用ORM和编写复杂查询之间保持良好的界限。 在某些情况下,ORM可能会使开发人员感到困惑或使代码繁琐且难以阅读。
祝你好运