如何防止此SQLAlchemy一对多关系尝试插入重复数据?

时间:2022-10-04 15:37:34

I have the following relationship in SQLAlchemy:

我在SQLAlchemy中有以下关系:

class Post(db.Base):
    __tablename__ = 'posts'
    id = Column(Integer, primary_key=True)
    url = Column(String(512), unique=True)
    timestamp = Column(DateTime)
    comments = relationship("Comment", backref=backref("post", cascade="save-update"))

    @classmethod
    def merge(cls, dst, src):
        # self.url doesn't change (if it does, we consider it a new post)
        dst.title = src.title
        dst.author = src.author
        dst.timestamp = src.timestamp
        dst.comments = src.comments

class Comment(db.Base):
    __tablename__ = 'comments'
    id = Column(Integer, primary_key=True)
    post_id = Column(Integer, ForeignKey('posts.id'))
    # switched to using backref, which obviates the need for following line:
    # post = relationship("Post", back_populates="comments")
    text = Column(UnicodeText)
    author = Column(String(128))
    timestamp = Column(DateTime)
    score = Column(Integer)

    def __init__(self, text, author, timestamp):
        self.text = text
        self.author = author
        self.timestamp = timestamp

I first create and insert the posts (and associated comments), then at a later time, come back and re-pull the posts and comments from the website. For some reason, when I session.commit(), I get an error only when I update the comments, but changing the other fields is fine.

我首先创建并插入帖子(以及相关的评论),然后在以后回来并重新提取网站上的帖子和评论。出于某种原因,当我session.commit()时,我只在更新注释时才会收到错误,但更改其他字段很好。

My question is: how should I be updating these posts? I've been told that some sort of UPSERT functionality is not ideal, nor does the built-in .merge() do what I expect (i.e., it tries to recreate the post).

我的问题是:我该如何更新这些帖子?我被告知某种UPSERT功能并不理想,内置的.merge()也没有达到我的预期(即,它试图重新创建帖子)。

Here's what I'm doing now:

这就是我现在正在做的事情:

# update existing posts if they exist (look up by url), otherwise insert
for p in posts:
    existing_post = db.session.query(post.Post).filter(post.Post.url==p.url).first()
    if existing_post:
        post.Post.merge(existing_post, p)
    else:
        pass
        db.session.add(p)

db.session.commit()

If I comment out the dst.comments = src.comments from the class method .merge(dst,src), then I have no issue.

如果我从类方法.merge(dst,src)中注释掉dst.comments = src.comments,那么我没有问题。

Traceback (most recent call last):
  File "/Users/p/src/python-envs/app/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1139, in _execute_context
context)
  File "/Users/p/src/python-envs/app/lib/python3.5/site-packages/sqlalchemy/engine/default.py", line 450, in do_execute
