表映射定义

表的映射定义有两种模式:声明式定义(SQLAlchemy ORM)和经典式定义(SQLAlchemy Core)。

声明式定义

声明式定义的表映射需要继承一个基类,这个基类是固定的,可以使用以下语句获得。

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

之后就可以用来定义映射类了。

from sqlalchemy import Column, String, Integer

class User(Base):
	__tablename__ = 'user'
	
	id = Column(String(200), primary_key=True, nullable=False)
	name = Column(String(200), nullable=False)
	password = Column('pass', String(50), nullable=False)
	age = Column(Integer)

Column()的第一个参数是指定要映射的数据表列名,这里可以允许映射类的属性与数据表列名不一致。如果不指定列名,则会使用映射类属性名作为数据表列名。

映射类中还可以存在一个__mapper_args__属性,这个属性的类型是一个字典,用以对映射进行配置。这个属性常用的配置项有:

  • column_prefix,指定列名前缀。
  • include_properties,列表类型,用来指定要映射的属性。
  • exclude)properties,列表类型,用来指定不进行映射的属性。
  • primary_key,列表类型,用来指定表的主键列。
  • polymorphic_identity,字符串类型,用来指定区分值,用于单表存储按照类型区分的多个映射类,每个子类都需要指定。
  • polymorphic_on,Column类型,用来指定用于区分多个映射的列,只需要在基类中指定。

经典式定义

经典式定义的表映射则是需要将表描述与映射类手工绑定,上一节的映射示例使用经典式定义则是下面这样的景象。

from sqlalchemy import Table, MetaData, Column, Integer, String
from sqlalchemy.orm import mapper

metadata = MetaData()

user = Table('user', metadata, 
	Column('id', String(200), primary_key=True, nullable=False),
	Column('name', String(200), nullable=False),
	Column('pass', String(50), nullable=False),
	Column('age', Integer)
)

class User(object):
	def __init__(self, id, name, pass, age):
		self.id = id
		self.name = name
		self.password = pass
		self.age = age

mapper(User, user)

在表映射定义上,声明式定义已经简洁了很多,这里就不再对经典式定义过多介绍了,后文也主要以声明式定义为主。

Warning

注意,如果仅使用Table()来定义模型,不将其与一个类建立映射关系,则是SQLAlchemy Core的定义方法,这时需要采用SQLAlchemy Core的查询方法,不能采用SQLAlchemy ORM的查询方法。两种查询方法不能通用。

计算列

计算列有两种定义方式,一种是使用修饰器,一种是使用column_property

以下使用修饰器定义了一个用户表示字段。

from sqlalchemy.ext.hybrid import hybrid_property

class User(Base):
	__tablename__ = 'user'
	
	id = Column(String(200), primary_key=True, nullable=False)
	name = Column(String(200), nullable=False)
	password = Column('pass', String(50), nullable=False)
	age = Column(Integer)
	
	@hybrid_property
	def standardname(self):
		return "[{}]{}".format(self.id, self.name)

而使用column_property则更加简单一些。

from sqlalchemy.orm import column_property

class User(Base):
	__tablename__ = 'user'
	
	id = Column(String(200), primary_key=True, nullable=False)
	name = Column(String(200), nullable=False)
	password = Column('pass', String(50), nullable=False)
	age = Column(Integer)
	standardname = column_property("[{}]{}".format(id, name))

一对多映射

SQLAlchemy中的关联映射并不复杂,只需要逐项定义关联项即可。前面给出的两个表,即是一个一对多的经典关联,下面就利用这两个表来演示一对多映射的定义方法。

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
	__tablename__ = 'user'
	
	id = Column(String(200), primary_key=True, nullable=False)
	name = Column(String(200), nullable=False)
	password = Column('pass', String(50), nullable=False)
	age = Column(Integer)
	roles = relationship("Role")

class Role(Base):
	__tablename__ = 'role'
	
	id = Column(Integer, primary_key=True, nullable=False),
	user_id = Column(String(200), nullable=False, ForeignKey('user.id'))
	role = Column(String(20), nullable=False)

使用relationship()可以定义指定属性为关联属性,建立单向的关联关系,示例中使用back_populates参数将关联关系指向了对向的属性,从而就建立了双向关联关系。这个参数可以根据需要选择使用。当relationship()只关联单一的字段时,可以使用backref参数来指定逆向关联,该关联是在内存中实现的,使用backref可以不必在被映射的对方定义关联字段。

ForeignKey()函数给映射列指出了其参考的外键列,外键列使用表名.列名的方式表示。外键列不需要在数据库中声明为外键,这里只需要虚拟的设定外键即可。

