3

SQL Alchemyで簡単に処理できるはずの小さな問題がありますが、正しく処理できないようです。2つのテーブルがあり、1つは親テーブルで、もう1つは子テーブルです。子レコードごとに一意のIDが必要ですが、一意の親レコードのコンテキストのみが必要です。

私は宣言型ベースのアプローチを使用しています。

FKとリレーション関数を使用して親子リレーションシップを設定します。私が達成しようとしているのは、Categoryの一意の名前グループ内の最大CategoryID値を検索し、それを1つ増やす疑似自動インクリメント関数のようなものを取得することです。さまざまなデフォルト関数を使用してみましたが、挿入時にCategoryUniqueNameを指定できないという問題が発生しました。func.max(CategoryItems.CategoryID)のようなものを選択しようとしたときに、ルックアップクエリに正しいフィルターが適用されるように、CategoryItems.CategoryUniqueNameの現在の値を渡す方法が見つかりません。クエリをハードコーディングすると、問題なく機能します。これは私がうまくいくと思っていることですが、繰り返しになりますが、フィルターの一意の値を指定する方法が見つかりません。

unique_group='my_group'
result=con.query(func.max(CategoryItems.CategoryID)).filter(and_(
        CategoryItems.CategoryUniqueName==unique_group, 
    )).one()

クラスを以下に示します。標準のSQLAlchemy内でこれを実現する方法に関するガイダンスに感謝します。いつでも値を検索して、同じトランザクション内で直接指定できることはわかっていますが、他の場所に追加のロジックを必要としないスタンドアロンのSQLAlchemyアプローチを考え出そうとしています。

class Category(Base):
    __tablename__ = 'parent_table'
    __table_args__ = {'mysql_engine':'InnoDB', 'useexisting':True}

    CategoryUniqueName = Column(Unicode(255), primary_key=True)
    CategoryGroupName = Column(Unicode(255), nullable=False)
    CategoryGroupMemo = Column(UnicodeText)
    SortOrder = Column(Integer, index=True)
    IsLocked = Column(Boolean, default=0)

class CategoryItems(Base):
    __tablename__ = 'child_table'
    __table_args__ = {'mysql_engine':'InnoDB', 'useexisting':True}

    CategoryUniqueName = Column(Unicode(255), ForeignKey(Category.CategoryUniqueName), primary_key=True)
    CategoryID = Column(Integer, primary_key=True, autoincrement=False)
    CategoryName = Column(Unicode(255), nullable=False, index=True)
    CategoryMemo = Column(UnicodeText)
    CategoryImage = Column(Unicode(255))
    CategoryFlex1 = Column(Unicode(255), index=True)
    CategoryFlex2 = Column(Unicode(255), index=True)
    CategoryFlex3 = Column(Unicode(255), index=True)
    SortOrder = Column(Integer, index=True)

    category_group = relation(
        Category, 
        backref=backref(
            'items', 
            order_by=SortOrder, 
            collection_class=ordering_list('SortOrder'), 
            cascade="all, delete, delete-orphan"
    ))
4

3 に答える 3

3

私は行くための3つの方法を見ます:

  1. 最も明白で十分に文書化されています。before_insert()挿入されたパラメータをフックで置き換えるマッパー拡張機能を作成します。
  2. default関数を引数として渡します。この関数は、context必要なすべてのデータを含むパラメーターを使用して呼び出されます:context.compiled_parameters[0]['CategoryUniqueName']context.connection
  3. パラメータを渡しFetchedValue()server_defaultトリガーを使用してジョブサーバー側を実行します。

これらのソリューションはすべて、ddaaによって言及された競合状態を持っています。競合状態の場合、コードはデータベースの状態を壊しませんが、主キーが適切に定義されている場合は例外として失敗します(コードには当てはまりません!)。まれに、一部のアプリケーションが失敗する(Webアプリケーションで500ページを表示する)ことが許容される場合があります。

CategoryIDを主キーとして定義していることに注意してください。これにより、列の異なる値に同じ番号を再利用することはできなくなりCategoryUniqueNameます。2列の複合プライマリインデックスに変更する必要があります。

于 2009-12-10T09:53:56.843 に答える
1

デニスの洞察に感謝します。私はオプション1と2の両方で遊んだのですが、それらは非常にうまく機能します。コンテキストパラメータがオプション2のキーでした。自動的に渡されていることに気づきませんでした。私が注意したことの1つは、オプション1は、単一のユーザーが複数のレコードを送信する場合でも競合状態を導入することでした。これはフラッシュとセーブのタイミングと関係があると思います。ただし、オプション2は見事に機能します。

これは、デフォルトのパラメータから呼び出される小さな関数です。

def getNextId(context):
    unique_name=context.compiled_parameters[0]['CategoryUniqueName']
    sql = """
        SELECT MAX(CategoryID)
        FROM child_table
        WHERE CategoryUniqueName='%s'""" % (unique_name, )

    result = context.connection.execute(sql).fetchone()[0]
    if result > 0:
         return result + 1
    else:
        return 1 
于 2009-12-10T18:55:49.313 に答える
0

つまり、達成しようとしているのは、CategoryUniqueNameが異なるCategoryItemの各セットに、CategoryIdを個別に自動インクリメントさせることです。

そうであれば、現在のアプローチ(追加するCategoryItemsのサブセットでCategoryIdの現在の最大値を取得する)は壊れています。固有の競合状態があります。同時挿入では同じCategoryIdが使用されます。

本当にCategoryIdを個別にインクリメントする必要がありますか?通常の自動インクリメント機能を使用してみませんか。特定のCategoryUniqueNameのシーケンスCategoryIdには穴がありますが、それは本当に問題ですか?

連続したシーケンス番号が必要な場合は、手動ロックを使用して競合状態を防ぐ必要があります。

于 2009-12-09T12:05:44.427 に答える