cursor.execute(statement, parameters)
psycopg2.IntegrityError: duplicate key value violates unique constraint "posts_url_key"
DETAIL:  Key (url)=(http://www.example/post) already exists.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 2407, in <module>
    globals = debugger.run(setup['file'], None, None, is_module)
  File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 1798, in run
    launch(file, globals, locals)  # execute the script
  File "/Applications/PyCharm.app/Contents/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc) 
  File "/Users/p/src/bss/slurper.py", line 75, in <module>
    db.session.commit()
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/session.py", line 813, in commit
    self.transaction.commit()
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/session.py", line 392, in commit
    self._prepare_impl()
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/session.py", line 372, in _prepare_impl
    self.session.flush()
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/session.py", line 2027, in flush
    self._flush(objects)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/session.py", line 2145, in _flush
    transaction.rollback(_capture_exception=True)
  File "/Users/p/src/python-envs/bss/lib/python3.5/sitepackages/sqlalchemy/util/langhelpers.py", line 60, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "/Users/p/src/python-envs/bss/lib/python3.5/sitepackages/sqlalchemy/util/compat.py", line 183, in reraise
    raise value
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/session.py", line 2109, in _flush
    flush_context.execute()
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/unitofwork.py", line 373, in execute
    rec.execute(self)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/unitofwork.py", line 532, in execute
uow
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/persistence.py", line 174, in save_obj
mapper, table, insert)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/persistence.py", line 800, in _emit_insert_statements
execute(statement, params)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 914, in execute
return meth(self, multiparams, params)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/sql/elements.py", line 323, in _execute_on_connection
return connection._execute_clauseelement(self, multiparams, params)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1010, in _execute_clauseelement
compiled_sql, distilled_params
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1146, in _execute_context
context)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1341, in _handle_dbapi_exception
exc_info
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/util/compat.py", line 189, in raise_from_cause
reraise(type(exception), exception, tb=exc_tb, cause=exc_value)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/util/compat.py", line 182, in reraise
raise value.with_traceback(tb)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1139, in _execute_context
context)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/engine/default.py", line 450, in do_execute
      cursor.execute(statement, parameters)
  sqlalchemy.exc.IntegrityError: (psycopg2.IntegrityError) duplicate key value violates unique constraint "posts_url_key"
  DETAIL:  Key (url)=(http://www.example.com/post) already exists.
   [SQL: 'INSERT INTO posts (url, title, author, timestamp) VALUES (%(url)s, %(title)s, %(author)s, %(timestamp)s) RETURNING posts.id'] [parameters: {'timestamp': datetime.datetime(2016, 1, 13, 21, 0), 'title': ‘Blog Post Title’, 'url': 'http://www.example.com/post', 'author': ‘authorname’}]

  Process finished with exit code 1

1 个解决方案

#1


0  

You should use the hybrid_method decorator builtin to SQLALchemy. This works for me.

您应该使用内置于SQLALchemy的hybrid_method装饰器。这对我有用。

class Post(Base):
    __tablename__ = 'posts'
    id = Column(Integer, primary_key=True)
    url = Column(String(512), unique=True)
    timestamp = Column(DateTime)
    comments = relationship("Comment", backref=backref("post", cascade="save-update"))

    @hybrid_method
    def merge(self, dst):
        # self.url doesn't change (if it does, we consider it a new post)
        self.timestamp = dst.timestamp
        self.comments = dst.comments

class Comment(Base):
    __tablename__ = 'comments'
    id = Column(Integer, primary_key=True)
    post_id = Column(Integer, ForeignKey('posts.id'))
    text = Column(UnicodeText)
    author = Column(String(128))
    timestamp = Column(DateTime)
    score = Column(Integer)

c1 = Comment()
c2 = Comment()
c3 = Comment(text=u"comment3")

p1 = Post(url="foo")
p1.comments = [c1, c2]
session.add(p1)
session.commit()

p1 = session.query(Post).filter_by(url="foo").one()
p2 = Post()
p2.comments = [c3]
p1.merge(p2)

session.commit()

p1 = session.query(Post).filter_by(url="foo").one()
print p1.comments[0].text # comment3

#1


0  

You should use the hybrid_method decorator builtin to SQLALchemy. This works for me.

您应该使用内置于SQLALchemy的hybrid_method装饰器。这对我有用。

class Post(Base):
    __tablename__ = 'posts'
    id = Column(Integer, primary_key=True)
    url = Column(String(512), unique=True)
    timestamp = Column(DateTime)
    comments = relationship("Comment", backref=backref("post", cascade="save-update"))

    @hybrid_method
    def merge(self, dst):
        # self.url doesn't change (if it does, we consider it a new post)
        self.timestamp = dst.timestamp
        self.comments = dst.comments

class Comment(Base):
    __tablename__ = 'comments'
    id = Column(Integer, primary_key=True)
    post_id = Column(Integer, ForeignKey('posts.id'))
    text = Column(UnicodeText)
    author = Column(String(128))
    timestamp = Column(DateTime)
    score = Column(Integer)

c1 = Comment()
c2 = Comment()
c3 = Comment(text=u"comment3")

p1 = Post(url="foo")
p1.comments = [c1, c2]
session.add(p1)
session.commit()

p1 = session.query(Post).filter_by(url="foo").one()
p2 = Post()
p2.comments = [c3]
p1.merge(p2)

session.commit()

p1 = session.query(Post).filter_by(url="foo").one()
print p1.comments[0].text # comment3