.. meta:: :keywords: web2py, framework, DAL, データベース抽象化レイヤ, Set class, Setクラス .. currentmodule:: dal .. _class_set: Set === Setはレコードセット定義のクラスです。さらに定義したレコードセットに対する、レコードの取得・更新・削除といったメソッドも 持っています。 参考: :ref:`Set Rows Row クラスについて` .. include:: class_diagrams/set.html .. _class_set_instantiate: インスタンス化 -------------- :ref:`class_dal` インスタンスを関数として呼ぶと、Setインスタンスを返します。これでレコードセットを 定義したことになります。また Setインスタンスを生成した段階では、データーベースクエリは実行されません [#f1]_ 。 .. topic:: 【 Setインスタンスの生成 】 * 生成・・・・・・ DALインスタンスを関数呼び出しします。 :: >>> set = db(db.person.id > 0) DALインスタンスのパラメータには、 :ref:`class_query` インスタンスを指定します。Queryインスタンスはレコードセットの条件になります。 :ref:`class_table` インスタンスや :ref:`class_field` インスタンスもパラメータに指定できます。 この場合は、Queryインスタンスへと内部で自動変換します。 :: >>> set = db(db.person) >>> set = db(db.person.name) 変換後の構文 :: >>> set = db(db.person.id > 0) >>> set = db(db.person.name != None) Setインスタンスの作成ではDALインスタンスを使用しますが、この時次のパラメータを取ることが可能です。 ignore_common_filters コモンフィルタで使用するパラメータです。詳細は :ref:`common_filter` を参照してください。 メソッド -------- メソッド説明用サンプルでは次のテーブルを利用します。 :: db.define_table('person', Field('name'), format='%(name)s') db.define_table('dog', Field('owner_id', db.person), Field('name')) .. method:: Set.select([, *fields, **attributes]) -> Rowsインスタンス レコードセットのデータを取得します。 パラメータとしてフィールドやオプションを指定します。戻り値は :ref:`class_rows` インスタンスになります。 :: >>> rows = db(db.person).select(db.person.id, db.person.name) DALインスタンスのパラメータは省略することも可能です。 :: >>> rows = db().select(db.person.id, db.person.name) \*fields 戻り値に含めるフィールドを :ref:`class_field` インスタンスで指定します。 このパラメータは複数設定可能です。 Fieldインスタンスの代わりに、Tableインスタンスに **ALL** を付けて指定することも可能です。これは SQLALL クラスの インスタンスになります :: >>> rows = db().select(db.person.ALL) SQLALLインスタンスを指定すると、テーブルの全フィールドを指定したことになります。つまり次の構文と同等になります。 :: >>> rows = db(db.person).select() \*\*attributes オプションを指定できます。複数のオプションを指定可能です。設定可能なオプションは次の通りです。 * orderby ソート順を指定できます。ソート指定での注意事項がいくつかあります。 * パラメータにはソートしたいFieldインスタンスを設定します。 * チルダ記号(~)をつけると逆ソートになります。 * を指定するとソート順がランダムになります。 * 複数のフィールドでソートしたい時は、縦棒(|)でフィールド同士を連結させます。 :: >>> for row in db().select(db.person.ALL, orderby=db.person.name): ... print row.name Cosotama Pacedada Socepopa :: >>> for row in db().select(db.person.ALL, orderby=~db.person.name): ... print row.name Socepopa Pacedada Cosotama :: >>> for row in db().select(db.person.ALL, orderby=''): ... print row.name Pacedada Cosotama Socepopa はGAEではサポートしていません。同様にサンポートしていない環境では、次のように記述すると同じ 結果を得ることができます。 :: >>> import random >>> for row in db().select(db.person.ALL).sort(lambda x :random.random()): ... print row.name Cosotama Socepopa Pacedada :: >>> for row in db().select(db.person.ALL, orderby=db.person.name|db.person.id): ... print row.name, row.id Cosotama 3 Pacedada 1 Socepopa 2 参考: `orderby, groupby, limitby, distinct `_ | `orderby, groupby, limitby, distinct(日本語) `_ * groupby 同じ値をグループ化します。パラメータにはグループ化するフィールドのFieldインスンタンスを指定します。 :: >>> for row in db().select(db.dog.ALL, groupby=db.dog.owner_id): ... print row.name, row.owner_id.name Comoceta Socepopa Cosasamo Cosotama 注意点としては、このオプションはGAEではサポートしていません。 * distinct 重複したデータを取り除きます。パラメータにTrueを指定すれば、このオプションが有効になります。 :: >>> for row in db().select(db.dog.owner_id, distinct=True): ... print row.owner_id.name Socepopa Cosotama このオプションを有効にする場合はフィールド選択で、全フィールドを選択しない、もしくはidフィールドを除外することが 重要です。選択した場合、全レコードが重複無しの状態になるからです。 この他、distinctにブール値ではなく式を指定することが可能です。 :: >>> print db().select(db.dog.owner_id, distinct=db.dog.owner_id) この場合、SQLには次のように変換されます。 :: SELECT DISTINCT ON (dog.owner_id) dog.owner_id FROM dog; しかし、DISTINCT ON の構文は一部のデータベースでは動作しないようです。特に SQLite では動作しません。 * limitby 開始行と終了行を指定することで、テーブルデータの一部を切り抜くことができます。 パラメータは開始行と終了行で、タプルで指定します。またorderbyとも一緒に使用することは可能です。 :: >>> for row in db().select(db.person.ALL, limitby=(0,2)): ... print row.name Pacedada Socepopa :: >>> for row in db().select(db.person.ALL, orderby=db.person.name, limitby=(0,2)) ... print row.name Cosotama Pacedada * having 集計関数(メソッド)を用いた条件設定を行えます。これはSQL文のHAVING句と同じ動きをします。 :: >>> for row in db().select(db.dog.name, groupby=db.dog.owner_id, having='count(*) >= 3'): ... print row.name このオプションはGAEではサポートしていないと思われます。 * join 内部結合(inner join)の設定を行います。結合させるテーブルのフィールドを \*fields に指定する必要があります。 パラメータには、結合するテーブルのTableインスタンスメソッド on と、結合条件となるQueryインスタンスを設定します。 :: join = Tableインスタンス.on(Queryインスタンス) on は :meth:`~dal.Table.on` メソッドです。 複数のjoinを設定することも可能です。その場合、リストもしくはタプル形式で指定してください。 サンプルは、 :ref:`selectメソッドでの内部結合定義 ` を参照ください。 なお :meth:`dal.Set._init__` コンストラクタでも内部結合の設定が可能です。 * left 左外部結合(left outer join)の設定を行います。パラメータは joinオプションと同様に、結合するテーブルの Tableインスタンスメソッド on と、結合条件となるQueryインスタンスを設定します。 :: left = Tableインスタンス.on(Queryインスタンス) on は :meth:`~dal.Table.on` メソッドです。 複数のleftを設定することも可能です。その場合、リストもしくはタプル形式で指定してください。 サンプルは、 :ref:`左外部結合 ` を参照ください。 参考: `Left Outer Join `_ | `左外部結合 `_ * cache 取得したレコードセットのキャッシュを設定します。キャッシュを設定を行うと、システムのパフォーマンスが 向上します。 パラメータはタプルで渡します。タプルの最初の要素は、キャッシュモデルの指定です(cache.ram , cache.disk など)。 第二要素は、キャッシュの有効期限です(秒単位)。 :: def person_list(): rows = db().select(db.person.ALL, cache=(cache.ram, 60)) return dict(rows=rows) キャッシュの有効期限は、キャッシュを保存した時ではなく、キャッシュの読み込み時の有効期限です。 つまりサンプルでは、最終のデータベースIOから、60秒以内だったらデータベースから取り出すことはなく、cache.ram から取り出します。 60秒を超えている場合、データベースから取り出すと共に、cache.ram にデータをキャッシュします。 * cacheable cacheable を True に指定すると、検索結果の Rows がシリアライズ化されキャッシュされます。 デフォルトは False です。 True の場合、 :func:`~Row.update_record` 、 :func:`~Row.delete_record` といった組み込み関数は使用できません。 使用できなくでも問題がない場合、cacheableをTrueにするだけで、selectメソッドを高速化します。 もしcacheableがFalseで、cacheが設定されている場合、データベースでの問い合わせ結果だけがキャッシュされます。 cacheableもTrueの場合は、検索結果のRowsもキャッシュされるため、非常に高速になります。 :: def person_list(): rows = db().select(db.person.ALL, cache=(cache.ram, 60), cacheable=True) return dict(rows=rows) 参考: `Caching Selects `_ | `選択のキャッシュ `_ 戻り値 Rowsインスタンスです。 参考: `select `_ | `select(日本語) `_ .. method:: Set.count([, distinct]) -> レコード数 Setインスタンスに含まれるレコード数を返します。 :: >>> db(db.dog).count() 14 distinct データ重複の制御を行うパラメータです。パラメータにFieldインスタンスを指定すると、指定したフィールドの データ重複を排除したレコード件数を返します。デフォルト値はNoneです。 :: >>> db(db.dog).count(distinct=db.dog.owner_id) 3 >>> db(db.dog).count(db.dog.owner_id) 3 戻り値 レコード数です。 参考: `count, delete, update `_ | `count, isempty, delete, update(日本語) `_ .. method:: Set.delete() -> 削除レコード数 Setインスタンスに含まれるレコードの削除を行います。 :: >>> db(db.dog.id >= 5).delete() 10 戻り値 削除したレコード数です。 .. method:: Set.isempty() -> ブール値(True/False) Setインスタンスのレコードが空かどうかを返します。 True の場合は空です(レコードが存在しません)。 :: >>> db(db.person.name.startswith('A')).isempty() # Aで始まるレコードの存在チェック True >>> db(db.person.name.startswith('C')).isempty() # Cで始まるレコードの存在チェック False 戻り値 | True -> レコードが存在しません | False -> レコードが存在します .. method:: Set.update(\*\*update_fields) -> 更新レコード数 Setインスタンスに含まれるレコードの更新を行います。 :: >>> db(db.dog.id == 4).update(owner_id=1) # idが4のレコードに対し、owner_idフィールドの値を1に更新 1 \*\*update_fields パラメータ名にフィールド名をパラメータに更新値をセットします。複数設定が可能です。 戻り値 更新したレコード数です。 .. method:: Set.update_naive(\*\*update_fields) -> 更新レコード数 :ref:`callback_function` を起動しないで、Setインスタンスに含まれるレコードの更新を行います。 :: >>> db(db.dog.id == 4).update_naive(owner_id=1) 1 \*\*update_fields パラメータ名にフィールド名をパラメータに更新値をセットします。複数設定が可能です。 戻り値 更新したレコード数です。 .. method:: Set.validate_and_update(\*\*update_fields) -> Rowオブジェクト Setインスタンスに含まれるレコードに対して、レコード更新値をバリデータした後、更新を行います。 バリデータはフォームやSQLフォームで実施されるため、通常ではこのメソッドは使用しません。 :: >>> db(db.person.id==4).validate_and_update(name='a'*513) # バリデータエラー }> >>> db(db.person.id==4).validate_and_update(name='a'*512) # レコード更新成功 }> >>> db(db.person.id==4).update(name='a'*513) # updateメソッドではバリデータを実施しないので更新される 1 nameフィールドはstring型のため、デフォルトでは512文字より大きい場合バリデータで制限されます。 \*\*update_fields パラメータ名にフィールド名をパラメータに更新値をセットします。複数設定が可能です。 戻り値 Rowオブジェクトを返します。エラーがある場合は 'errors' の辞書値に、フィールド名がキーでエラーメッセージが値の辞書型データが設定されます。 更新成功時は、'updated' の辞書値に更新レコード数が設定されます。 よく似た機能で、 :meth:`~dal.Table.validate_and_insert` も存在します。 .. method:: Set.nested_select([, *fields, **attributes]) -> Expressionインスタンス :meth:`~dal.Set.select` の実行結果をExpressionインスタンスにします。これにより、ネストされたDAL構文を作成できます。 例えば次のように、Selectの実行結果を使って値を更新する場合、データベースに対して2度命令を発行する必要があります。 :: >>> person_id = db(db.person.name=='Nathanial Azzano').select().first()['id'] >>> db(db.dog.id==4).update(owner_id=person_id) 1 しかしnested_selectメソッドを利用すれば、ネストしたSelectを直接値に代入でき、データベースへの一度の命令発行でレコード更新が可能になります。 :: >>> person_id = db(db.person.name=='Nathanial Azzano').nested_select(db.person.id) >>> db(db.dog.id==4).update(owner_id=person_id) 1 ここで person_id の中身を見てみます。 :: >>> print person_id (SELECT person.id FROM person WHERE (person.name = 'Nathanial Azzano')) SQL文になっています。これは遅延実行されることを示しています。 :meth:`~dal.Set._select` に似ていますが、nested_selectは :ref:`class_expression` オブジェクトを返します。 またnested_selectを使わない方法は、``first()['id']`` を使っていることに注意してください。 これは通常の :meth:`~dal.Set.select` は :ref:`class_rows` を返すためです。 nested_selectを使用した方が、より実行効率が高いことが分かると思います。 .. method:: Set._select([*fields, **attributes]) -> SQL文 .. method:: Set._count([distinct=None]) -> SQL文 .. method:: Set._update(**update_fields) -> SQL文 .. method:: Set._delete() -> SQL文 SQL文を返します。 :ref:`generate_sql_statements` も参照ください。 .. _class_set_call: 関数へのエミュレーション ------------------------ Setインスタンスを関数呼び出しした場合、パラメータの Query インスタンスが **and** 条件として追加されます。 :: >>> set = db(db.person.name.startswith('C')) # nameフィールドが C で始まるレコードセットの定義 >>> set = set(db.person.name.endswith('A')) # 最後が A で終わる条件を追加 >>> print set.select() person.id,person.name 4,Cecemosa 8,Codadama 次のようにも記述できます。 :: >>> print db(db.person.name.startswith('C'))(db.person.name.endswith('O')).select() person.id,person.name 6,Cedumoco 7,Ceduduco Queryクラスの :ref:`class_query_logical_operator` で記述すると次のようになります。 :: >>> print db(db.person.name.startswith('C') & db.person.name.endswith('O')).select() person.id,person.name 6,Cedumoco 7,Ceduduco ---- .. [#f1] Pythonのinstance型オブジェクトではありませんが、簡単に説明するために **インスタンス** という言葉を使用しています。