У меня есть решение, которое я считаю эффективным и которое позволит вам легко создавать свойства только для чтения на основе комбинаций атрибутов Child
, используя set
в качестве отношения collection_class
.
Вы можете использовать идиому Alternate Join Condition , чтобы создавать атрибуты отношений, которые являются подмножествами набора дочерних элементов Parent
, например, вот отношение, которое вернет все женскиеchildren:
female_children = sa.orm.relationship(
'Child', collection_class=set,
primaryjoin='and_(Parent.id == Child.parent_id, Child.sex == "female")'
)
Я не буду объяснять это слишком сильно, поскольку оно очень похоже на документированные примеры, которые были связаны с выше, за исключением примечания к спецификации collection_class=set
, важность которой будетстанет очевидным позже.
Я продолжал создавать похожие отношения для каждой возможной категоризации Child
, опять же, для всех collection_class=set
:
class Parent(Base):
id = sa.Column(sa.Integer, primary_key=True)
children = sa.orm.relationship('Child', collection_class=set)
male_children = sa.orm.relationship(
'Child', collection_class=set,
primaryjoin='and_(Parent.id == Child.parent_id, Child.sex == "male")'
)
female_children = sa.orm.relationship(
'Child', collection_class=set,
primaryjoin='and_(Parent.id == Child.parent_id, Child.sex == "female")'
)
working_children = sa.orm.relationship(
'Child', collection_class=set,
primaryjoin='and_(Parent.id == Child.parent_id, Child.is_working == True)'
)
married_children = sa.orm.relationship(
'Child', collection_class=set,
primaryjoin='and_(Parent.id == Child.parent_id, Child.is_married == True)'
)
Дочерняя моделькак вы определили (за исключением длин строк, так как я работаю с MySQL):
class Child(Base):
id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.Integer, sa.ForeignKey('parent.id'))
sex = sa.Column(sa.String(6))
is_married = sa.Column(sa.Boolean)
is_working = sa.Column(sa.Boolean)
Вот некоторые тестовые данные:
s = Session()
parent = Parent()
s.add(parent)
parent.children.update([
Child(sex='male', is_married=True, is_working=False),
Child(sex='female', is_married=True, is_working=True),
Child(sex='male', is_married=False, is_working=True)
])
s.commit()
Когда выизвините за Parent
, joinedload
все отношения, так что вы совершаете только один круговой переход на БД (поскольку вы заявили, что эффективность представляет собой проблему, хотя это не является строго необходимым, ленивая загрузка будеттоже работает нормально):
parent = s.query(Parent).options(sa.orm.joinedload('*')).first()
print(parent.children)
# InstrumentedSet({Child(is_married=False, id=3, is_working=True, sex=male, parent_id=1), Child(is_married=True, id=2, is_working=True, sex=female, parent_id=1), Child(is_married=True, id=1, is_working=False, sex=male, parent_id=1)})
print(parent.female_children)
# InstrumentedSet({Child(is_married=True, id=2, is_working=True, sex=female, parent_id=1)})
print(parent.male_children)
# InstrumentedSet({Child(is_married=False, id=3, is_working=True, sex=male, parent_id=1), Child(is_married=True, id=1, is_working=False, sex=male, parent_id=1)})
print(parent.married_children)
# InstrumentedSet({Child(is_married=True, id=2, is_working=True, sex=female, parent_id=1), Child(is_married=True, id=1, is_working=False, sex=male, parent_id=1)})
Поскольку атрибуты отношения на Parent
все sets
, это позволяет очень эффективно работать с ними.Например, вот изоляция работающих детей мужского пола:
print(parent.working_children & parent.male_children)
# {Child(is_married=False, id=3, is_working=True, sex=male, parent_id=1)}
Затем вы можете определить любые свойства только для чтения для вашей модели Parent
, которые вам нравятся, вот несколько примеров:
class Parent(Base):
### excluded all of the columns and relationships for brevity ###
@property
def working_sons(self):
return self.working_children & self.male_children
@property
def working_daughters(self):
return self.working_children & self.female_children
@property
def unmarried_children(self):
return self.children - self.married_children
@property
def married_unemployed_sons(self):
return (
self.male_children - self.working_children & self.married_children)
print(parent.working_sons)
# {Child(is_married=False, id=3, is_working=True, sex=male, parent_id=1)}
print(parent.working_daughters)
# {Child(is_married=True, id=2, is_working=True, sex=female, parent_id=1)}
print(parent.unmarried_children)
# {Child(is_married=False, id=3, is_working=True, sex=male, parent_id=1)}
print(parent.married_unemployed_sons)
# {Child(is_married=True, id=1, is_working=False, sex=male, parent_id=1)}
Поскольку все это операции над множествами, они очень эффективны, но при желании вы можете кэшировать их результаты при первом доступе.
В вашем вопросе конкретно упоминается свойство bool
, has_working_sons
, поэтому вы можетеиспользуйте достоверность свойства Parent.working_sons
при тестировании, если у Parent
есть работающий сын (например, if parent.working_sons: ...
), или используйте bool(Parent.working_sons)
, если вам действительно нужно, чтобы он был True
/ False
.