python - 如何使 SQLAlchemy 插入与 Postgres 多处理证明 upsert 触发器一起工作?

标签 python postgresql sqlalchemy

我有一个需要更新插入(插入,如果存在更新)功能的多处理应用程序。

我决定使用触发器解决方案来处理 upsert。 (您为每个名为 is_upsert 的启用更新插入的表添加额外的列,并在触发器中检查此字段,如果它为假,则执行正常插入,但如果为真,则执行更新插入逻辑 - 尝试更新,如果它失败,因为记录没有'存在你尝试插入)。

触发逻辑如下:

CREATE OR REPLACE FUNCTION upsert_trigger_function_{table}()
RETURNS TRIGGER AS $upsert_trigger_function$
    DECLARE
    row record;
    BEGIN
    RAISE NOTICE 'upsert trigger fired, upsert is %%', NEW.{upsert_column};
    IF NEW.{upsert_column} THEN
        NEW.{upsert_column} := false;
        LOOP
            UPDATE {table} SET
                {update_set}
            WHERE
                {update_where}
            ;
            IF found THEN
                RETURN NULL;
            END IF;
            BEGIN
                INSERT INTO {table} SELECT NEW.*;
                RETURN NULL;
            EXCEPTION WHEN unique_violation THEN
                -- loop
            END;
        END LOOP;
        RETURN NULL;
    ELSE
        RETURN NEW;
    END IF;
    END;
$upsert_trigger_function$ LANGUAGE plpgsql;

测试对象,(add_upsert只是让上面的触发器被安装):

class SimpleItem(PipelinesBase):
    __tablename__ = 'simple_item'

    id = Column(BigInteger, primary_key=True)
    item_type = Column(String, nullable=False, unique=True)
    quantity = Column(Integer, nullable=False)
    price = Column(Float, nullable=False)
    in_stock = Column(Boolean, nullable=False)
    arrived = Column(Date)
    sys_time = Column(
    TSTZRANGE,
    nullable=False,
    server_default=text("TSTZRANGE(now(), null)"),
    )
    _upsert = Column(Boolean, nullable=False, server_default=text('false'))
    _type_identifier = 1400
add_upsert(SimpleItem, ['item_type'])

测试脚本

from sqlalchemy.engine import create_engine
from pipelines.settings_proxy import TEST_DB
from sqlalchemy.orm.session import sessionmaker
from test_pipelines.test_persistence.mock_items import SimpleItem
from test_pipelines.test_persistence.helpers import random_simple_item

def main():
    engine = create_engine(TEST_DB)

    values = random_simple_item(_upsert=True)

    session = sessionmaker(engine)()

    si = SimpleItem(**values)
    session.add(si)
    session.commit()

    si = SimpleItem(**values)
    si.price = 1
    session.merge(si)
    session.commit()

它在使用 SQL 语句时工作正常,但是当我将它与 SQLAlchemy ORM add 对象一起使用时,有

Traceback (most recent call last):
  File "pipelines/persistence/experiment_with_upsert_field.py", line 59, in <module>
    main()
  File "pipelines/persistence/experiment_with_upsert_field.py", line 27, in main
    session.commit()
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/orm/session.py", line 801, in commit
    self.transaction.commit()
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/orm/session.py", line 392, in commit
    self._prepare_impl()
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/orm/session.py", line 372, in _prepare_impl
    self.session.flush()
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/orm/session.py", line 2019, in flush
    self._flush(objects)
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/orm/session.py", line 2137, in _flush
    transaction.rollback(_capture_exception=True)
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/util/langhelpers.py", line 60, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/util/compat.py", line 184, in reraise
    raise value
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/orm/session.py", line 2101, in _flush
    flush_context.execute()
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/orm/unitofwork.py", line 373, in execute
    rec.execute(self)
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/orm/unitofwork.py", line 532, in execute
    uow
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/orm/persistence.py", line 174, in save_obj
    mapper, table, insert)
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/orm/persistence.py", line 800, in _emit_insert_statements
    execute(statement, params)
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/engine/base.py", line 914, in execute
    return meth(self, multiparams, params)
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/sql/elements.py", line 323, in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/engine/base.py", line 1010, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/engine/base.py", line 1159, in _execute_context
    result = context._setup_crud_result_proxy()
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/engine/default.py", line 828, in _setup_crud_result_proxy
    self._setup_ins_pk_from_implicit_returning(row)
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/engine/default.py", line 893, in _setup_ins_pk_from_implicit_returning
    for col in table.primary_key
  File "/home/sebastian/local/virtualenvs/perception/lib/python3.4/site-packages/sqlalchemy/engine/default.py", line 891, in <listcomp>
    for col, value in [
TypeError: 'NoneType' object is not subscriptable

深入 sqlalchemy.engine.default。我很确定这是因为我的触发器在执行 UPSERT 时返回 NULL,并且 SQLAlchemy 尝试使用 RETURNING 语句传播具有插入 ID 的对象。这显然失败了,因为不可能从它的下属插入/更新中获得触发器中的正确 ID,同时阻止正常的正常插入。

请注意,我已经将 upsert 作为特殊函数进行了测试,但它对我不起作用,因为我牺牲了 SQLAlchemy 对更新复杂项目(与其他项目有关系的项目)的帮助。

所以这是我的问题:我怎样才能告诉 SQLAlchemy 避免加载插入的对象 ID?

最佳答案

经过研究和测试发现在SQLAlchemy ORM中是做不到的。然而,它可以在 SQLAlchemy Core 中通过将 inline 关键字参数设置为 True 来完成:

engine.execute(
    SimpleItem.__table__.insert(inline=True),
    values
)

values['price'] = 1
engine.execute(
    SimpleItem.__table__.insert(inline=True),
    values
)

关于python - 如何使 SQLAlchemy 插入与 Postgres 多处理证明 upsert 触发器一起工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36082196/

相关文章:

postgresql - 如何在所有表中搜索特定值(PostgreSQL)?

arrays - 如果对象不存在,则将对象连接到数组

sqlalchemy - 如何使用 SQLAlchemy 多对多关系实例化 wtforms.ext.sqlalchemy.fields.QuerySelectMultipleField

python - 我无法弄清楚这个压缩代码

python - 拟合 scikit-learn 决策树和随机森林分类器时出现 MemoryError

postgresql - find_or_create_by - id = nil 当记录存在时

python - 在 SQLAlchemy 中更新关联对象中的字段

python sqlalchemy 在元组数据结构中插入多行

javascript - Django CMS/WYMEditor 停止剥离空白

Python CookieJar - 由 Google Analytics 设置的 Cookie