使用SQLite将内联查询结果分配给SQLAlchemy中的列

时间:2014-07-17 15:39:47

标签: python sqlite sqlalchemy

我想从SQLAlchemy中的查询结果生成主键列。是的,我有理由不使用自动增量。

我有一张桌子:

-- Describe THERAPY
CREATE TABLE foo (
    pk INTEGER NOT NULL PRIMARY KEY,
    bar INTEGER
)

和SQLAlchemy ORM模型:

class Foo(Base):
    __tablename__ = 'foo'
    pk = Column(Integer, primary_key=True)
    bar = Column(Integer)

在SQLite中这没问题,我可以使用子查询进行简单的插入:

insert into foo (pk) values ((select coalesce(max(foo.pk) + 1, 1) from foo));

我无法在SQLAlchemy中执行此操作:

foo = Foo()
foo.pk = sql.select([sql.func.coalesce(sql.func.max(Foo.pk) + 1, 1)]).as_scalar()
session.add(foo)
session.commit()

导致stacktrace:

InvalidRequestError: Instance <Foo at 0x224f710> cannot be refreshed - it's not  persistent and does not contain a full primary key.

将查询分配给foo.bar可以很好地完成预期的结果

foo = Foo()
foo.pk = 1
foo.bar = sql.select([sql.func.coalesce(sql.func.max(Foo.pk) + 1, 1)]).as_scalar()
session.add(foo)
session.commit()
# outputs (1, 1)
print (foo.bar, foo.pk)

1 个答案:

答案 0 :(得分:1)

  

导致堆栈跟踪:   InvalidRequestError:无法刷新实例 - 它不是持久性的,并且不包含完整的主键。

所以我不想在这里成为一个坚持者,但这不是一个堆栈跟踪,这是一个错误信息。

如果我们重现您的完整脚本,我们可以看到发生了什么。

首先,始终使用echo = True来查看正在发生的事情。我们可以看到该声明确实按计划工作:

INSERT INTO foo (pk, bar) VALUES ((SELECT coalesce(max(foo.pk) + ?, ?) AS coalesce_1 

然而,错误随后会发生,使用此部分跟踪:

  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/unitofwork.py", line 389, in finalize_flush_changes
    self.session._register_newly_persistent(other)
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/session.py", line 1408, in _register_newly_persistent
    instance_key = mapper._identity_key_from_state(state)
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/mapper.py", line 2285, in _identity_key_from_state
    for col in self.primary_key
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py", line 580, in get
    value = callable_(state, passive)
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/state.py", line 423, in __call__
    self.manager.deferred_scalar_loader(self, toload)
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/loading.py", line 597, in load_scalar_attributes
    "contain a full primary key." % state_str(state))
sqlalchemy.exc.InvalidRequestError: Instance <Foo at 0x10165d510> cannot be refreshed - it's not  persistent and does not contain a full primary key.

我们可以看到SQLAlchemy ORM像所有ORM一样,需要知道这个新插入对象的主键 - “来自状态的身份密钥”。 SQLAlchemy在这里非常失败。这可以改进。

要理解这里的全貌是非常复杂的,因为这里的现实有许多角落和缝隙,所以我会试着把它们弹出来:

  1. 通常,这里不可能避免失败。 SQLite不支持RETURNING,因此我们可以通过cursor.lastrowid在内联完成时获取该值的唯一方法。 Cursor.lastrowid通常只给我们使用数据库的排序机制生成的值。在MySQL上,我们必须使用AUTOINCREMENT列;我们从lastrowid那里得到零回报。

  2. 显然,基于一个简短的测试,最近的pysqlite在cursor.lastrowid中给出了实际值。我以前从未见过DBAPI这样做,SQLAlchemy需要额外的增强功能来区分lastrowid仅适用于自动增量值的数据库,而lastrowid实际上无论它来自何处都能为您提供值。现在还没有。

  3. 如果我们在支持RETURNING的数据库上运行此脚本,整个过程就可以了。这是Postgresql:

    INSERT INTO foo (pk, bar) VALUES ((SELECT coalesce(max(foo.pk) + %(max_1)s, %(param_1)s) AS coalesce_1 
    FROM foo), %(bar)s) RETURNING foo.pk
    
  4. 所以现在(2014年7月,注释issue 3133,针对SQLAlchemy 1.0,添加以允许使用此值),用于ORM使用(需要PK,而不是Core)由于没有跟踪对象,因此不需要这样做,要在lastrowid后端使用此模式,需要在INSERT之外调用该语句。我们可以使用ColumnDefault实现这一点:

    class Foo(Base):
        __tablename__ = 'foo'
        pk = Column(Integer, primary_key=True)
        bar = Column(Integer)
    
     Foo.__table__.c.pk.default = ColumnDefault(sql.select([sql.func.coalesce(sql.func.max(Foo.pk) + 1, 1)]).as_scalar())
    
  5. 我们会在哪里看到:

    SELECT (SELECT coalesce(max(foo.pk) + ?, ?) AS coalesce_1 
    FROM foo) AS anon_1
    (1, 1)
    INSERT INTO foo (pk, bar) VALUES (?, ?)
    (1, None)
    

    这适用于任何后端。

    或者如果需要“每个实例”系统,只需运行语句:

    foo = Foo()
    foo.pk = session.scalar(sql.select([sql.func.coalesce(sql.func.max(Foo.pk) + 1, 1)]))
    session.add(foo)
    session.commit()