2597


12月からPythonを学び始めました。FlaskとSQLAlchemyを使ってアプリを作ろうとしています。

困ったのが、検索条件の組み立てです。ベタにやるとこんな感じになると思います。名前と苗字を検索条件に入れてユーザーを探すイメージです。

[code]

    def search():
      firstname =''
      lastname = ''
      rs = session.Query(User)
      if firstname:
         rs = rs.filter(User.firstname==firstname)
      if lastname:
         rs = rs.filter(User.lastname==lastname)
    
[/code]

これしか方法が無いなら諦めるのですが、例えば検索条件に入れた変数(この場合はfirstname,lastname)がNoneとか空文字だったらfilterを通しても無視されるような感じに出来たら良いなぁ...と。

ユーザーから与えられる複数のリクエストパラメータに対して、よりスマートに検索条件を組み立てる方法があればご教示頂けると幸いです。


CC BY-SA 3.0
2014-12-25T17:18:20.750
2593
2019-04-11T01:38:25.237
2019-04-11T01:38:25.237
754
5766
question
python sqlalchemy
SQLAlchemyの検索条件組み立て


細かい実行確認はしてないですが、多分

[code]

    def search():
      firstname =''
      lastname = ''
      rs = session.Query(User)
      rs = rs.filter((User.firstname==firstname) | (not firstname))
      rs = rs.filter((User.lastname==lastname) | (not lastname))
    
[/code]

で、いけるんじゃないでしょうか（ただ、フィルタにはなってしまいますが）。

`&` `|` は SQLAlchemyがビット演算子をオーバーロードしているものなので、 and, or ではありません。

`&` `|` を使う代わりに `or_`, `and_` というのもあったと思います。

[code]

      rs.filter(or_(User.firstname==firstname, not firstname))
    
[/code]

そうじゃなくて、イコールの比較がそもそもいらないなら （ 検索条件値が SQLでいうところの`firstname = '' or firstname is
null`の時、 DBの値に依らず trueにしたいなら）

[code]

      rs.filter(not firstname).filter(not lastname)
    
[/code]

で、いいと思います。


CC BY-SA 3.0
2014-12-25T18:25:21.053
2597
2014-12-28T08:38:52.100
2014-12-28T08:38:52.100
728
728
2593
answer


やはりベタですが、以下の様にも書けるかと思います。

[code]

    rs.filter(and_(and_(User.firstname != '',
                        User.firstname != None,
                        User.firstname == firstname),
                   and_(User.lastname  != '',
                        User.lastname  != None,
                        User.lastname  == lastname))
    
[/code]

`firstname` や `lastname` 変数が空文字列や None であってもマッチするレコードはないはずです。まぁ、DB テーブルの当該カラムに
NOT NULL 制約を設定してしまえば良いのかも知れませんが、空文字列と NULL を別個に扱う DB では不十分かもしれません。

ところで、上記のフィルタがどの様な WHERE 句(SQL)になるのかを確認したい場合には、そのまま print します。

[code]

    print and_(and_(User.firstname != '',
                    User.firstname != None,
                    User.firstname == firstname),
               and_(User.lastname  != '',
                    User.lastname  != None,
                    User.lastname  == lastname))
    =>
    "User".firstname != :firstname_1 AND "User".firstname IS NOT NULL AND
    "User".firstname = :firstname_2 AND "User".lastname != :lastname_1 AND
    "User".lastname IS NOT NULL AND "User".lastname = :lastname_2
    
[/code]

変数などはキーワード変数に置き換えられてしまいますが、確認には十分かと思います。


CC BY-SA 3.0
2014-12-25T18:26:01.720
2598
2014-12-25T19:02:40.853
2014-12-25T19:02:40.853
2593
answer


質問に書かれているようにPython側のif文で filter 追加を分岐させるのが一般的によいパフォーマンスを得られると思います。
filterの中でどうにかしようとすると生成されるSQLが複雑になり index が利用されなくなる可能性が高くなります。


CC BY-SA 3.0
2014-12-28T12:29:15.713
2747
2014-12-28T12:29:15.713
2167
2593
answer


いろいろな条件から、 `filter()` に指定する引数を動的に生成するという話しなら、こんな感じでも書けます。

[code]

    def query(**kwargs):
        exprs = [(getattr(User, name) == value) 
            for name, value in kwargs.items() if value is not None]
    
        return session.query(User).filter(*exprs).all()
    
[/code]

呼び出すときは、必要なフィールド名と値を指定して

[code]

    print(list(query(firstname='john', lastname='smith')))
    
[/code]

な感じで。

演算子の指定順はバラバラになりますので、検索のパフォーマンスに影響したりするかもしれませんが...


CC BY-SA 3.0
2014-12-29T04:13:02.480
2767
2014-12-29T04:21:41.537
2014-12-29T04:21:41.537
2913
2913
2593
answer