在一对多映射中,外键一般都放置在子表中,来与主表建立关联。relationship()放置在主表来代表子表的实例集合。

对于关联常用的级联操作,可以使用cascade参数在relationship()函数中定义,需要级联的功能使用英文逗号隔开连接成字符串即可,例如cascade=""save-update, merge, delete"。对于使用backref定义的逆向关联,可以使用cascade_backrefs来定义是否使用逆向级联操作。

常用的级联操作有:

  • save-update
  • delete
  • delete-orphan
  • merge
  • refresh-expire
  • expunge

多对一映射

其实在形成一对多映射时,子表对于主表的关联就可以同时形成多对一的映射关系。多对一的映射通常将外键设置在主表中,relationship()也在主表声明,来定义对子表的关联。

同样使用back_populates可以建立双向的关联,这与一对多的关系是相同的。

一对一映射

realtionship()默认会创建使用列表的“多”映射,如果是一对一映射,就需要在不需要出现列表的一方的relationship()中使用uselist参数来将其转换成单一属性。

也就是说,谁那一方的属性应该是单数,就在谁的relationship()中添加uselist=False参数。

多对多映射

多对多映射通常会用到中间表来定义关联关系,这在映射定义时,就需要在relationship()中定义secondary参数。例如以下关联映射的定义:

association_table = Table('association', Base.metadata,
	Column('left', Integer, ForeignKey('left.id')),
	Column('right', Integer, ForeignKey('right.id'))
)

class Left(Base):
	__tablename__ = 'left'
	
	id = Column(Integer, primary_key=True)
	right = relationship('Right', secondary=association_table, backref="left")

class Right(Base):
	__tablename__ = 'right'
	
	id = Column(Integer, primary_key=True)

其中Left中的secondary参数指定了多对多映射使用的中间表,并且使用了backrefRight中定义了映射字段left来完成双向映射。

当中间表中还有其他要使用的字段时,可以将中间表也定义为一个类,此时上例就变成了下面这样。

class Association(Base):
	__tablename__  = 'association_table'
	
	left_id = Column('left', Integer, ForeignKey('left.id'), primary_key=True)
	right_id = Column('right', Integer, ForeignKey('right.id'), primary_key=True)
	extra_data = Column(String(200))
	right = realationship('Right')

class Left(Base):
	__tablename__ = 'left'
	
	id = Column(Integer, primary_key=True)
	right = realtionship('Association')

class Right(Base):
	__tablename__ = 'right'
	
	id = Column(Integer, primary_key=True)

自关联映射

在数据表设计的时候,经常会使用数据表自己与自己关联来产生级联数据,这种情况下就不能使用前面的关联方法。如果使用前面的一对多的映射方法,就会得到一个异常。这时就需要relationship()函数的另一个参数remote_side来定义循环映射主键。

class Node(Base):
	__tablename__ = 'node'
	
	id = Column(Integer, primary_key=True),
	parent_id = Column(Integer, ForeignKey('node.id'))
	data = Column(String(100))
	children = relationship('Node', remote_side=[id])

示例中定义了一个单向的映射,如果需要改为双向映射,只需要将关联映射一句改为children = relationship('Node', backref=backref('parent', remote_side=[id]))

如果存在多个键的复杂自关联映射,则需要将关联键都列举在remote_side中。

自定义关联条件

在默认情况下,关联条件都是采用相等的处理方式。但是个别情况下可能会使用其他的关联方式,比如like或者是数组包含(PostgreSQL支持的操作)等。在这种情况下可以使用relationship()primaryjoin参数来指定关联条件。

primaryjoin接受一个布尔表达式。

from sqlalchemy.orm import foreign, remote

class Host(Base):
	__tablename__ = 'host'
	
	id = Column(Integer, primary_key=True)
	ip_address = COlumn(INET)
	content = Column(String(50))
	
	parent_host = relationship('Host', primaryjoin=remote(ip_address) == cast(foreign(content), INET))

如果是使用PostgreSQL的特殊比较操作符,则需要使用op方法,格式为属性名.op('操作符', is_comparision=True)(外表字段)。例如:

class IPA(Base):
	__tablename__ = 'ip_address'
	
	id = Column(Integer, primary_key=True)
	v4address = Column(INET)
	
	network = relationship('Network', 
		primaryjoin="IPA.v4address.op('<<', is_comparision=True)(foreign(Network.v4representation))",
		viewonly=True
	)

class Network(Base):
	__tablename__ = 'network'
	
	id = Column(Integer, primary_key=True)
	v4representation = Column(CIDR)