处理SQLAlchemy

时间:2015-09-28 14:21:55

标签: python sql sqlite sqlalchemy rdbms

我在我的应用程序中发现了这个问题,使用Python3和SQLAlchemy连接的sqlite-database。我从面向对象的开发人员的角度设计了数据结构。我认为这是我的问题之一。 ;)

简单描述: 实体a(来自表/类A)可以具有对实体b的多重引用(来自表/类B)。

样品:

CREATE TABLE "B" (
        oid INTEGER NOT NULL,
        val INTEGER,
        PRIMARY KEY (oid)
);
CREATE TABLE "A" (
        oid INTEGER NOT NULL,
        PRIMARY KEY (oid)
);
CREATE TABLE a_b_relation (
        a_oid INTEGER,
        b_oid INTEGER,
        FOREIGN KEY(a_oid) REFERENCES "A" (oid),
        FOREIGN KEY(b_oid) REFERENCES "B" (oid)

sqlite> SELECT oid FROM A;
1
sqlite> SELECT oid, val FROM B;
1|0
2|1
sqlite> SELECT a_oid, b_oid FROM a_b_relation;
1|1
1|1

您在此处看到a有两个对同一实体/行b的引用。 在我的数据结构中,这是有道理的。但也许它打破了SQL- / RDBMS规则?

当我在Python3中使用该对象执行此操作时会导致错误,因为SQLAlchemy尝试为此执行DELETE语句。

a._bbb.clear()

错误

DELETE FROM a_b_relation WHERE a_b_relation.a_oid = ? AND a_b_relation.b_oid = ?
(1, 1)
ROLLBACK
sqlalchemy.orm.exc.StaleDataError: DELETE statement on table 'a_b_relation' expected to delete 1 row(s); Only 2 were matched.

在这种情况下,错误对我有意义。但我不知道如何处理。

从我看来,它看起来像SQLAlchemy中的“bug”,因为构造的DELETE语句不会处理我的用例。但我当然知道,特别是SQLA开发人员会考虑他们做了什么,并且这种行为有一个很好的设计理由。

以下是创建和填充示例数据库以显示此问题的代码:

#!/usr/bin/env python3

import os.path
import os
import sqlalchemy as sa 
import sqlalchemy.orm as sao
import sqlalchemy.ext.declarative as sad
from sqlalchemy_utils import create_database

_Base = sad.declarative_base()
session = None

a_b_relation= sa.Table('a_b_relation', _Base.metadata,
    sa.Column('a_oid', sa.Integer, sa.ForeignKey('A.oid')),
    sa.Column('b_oid', sa.Integer, sa.ForeignKey('B.oid'))
)


class A(_Base):
    __tablename__ = 'A'

    _oid = sa.Column('oid', sa.Integer, primary_key=True)
    _bbb = sao.relationship('B', secondary=a_b_relation)

    def __str__(self):
        s = '{}.{} oid={}'.format(type(self), id(self), self._oid)
        for b in self._bbb:
            s += '\n\t{}'.format(b)
        return s


class B(_Base):
    __tablename__ = 'B'

    _oid = sa.Column('oid', sa.Integer, primary_key=True)
    _val = sa.Column('val', sa.Integer)

    def __str__(self):
        return '{}.{} oid={} val={}'.format(type(self), id(self), self._oid, self._val)


dbfile = 'set.db'

def _create_database():
    if os.path.exists(dbfile):
        os.remove(dbfile)

    engine = sa.create_engine('sqlite:///{}'.format(dbfile), echo=True)
    create_database(engine.url)
    _Base.metadata.create_all(engine)
    return sao.sessionmaker(bind=engine)()

def _fill_database():
    a = A()
    session.add(a)

    for v in range(2):
        b = B()
        b._val = v
        session.add(b)
        if v == 0:
            # THIS CAUSE THE PROBLEM I THINK
            a._bbb += [b]
            a._bbb += [b]

    session.commit()


if __name__ == '__main__':
    session = _create_database()
    _fill_database()

    a = session.query(A).first()
    a._bbb.clear()
    session.commit()

也许它有点远,但我也会描述原始的数据结构。它是关于为健身房制作使用统计数据。 :)

你走到一台机器上并且几次抬起几公斤 - 这被称为TrainingUnit(在示例中相当于A)。 为了预热,你需要重复12次,每次重复35公斤 - 在示例中称为SetSet(相当于B;请记住Set是保留的SQL关键字)。 然后你做一分钟休息,用40公斤重复12次 - 第二组。然后再次进行第三次(!),重复12次,再重40次。 因此TrainingUnit实例/行需要三个与SetSet的实例/行的关系。因为第二组和第三组具有相同的值/设置,所以我在这里使用相同的SetSet实例/行。

大多数情况下,运动员每天都使用相同的配置进行TrainingUnits - 这意味着每天都有相同的值。这就是我想重用SetSet的实例/行的原因。 每个TrainingUnit的套数不固定 - 这取决于运动员他/她将制作多少套。

1 个答案:

答案 0 :(得分:0)

我的解决方案取决于资源。谢谢你的帮助!

我使用示例代码来描述解决方案。

SQL中的架构......

class Route
{
public:
    Route ( const Leg& myLeg ): theLeg( myLeg ) {}
private:
    const Leg& theLeg;
};

...和SQLAlchemy

CREATE TABLE "A" (
        oid INTEGER NOT NULL,
        PRIMARY KEY (oid)
);
CREATE TABLE "B_Val" (
        oid INTEGER NOT NULL,
        val INTEGER,
        PRIMARY KEY (oid)
);
CREATE TABLE "B" (
        oid INTEGER NOT NULL,
        b_val_fk INTEGER,
        PRIMARY KEY (oid),
        FOREIGN KEY(b_val_fk) REFERENCES "B_Val" (oid)
);
CREATE TABLE a_b_relation (
        a_oid INTEGER,
        b_oid INTEGER,
        FOREIGN KEY(a_oid) REFERENCES "A" (oid),
        FOREIGN KEY(b_oid) REFERENCES "B" (oid)
);