Вопрос по python – SQLAlchemy: таблица отношений с составным первичным ключом

10

У меня есть набор таблиц, которые выглядят так:

workflows = Table('workflows', Base.metadata,
                  Column('id', Integer, primary_key=True),
                 )

actions = Table('actions', Base.metadata,
                Column('name', String, primary_key=True),
                Column('workflow_id', Integer, ForeignKey(workflows.c.id), primary_key=True),
               )

action_dependencies = Table('action_dependencies', Base.metadata,
                            Column('workflow_id', Integer, ForeignKey(workflows.c.id), primary_key=True),
                            Column('parent_action', String, ForeignKey(actions.c.name), primary_key=True),
                            Column('child_action', String, ForeignKey(actions.c.name), primary_key=True),
                           )

Мои классы ORM выглядят так:

class Workflow(Base):
    __table__ = workflows

    actions = relationship("Action", order_by="Action.name", backref="workflow")


class Action(Base):
    __table__ = actions

    children = relationship("Action",
                            secondary=action_dependencies,
                            primaryjoin=actions.c.name == action_dependencies.c.parent_action,
                            secondaryjoin=actions.c.name == action_dependencies.c.child_action,
                            backref="parents"
                           )

Так что в моей системе каждое действие уникально идентифицируется комбинацией идентификатора рабочего процесса и его имени. Мне бы хотелось, чтобы каждое действие имелоparents а такжеchildren атрибут, который ссылается на родительские и дочерние действия. Каждое действие может иметь несколько родителей и детей.

Проблема возникает, когда у меня есть такая функция, как:

def set_parents(session, workflow_id, action_name, parents):
    action = session.query(db.Action).filter(db.Action.workflow_id == workflow.id).filter(db.Action.name == action_name).one()

    for parent_name in parents:
        parent = session.query(db.Action).filter(db.Action.workflow_id == workflow.id).filter(db.Action.name == parent_name).one()
        action.parents.append(parent)

    session.commit()

Я получаю ошибку как:

IntegrityError: (IntegrityError) action_dependencies.workflow_id may not be NULL u'INSERT INTO action_dependencies (parent_action, child_action) VALUES (?, ?)' (u'directory_creator', u'packing')

Как мне получить отношения для правильной установки workflow_id?

Обратите внимание, что вашparents/children отношения должны также включатьworkflow_id вprimaryjoin а такжеsecondary условия, иначе вы получите их для всех рабочих. van
Хорошая мысль, хорошая мысль. дай мне подумать... van
Потому что первичный ключ для действия является составной частью его имени и workflow_id. Если бы идентификатор workflow_id не был в action_dependencies, не было бы никакого способа сказать, на какие действия рабочего процесса ссылалась зависимость. Kamil Kisiel
Зачем вам нужно иметьworkflow_id вaction_dependencies Таблица? van

Ваш Ответ

2   ответа
11

которые я упомянул в комментариях:

proper composite ForeignKeys correct relationship configuration using the FKs

Код:

workflows = Table('workflows', Base.metadata,
                  Column('id', Integer, primary_key=True),
                 )

actions = Table('actions', Base.metadata,
                Column('workflow_id', Integer, ForeignKey(workflows.c.id), primary_key=True),
                Column('name', String, primary_key=True),
               )

action_dependencies = Table('action_dependencies', Base.metadata,
                            Column('workflow_id', Integer, ForeignKey(workflows.c.id), primary_key=True),
                            Column('parent_action', String, ForeignKey(actions.c.name), primary_key=True),
                            Column('child_action', String, ForeignKey(actions.c.name), primary_key=True),
                            ForeignKeyConstraint(['workflow_id', 'parent_action'], ['actions.workflow_id', 'actions.name']),
                            ForeignKeyConstraint(['workflow_id', 'child_action'], ['actions.workflow_id', 'actions.name']),
                           )
class Workflow(Base):
    __table__ = workflows
    actions = relationship("Action", order_by="Action.name", backref="workflow")

class Action(Base):
    __table__ = actions
    children = relationship("Action",
                            secondary=action_dependencies,
                            primaryjoin=and_(actions.c.name == action_dependencies.c.parent_action,
                                actions.c.workflow_id == action_dependencies.c.workflow_id),
                            secondaryjoin=and_(actions.c.name == action_dependencies.c.child_action,
                                actions.c.workflow_id == action_dependencies.c.workflow_id),
                            backref="parents"
                           )

# create db schema
Base.metadata.create_all(engine)

# create entities
w_1 = Workflow()
w_2 = Workflow()
a_11 = Action(name="ac-11", workflow=w_1)
a_12 = Action(name="ac-12", workflow=w_1)
a_21 = Action(name="ac-21", workflow=w_2)
a_22 = Action(name="ac-22", workflow=w_2)
session.add(w_1)
session.add(w_2)
a_22.parents.append(a_21)
session.commit()
session.expunge_all()
print '-'*80

# helper functions
def get_workflow(id):
    return session.query(Workflow).get(id)
def get_action(name):
    return session.query(Action).filter_by(name=name).one()

# test another OK
a_11 = get_action("ac-11")
a_12 = get_action("ac-12")
a_11.children.append(a_12)
session.commit()
session.expunge_all()
print '-'*80

# test KO (THIS SHOULD FAIL VIOLATING FK-constraint)
a_11 = get_action("ac-11")
a_22 = get_action("ac-22")
a_11.children.append(a_22)
session.commit()
session.expunge_all()
print '-'*80
0

Как это работает?

Но чтобы сделать составное ограничение, ключ, который является «уникальным вместе», используйте это в определении таблицы:

UniqueConstraint(u"name", u"workflow_id"),

Но если вы действительно хотите, чтобы он был первичным ключом, вы также можете использовать это:

PrimaryKeyConstraint(*columns, **kw)

http://docs.sqlalchemy.org/en/latest/core/schema.html#sqlalchemy.schema.PrimaryKeyConstraint

нет ничего плохого в ограничениях внешнего ключа на первичных ключах; это типичный способ получить «один-к-одному»; отношения, чтобы отобразить подклассы в базу данных или иметь атрибуты, которые все могут быть «вместе взятыми»

Похожие вопросы