SQLAlchemy如何在旧值[1, 2]和新值[2, 3]时更新子项

0 投票
1 回答
1883 浏览
提问于 2025-04-18 10:47

我有两个模型:

class Person(Model):
    id
    name
    skills = relationship(Skill)

class Skill(Model):
    id
    skill
    person_id

一开始,比如说:

jack = Person(name='jack')
jack.skills = [Skill(s) for s in ['python', 'ruby']]
jack.save()

然后,有一天,杰克失去了他的技能“ruby”,但获得了“swift”,所以他的技能变成了 ['python', 'swift']。

我现在更新技能的方式是:

  1. 先找出现有的技能,我得到 old = ['python', 'ruby']
  2. 然后获取新的技能列表 new = ['python', 'swift']
  3. 把旧的和新的技能都转成集合 set(old), set(new)
  4. unchanged = old.intersection(new) 找出没有变化的技能
  5. 把每个在 set(new - unchanged) 中的技能加进去
  6. 把每个在 set(old-unchanged) 中的技能删掉

有没有更简单的方法来做到这一点呢?

1 个回答

0

在关系中使用 collection_class=set,这样可以把它当作一个集合来处理,而不是一个列表。

下面是一个关于如何将人和技能关联起来的示例。这是一个多对多的关系,也就是说,每个技能可以关联多个不同的人,而不是每个技能只关联一个人。这个关系通过一个叫做 person_skill 的表来实现。这里的关系集合是一个 set,而 Skill 类里有一个 __hash__ 函数,这样同名的技能会被认为是相同的。

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

engine = create_engine('sqlite:///:memory:', echo=True)
Base = declarative_base(bind=engine)
Session = sessionmaker(bind=engine)
session = Session()


class Person(Base):
    __tablename__ = 'person'

    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False, unique=True)

    # many-to-many relation, as a set
    skills = relationship('Skill', 'person_skill', collection_class=set)


class Skill(Base):
    __tablename__ = 'skill'

    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False, unique=True)

    def __hash__(self):
        # so that the set collection will handle duplicate entries
        return hash((self.__class__, self.name))


# many-to-many table, relate a person to a skill
person_skill = Table(
    'person_skill', Base.metadata,
    Column('person_id', Integer, ForeignKey(Person.id), primary_key=True),
    Column('skill_id', Integer, ForeignKey(Skill.id), primary_key=True)
)


# create the tables
Base.metadata.create_all()

# populate some skills and people
s1 = Skill(name='python')
s2 = Skill(name='sqlalchemy')
s3 = Skill(name='questions')
s4 = Skill(name='ruby')

p1 = Person(name='davidism', skills={s1, s2, s4})
p2 = Person(name='user2653947', skills={s3})

session.add_all([p1, p2])
session.commit()

# change some skills on people
p1.skills.discard(s4)
p2.skills.add(s2)
session.commit()

这并不是一个完整的解决方案。比如,你可以参考 这个回答中展示的独特对象模式,确保你创建的技能不会重复。

撰写回答