2

「foobar」および「foobar_data_cache」テーブルで表されるfoobarの概念がデータベースであり、「Foobar」クラスはpythonです。「foobar」テーブルはコンセプトに関するデータを表し、「foobar_data_cache」テーブルはコンセプトに関するその他のデータを表します。これらのデータは、データベース内の他の多くの情報から派生し、データベース トリガーによって計算されます。一貫性を保つために、INSERT、UPDATE、および DELETE 権限は「foobar_data_cache」テーブルから取り消されます。

SQLAlchemy を使用して、'Foobar' クラスを 2 つのテーブル 'foobar' と 'foobar_data_cache' に結合してマップしたいと考えています。'foobar_data_cache' テーブルからのデータを表すために別のクラスを使用し、これら 2 つのクラス間の関係を構築する理由はありません。これは、両方のテーブルからのデータが強く関連しているためです。実際、データベースの観点からは、2 つのテーブル間に 1 対 1 の関係があり、以下によって保証され
ます
。 「foobar」の各行に対応する行が「foobar_data_cache」にあることを確認するトリガー

私の問題は、SQLAlchemy ORM を使用して新しい Foobar オブジェクトを永続化しようとすると、「foobar_data_cache」テーブルに行を挿入しようとすることです。これを防止したいと考えています。

では、「foobar_data_cache」テーブルを読み取り専用と見なすように SQLAlchemy を構成することは可能ですか? はいの場合、どのように?

私の問題を説明するコードは次のとおりです。

from sqlalchemy import (
    Table,
    Column,
    Integer,
    String,
    ForeignKeyConstraint,
    join,
    create_engine,
    )
from sqlalchemy.orm import (
    column_property,
    sessionmaker,
    )
from sqlalchemy.schema import (
    FetchedValue,
    )
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

_foobar_table = Table('foobar', Base.metadata,
        Column('id', Integer, primary_key=True),
        Column('some_data', String),
    )

_foobar_data_cache_table = Table('foobar_data_cache', Base.metadata,
        Column('foobar_id', Integer, primary_key=True),
        Column('computed_data', String, server_default=FetchedValue()),
        ForeignKeyConstraint(['foobar_id'], ['foobar.id']),
    )

class Foobar(Base):
    __table__ = _foobar_table.join(_foobar_data_cache_table)
    _id = column_property(_foobar_table.c.id, _foobar_data_cache_table.c.foobar_id)

    def __init__(self, some_data):
        self.some_data = some_data

if __name__ == '__main__':
    engine = create_engine('postgresql://tester@localhost:5432/mytestdb')
    Session = sessionmaker(bind=engine)
    session = Session()

    my_foobar = Foobar('Dummy data')
    session.add(my_foobar)
    session.commit()

2 つのテーブルを作成する SQL コマンドは次のとおりです。

CREATE TABLE foobar (
  id int NOT NULL DEFAULT -2147483648,
  some_data varchar NOT NULL,
  CONSTRAINT pk_foobar PRIMARY KEY (id)
);

CREATE TABLE foobar_data_cache (
  foobar_id int NOT NULL,
  computed_data varchar NOT NULL,
  CONSTRAINT pk_foobar_data_cache PRIMARY KEY (foobar_id),
  CONSTRAINT fk_foobar_data_cache_foobar_1 FOREIGN KEY (foobar_id)
    REFERENCES foobar (id) MATCH FULL
    ON UPDATE CASCADE ON DELETE CASCADE
);  

注:
1 対 1 の関係であるという事実を考慮して、データを 2 つの異なるテーブルに分割する理由を疑問に思う人もいるかもしれません。この問題は、単一のテーブルと FetchedValue コンストラクト ( SQLAlchemy でマップされた列のサブセットのみを永続化する方法を参照) を計算列に使用することで簡単に解決できます。うーん、少し複雑ですが、説明しようと思います。まず、上記で説明されていないその他の事項は次のとおりです。
- PostgreSQL 8.4 を使用していますが、とりわけ、遅延可能な UNIQUE 制約を持つことができません
- 私の列はどれも NULL 値を受け入れません
- 'foobar_data_cache' の一部の列には一意の制約があります (遅延可能ではありません)
- 「foobar_data_cache」のデータを計算するトリガーは、トランザクションの終了まで延期されます。実際、外部キーの制約により、「foobar」への挿入後にのみ挿入できる他のテーブルから情報を取得するためです。

つまり、単一のテーブルを使用する場合、NOT-NULL 制約のために、計算列に一時的なダミー値を使用する必要があります。私のトリガーは、トランザクションの最後に最終的にそれをオーバーライドします。そして問題は同時実行性です。実際、別のトランザクション T1 の実行中に新しい「foobar」を挿入しようとする新しいトランザクション Tx は、実行中のトランザクション T1 に対応する行に一意の列のデフォルトのダミー値が既に存在するため、失敗します。一意の制約を持つ列に対してランダムなダミー値を生成できますが、そのスタイルは好きではありません。

4

1 に答える 1

2

利用可能なテーブルの主キーがない場合、ORM は INSERT/UPDATE/DELETE をスキップします。この場合、これが達成されます。" "exclude_propertiesの値は気にしないため、複合 "PK" 列がなくなっていることにも注意してください。foobar_id

from sqlalchemy import (
    Table,
    Column,
    Integer,
    String,
    ForeignKeyConstraint,
    join,
    create_engine,
    )
from sqlalchemy.orm import (
    column_property,
    sessionmaker,
    )
from sqlalchemy.schema import (
    FetchedValue,
    )
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

_foobar_table = Table('foobar', Base.metadata,
        Column('id', Integer, primary_key=True),
        Column('some_data', String),
    )

_foobar_data_cache_table = Table('foobar_data_cache', Base.metadata,
        Column('foobar_id', Integer, primary_key=True),
        Column('computed_data', String, server_default=FetchedValue()),
        ForeignKeyConstraint(['foobar_id'], ['foobar.id']),
    )

class Foobar(Base):
    __table__ = _foobar_table.join(_foobar_data_cache_table)
    _id = _foobar_table.c.id

    def __init__(self, some_data):
        self.some_data = some_data

    __mapper_args__ = {"exclude_properties": [_foobar_data_cache_table.c.foobar_id]}

if __name__ == '__main__':
    engine = create_engine('postgresql://scott:tiger@localhost/test', echo=True)

    Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)

    Session = sessionmaker(bind=engine)
    session = Session()

    my_foobar = Foobar('Dummy data')
    session.add(my_foobar)

    # simulate your trigger...
    session.flush()
    assert session.scalar(_foobar_data_cache_table.count()) == 0
    session.execute(
        _foobar_data_cache_table.insert(),
        params=dict(
                foobar_id=my_foobar._id,
                computed_data="some computed data"
                )
    )

    session.commit()

    obj = session.query(Foobar).first()
    assert obj.computed_data == "some computed data"

とは言え、 との間のリンクに従来のを使用すると、マッピングされた に結合するのではなく、この全体のマッピングがより簡単になりますrelationship()_foobar_foobar_data_cache_tablejoin()

于 2012-12-23T17:02:24.220 に答える