【问题标题】:Referential Integrity and Transactions in SqlAlchemySqlAlchemy 中的引用完整性和事务
【发布时间】:2021-06-24 12:39:54
【问题描述】:

我正在尝试将数据插入两个表中,后者引用前者。比如:

CREATE TABLE foo (foo_id bigserial primary key, name varchar)
CREATE TABLE bar (bar_id bigserial primary key, name varchar,
                  foo_id int constraint foo_fkey references foo)

我想在一个事务中添加两行,这样如果出现任何故障,就不会向数据库写入任何内容。如果我只是将两个对象都添加到我的会话中,我不会得到自动递增的 foo_id,所以我添加 foo 对象,执行刷新(填充 id),然后设置 bar 对象,然后再次刷新(我以后还有更多工作要做,但没有被这种关系引用。在第二次刷新时,我收到了一个引用完整性错误,因为 foo 中的行实际上并没有被写入。

我可以在 SQL 中轻松完成此操作:

BEGIN TRANSACTION;
INSERT INTO foo (name) values('foo');
INSERT INTO bar (name, foo_id) VALUES('bar', currval(pg_get_serial_sequence('foo', 'foo_id')));
COMMIT TRANSACTION;

有没有办法让我暂时暂停 RI 检查?或者,我有没有更好的方法来获取新的 foo_id 而不是刷新?

这是我正在执行的那种 sqla 代码:

session.begin()
f = Foo()
f.name = 'Foo'
session.add(f)
session.flush()
b = Bar()
b.name = 'Bar'
b.foo_id = f.foo_id
session.add(b)
session.flush()
...
session.commit()
session.close()

我稍后使用 bar_id 值,这就是我需要第二次刷新的原因。错误发生在第二次刷新时。

【问题讨论】:

  • 你能分享示例 SQLA 代码吗?如果存在关系,您通常可以执行类似 `parent = Parent(child=child)` 之类的操作,并让键在提交时进行排序,而无需中间刷新。或者您可以使 FK 约束可延迟(我从未尝试过)。
  • @snakecharmerb,编辑了原始帖子以添加我正在执行的代码类型。实际的表/对象有更多的列,所以为了简洁起见,我在这里解释一下。我会看看我是否可以建立关系,看看是否有帮助......
  • 好吧,我尝试使用关系,但我没有收到 RI 错误,但现在我也没有得到填充的 ID。没有刷新或提交。

标签: postgresql sqlalchemy referential-integrity


【解决方案1】:

我无法在第二次刷新时重现完整性错误。假设具有如下一对一关系的模型设置:

class Foo(Base):
    __tablename__ = 'foo'

    id = sa.Column(sa.Integer, primary_key=True)
    bar = orm.relationship('Bar', back_populates='foo', uselist=False)


class Bar(Base):
    __tablename__ = 'bar'

    id = sa.Column(sa.Integer, primary_key=True)
    foo_id = sa.Column(sa.Integer, sa.ForeignKey('foo.id'))
    foo = orm.relationship('Foo', back_populates='bar')

这些动作的输出:

# No explicit id, no flush, commit
foo1 = Foo()
bar1 = Bar(foo=foo1)
session.add(bar1)
session.commit()
print(f'foo {foo1.id}, bar {bar1.id}')

# No explicit id, flush
bar2 = Bar()
foo2 = Foo(bar=bar2)
session.add(foo2)
session.flush()
print(f'foo {foo2.id}, bar {bar2.id}')
session.commit()


# Explicit id, two flushes
foo3 = Foo()
session.add(foo3)
session.flush()
bar3 = Bar()
bar3.foo_id = foo3.id
session.add(bar3)
session.flush()
print(f'foo {foo3.id}, bar {bar3.id}')
session.commit()

是我所期望的:

foo 1, bar 1
foo 2, bar 2
foo 3, bar 3

【讨论】:

  • 这是您示例的最后一部分为我完成的。我只是实例化“Bar”对象并将其添加到“Foo”中的集合中,但是当我添加 Bar 对象时,它就完成了。 f = Foo() f.name = 'foo' session.add(f) b = Bar() b.name = 'bar' session.add(b) f.b_list.append(b) session.flush()
【解决方案2】:

Postgres 允许在 CTE 中插入语句。这允许您在单个 SQL 中完成您想要的。 (见demo

with new_foo as 
     ( insert into foo(name) 
        values('foo') 
       returning foo_id
     ) 
insert into bar(name, foo_id) 
     select 'bar', foo_id 
       from new_foo;

这就是提供你的遮蔽层 (SalAlchemy) 也允许它。

【讨论】:

  • 是的,我的问题与其说是 postgres,不如说是 sqla。不过谢谢!
猜你喜欢
  • 2014-12-09
  • 2013-07-12
  • 1970-01-01
  • 2011-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多