web-dev-qa-db-ja.com

sqlalchemy動的フィルタリング

SQLAlchemyORMを使用して動的フィルタリングを実装しようとしています。

StackOverflowを調べていたところ、非常によく似た質問が見つかりました: SQLALchemy dynamic filter_by

それは私にとっては便利ですが、十分ではありません。

だから、ここにコードのいくつかの例があります、私は書き込もうとしています:

# engine - MySQL engine
session_maker = sessionmaker(bind=engine)
session = session_maker()

# my custom model
model = User

def get_query(session, filters):
    if type(filters) == Tuple:
        query = session.query(model).filter(*filters)
    Elif type(filters) == dict:
        query = session.query(model).filter(**filters)
    return query

それから私はそれを非常に似たもので再利用しようとしています:

filters = (User.name == 'Johny')
get_query(s, filters) # it works just fine

filters = {'name': 'Johny'}
get_query(s, filters)

2回目の実行後、いくつかの問題があります。

TypeError: filter() got an unexpected keyword argument 'name'

filtersを次のように変更しようとしているとき:

filters = {User.name: 'Johny'}

それは戻ります:

TypeError: filter() keywords must be strings

ただし、手動クエリでは正常に機能します。

s.query(User).filter(User.name == 'Johny')

フィルタの何が問題になっていますか?

ところで、それはケースのためにうまくいくように見えます:

filters = {'name':'Johny'}
s.query(User).filter_by(**filters)

しかし、言及された投稿からの推奨事項に従って、私はfilterだけを使用しようとしています。

filterの代わりにfilter_byを使用できるのが1つだけの場合、これら2つの方法に違いはありますか?

10
smart

あなたの問題は、filter_byはキーワード引数を取りますが、filterは式を取ります。したがって、filter_by ** mydictのdictを展開すると機能します。フィルタを使用すると、通常、1つの引数を渡します。これはたまたま式です。したがって、** filters dictをfilterに展開すると、理解できない一連のキーワード引数をfilterに渡します。

保存されたフィルター引数のdictからフィルターのセットを構築する場合は、クエリの生成的な性質を使用して、フィルターを適用し続けることができます。例えば:

# assuming a model class, User, with attributes, name_last, name_first
my_filters = {'name_last':'Duncan', 'name_first':'Iain'}
query = session.query(User)
for attr,value in my_filters.iteritems():
    query = query.filter( getattr(User,attr)==value )
# now we can run the query
results = query.all()

上記のパターンの優れている点は、複数の結合された列で使用できること、and_とor_を使用して 'ands'と 'ors'を作成できること、<=または日付の比較を実行できることです。 filter_byをキーワードで使用するよりもはるかに柔軟性があります。唯一の注意点は、結合の場合、誤ってテーブルを2回結合しようとしないように少し注意する必要があり、複雑なフィルタリングの結合条件を指定する必要がある場合があることです。私はこれをかなり複雑なドメインモデルの非常に複雑なフィルタリングで使用し、魅力のように機能します。結合を追跡するために、entities_joinedのdictを実行し続けます。

17
Iain Duncan
class Place(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    search_id = db.Column(db.Integer, db.ForeignKey('search.id'), nullable=False)

    @classmethod
    def dinamic_filter(model_class, filter_condition):
        '''
        Return filtered queryset based on condition.
        :param query: takes query
        :param filter_condition: Its a list, ie: [(key,operator,value)]
        operator list:
            eq for ==
            lt for <
            ge for >=
            in for in_
            like for like
            value could be list or a string
        :return: queryset
        '''
        __query = db.session.query(model_class)
        for raw in filter_condition:
            try:
                key, op, value = raw
            except ValueError:
                raise Exception('Invalid filter: %s' % raw)
            column = getattr(model_class, key, None)
            if not column:
                raise Exception('Invalid filter column: %s' % key)
            if op == 'in':
                if isinstance(value, list):
                    filt = column.in_(value)
                else:
                    filt = column.in_(value.split(','))
            else:
                try:
                    attr = list(filter(lambda e: hasattr(column, e % op), ['%s', '%s_', '__%s__']))[0] % op
                except IndexError:
                    raise Exception('Invalid filter operator: %s' % op)
                if value == 'null':
                    value = None
                filt = getattr(column, attr)(value)
            __query = __query.filter(filt)
        return __query

次のように実行します。

places = Place.dinamic_filter([('search_id', 'eq', 1)]).all()
0
vgsantoniazzi