如何使用 SQLAlchemy 在一次提交中切换唯一行的两个字段?

发布于 2025-01-01 11:12:58 字数 831 浏览 3 评论 0原文

假设有一个具有唯一名称的对象。现在您想要切换两个对象的名称:

这是布局:

import sqlalchemy as sa
import sqlalchemy.orm as orm
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class MyObject(Base):
  __tablename__ = 'my_objects'
  id = sa.Column(sa.Integer, primary_key=True)
  name = sa.Column(sa.Text, unique=True)

if __name__ == "__main__":
  engine = sa.create_engine('sqlite:///:memory:', echo=True)
  Session = orm.sessionmaker(bind=engine)
  Base.metadata.create_all(engine)
  session = Session()

我想这样做:

a = MyObject(name="Max")
b = MyObject(name="Moritz")
session.add_all([a, b])
session.commit()

# Now: switch names!
tmp = a.name
a.name = b.name
b.name = tmp
session.commit()

这会引发 IntegrityError。有没有一种方法可以在一次提交内切换这些字段而不会出现此错误?

Assume to have a object with unique name. Now you want to switch the name of two objects:

Here is the layout:

import sqlalchemy as sa
import sqlalchemy.orm as orm
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class MyObject(Base):
  __tablename__ = 'my_objects'
  id = sa.Column(sa.Integer, primary_key=True)
  name = sa.Column(sa.Text, unique=True)

if __name__ == "__main__":
  engine = sa.create_engine('sqlite:///:memory:', echo=True)
  Session = orm.sessionmaker(bind=engine)
  Base.metadata.create_all(engine)
  session = Session()

And I would like to do this:

a = MyObject(name="Max")
b = MyObject(name="Moritz")
session.add_all([a, b])
session.commit()

# Now: switch names!
tmp = a.name
a.name = b.name
b.name = tmp
session.commit()

This throws an IntegrityError. Is there a way to switch these fields within one commit without this error?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

深海不蓝 2025-01-08 11:12:58

更纯粹的选择是删除 a,重命名 b,然后重新添加重命名的:

session.delete(a)
sqlalchemy.orm.session.make_transient(a)
a.name, b.name = b.name, a.name
session.flush()
session.add(a)
session.commit()

A more pure option would be to delete a, rename b, then re-add a renamed:

session.delete(a)
sqlalchemy.orm.session.make_transient(a)
a.name, b.name = b.name, a.name
session.flush()
session.add(a)
session.commit()
戈亓 2025-01-08 11:12:58

您在名称字段中给出了 unique=True ,因此当您尝试提交时,它将运行更新查询,从而引发错误。

情况是当您更改名称时,它将在内存中设置。但是,当它尝试运行更新查询时,旧记录已存在同名,因此它将给出 IntegrityError

改名的方法是

a = MyObject(name="Max")
b = MyObject(name="Moritz")
session.add_all([a, b])
session.commit()

# Now: switch names!
atmp = a.name
btemp = b.name

a.name = a.name+btemp # Temp set the any random name
session.commit()

b.name = atemp
a.name = btemp
session.commit() # Run the update query for update the record.

You gave unique=True in the name field so when you are trying to commit it will run the update query it will raise the error.

The situation is when you change the name it will set in memory. But when it will try to run the update query the old record already exist with same name so it will give the IntegrityError.

The way to change name is

a = MyObject(name="Max")
b = MyObject(name="Moritz")
session.add_all([a, b])
session.commit()

# Now: switch names!
atmp = a.name
btemp = b.name

a.name = a.name+btemp # Temp set the any random name
session.commit()

b.name = atemp
a.name = btemp
session.commit() # Run the update query for update the record.
请止步禁区 2025-01-08 11:12:58

Python允许这种语法(使用元组):

a.name, b.name = b.name, a.name

以这种方式切换两个普通参数是绝对可以的,但没有在你的情况下进行测试,也许你可以尝试一下?

Python allows this syntax(using tuples):

a.name, b.name = b.name, a.name

It's absolutely okay to switch two normal arguments this way, but not tested in your situation, maybe you can give it a try?

许一世地老天荒 2025-01-08 11:12:58

感谢 Philipp der Rautenberg 的评论 我找到了更好的解决方案,可以与 PostgreSQL 一起使用,但不能SQLite。

有关其工作原理的更多信息,请参阅此答案PostgreSQL 文档SQLAlchemy 文档

这个想法是首先将唯一约束声明为 deferrable,然后通过在 initially: 中使用它

import sqlalchemy as sa
import sqlalchemy.orm as orm
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class MyObject(Base):
    __tablename__ = 'my_object'
    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.Text)
    name_unique_constraint = sa.UniqueConstraint(name, deferrable=True, initially='DEFERRED')


if __name__ == "__main__":
    engine = sa.create_engine('postgresql+psycopg2://user:password@hostname/database_name', echo=True)
    Session = orm.sessionmaker(bind=engine)
    Base.metadata.create_all(engine)
    session = Session()

    a = MyObject(name="Max")
    b = MyObject(name="Moritz")
    session.add_all([a, b])
    session.commit()

    # Now: switch names!
    a.name, b.name = b.name, a.name

    session.add_all([a, b]) # this line seems to be necessary to raise exception if swap woudn't be possible
    session.commit()

或使用显式 sql 语句将其设置为 DEFERRED

db.execute('SET CONSTRAINTS ALL DEFERRED')

或者

db.execute('SET CONSTRAINTS uq_my_object_name DEFERRED')

但请记住在使用SET CONSTRAINTS时:

此命令仅更改当前事务内约束的行为。

Thanks to Philipp der Rautenberg's comment I found nicer solution which works with PostgreSQL, but not SQLite.

More information on how it works is in this answer and in PostgreSQL docs and in SQLAlchemy docs.

The idea is to first declare unique constraint as deferrable and then set it as DEFERRED either by using it in initially:

import sqlalchemy as sa
import sqlalchemy.orm as orm
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class MyObject(Base):
    __tablename__ = 'my_object'
    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.Text)
    name_unique_constraint = sa.UniqueConstraint(name, deferrable=True, initially='DEFERRED')


if __name__ == "__main__":
    engine = sa.create_engine('postgresql+psycopg2://user:password@hostname/database_name', echo=True)
    Session = orm.sessionmaker(bind=engine)
    Base.metadata.create_all(engine)
    session = Session()

    a = MyObject(name="Max")
    b = MyObject(name="Moritz")
    session.add_all([a, b])
    session.commit()

    # Now: switch names!
    a.name, b.name = b.name, a.name

    session.add_all([a, b]) # this line seems to be necessary to raise exception if swap woudn't be possible
    session.commit()

or with explicit sql statement:

db.execute('SET CONSTRAINTS ALL DEFERRED')

or

db.execute('SET CONSTRAINTS uq_my_object_name DEFERRED')

but remember when using SET CONSTRAINTS that:

This command only alters the behavior of constraints within the current transaction.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文