【问题标题】:How can I prevent this SQLAlchemy one-to-many relationship from trying to insert duplicate data?如何防止这种 SQLAlchemy 一对多关系尝试插入重复数据?
【发布时间】:2016-04-22 09:07:54
【问题描述】:

我在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

我首先创建并插入帖子(和相关的 cmets),然后稍后再从网站上重新拉出帖子和 cmets。出于某种原因,当我 session.commit() 时,只有在更新 cmets 时才会出现错误,但更改其他字段就可以了。

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

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

# 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()

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

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

【问题讨论】:

    标签: python sql orm sqlalchemy


    【解决方案1】:

    您应该使用 SQLALchemy 的内置 hybrid_method decorator。这对我有用。

    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
    

    【讨论】:

      猜你喜欢
      • 2020-03-23
      • 2019-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-24
      • 2015-03-29
      • 2013-08-18
      相关资源
      最近更新 更多