3.6. Set

Setはレコードセット定義のクラスです。さらに定義したレコードセットに対する、レコードの取得・更新・削除といったメソッドも 持っています。

参考: Set Rows Row クラスについて

Setクラス — レコードセット定義に関するクラス

select()

レコードセット取得

count()

レコード件数取得

isempty()

レコード存在チェック

update()

レコードの更新

update_naive()

レコードの更新
(コールバック関数の無視)

validate_and_update()

バリデータとレコード更新

delete()

レコードの削除

nested_select()

ネストしたSELECT

_select()

SQL文生成

_count()

SQL文生成

_update()

SQL文生成

_delete()

SQL文生成

3.6.1. インスタンス化

DAL インスタンスを関数として呼ぶと、Setインスタンスを返します。これでレコードセットを 定義したことになります。また Setインスタンスを生成した段階では、データーベースクエリは実行されません [1]

【 Setインスタンスの生成 】

  • 生成・・・・・・ DALインスタンスを関数呼び出しします。
    >>> set = db(db.person.id > 0)
    

DALインスタンスのパラメータには、 Query インスタンスを指定します。Queryインスタンスはレコードセットの条件になります。

Table インスタンスや 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

コモンフィルタで使用するパラメータです。詳細は コモンフィルタ を参照してください。

3.6.2. メソッド

メソッド説明用サンプルでは次のテーブルを利用します。

db.define_table('person',
    Field('name'), format='%(name)s')

db.define_table('dog',
    Field('owner_id', db.person),
    Field('name'))
Set.select([*fields, **attributes]) → Rowsインスタンス

レコードセットのデータを取得します。

パラメータとしてフィールドやオプションを指定します。戻り値は Rows インスタンスになります。

>>> rows = db(db.person).select(db.person.id, db.person.name)

DALインスタンスのパラメータは省略することも可能です。

>>> rows = db().select(db.person.id, db.person.name)
*fields

戻り値に含めるフィールドを Field インスタンスで指定します。 このパラメータは複数設定可能です。

Fieldインスタンスの代わりに、Tableインスタンスに ALL を付けて指定することも可能です。これは SQLALL クラスの インスタンスになります

>>> rows = db().select(db.person.ALL)

SQLALLインスタンスを指定すると、テーブルの全フィールドを指定したことになります。つまり次の構文と同等になります。

>>> rows = db(db.person).select()
**attributes

オプションを指定できます。複数のオプションを指定可能です。設定可能なオプションは次の通りです。

  • orderby

    ソート順を指定できます。ソート指定での注意事項がいくつかあります。

    • パラメータにはソートしたいFieldインスタンスを設定します。
    • チルダ記号(~)をつけると逆ソートになります。
    • <random> を指定するとソート順がランダムになります。
    • 複数のフィールドでソートしたい時は、縦棒(|)でフィールド同士を連結させます。
    >>> 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='<random>'):
    ...      print row.name
    Pacedada
    Cosotama
    Socepopa
    

    <random>は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, distinctorderby, 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 は on() メソッドです。 複数のjoinを設定することも可能です。その場合、リストもしくはタプル形式で指定してください。

    サンプルは、 selectメソッドでの内部結合定義 を参照ください。 なお dal.Set._init__() コンストラクタでも内部結合の設定が可能です。

  • left

    左外部結合(left outer join)の設定を行います。パラメータは joinオプションと同様に、結合するテーブルの Tableインスタンスメソッド on と、結合条件となるQueryインスタンスを設定します。

    left = Tableインスタンス.on(Queryインスタンス)
    

    on は on() メソッドです。 複数のleftを設定することも可能です。その場合、リストもしくはタプル形式で指定してください。

    サンプルは、 左外部結合 を参照ください。

    参考: 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 の場合、 update_record()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インスタンスです。

参考: selectselect(日本語)

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, updatecount, isempty, delete, update(日本語)

Set.delete() → 削除レコード数

Setインスタンスに含まれるレコードの削除を行います。

>>> db(db.dog.id >= 5).delete()
10
戻り値
削除したレコード数です。
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 -> レコードが存在します
Set.update(**update_fields) → 更新レコード数

Setインスタンスに含まれるレコードの更新を行います。

>>> db(db.dog.id == 4).update(owner_id=1)       # idが4のレコードに対し、owner_idフィールドの値を1に更新
1
**update_fields
パラメータ名にフィールド名をパラメータに更新値をセットします。複数設定が可能です。
戻り値
更新したレコード数です。
Set.update_naive(**update_fields) → 更新レコード数

コールバック関数 を起動しないで、Setインスタンスに含まれるレコードの更新を行います。

>>> db(db.dog.id == 4).update_naive(owner_id=1)
1
**update_fields
パラメータ名にフィールド名をパラメータに更新値をセットします。複数設定が可能です。
戻り値
更新したレコード数です。
Set.validate_and_update(**update_fields) → Rowオブジェクト

Setインスタンスに含まれるレコードに対して、レコード更新値をバリデータした後、更新を行います。 バリデータはフォームやSQLフォームで実施されるため、通常ではこのメソッドは使用しません。

>>> db(db.person.id==4).validate_and_update(name='a'*513)               # バリデータエラー
<Row {'updated': None, 'errors': <Row {'name': 'enter from 0 to 512 characters'}>}>

>>> db(db.person.id==4).validate_and_update(name='a'*512)               # レコード更新成功
<Row {'updated': 1, 'errors': <Row {}>}>

>>> db(db.person.id==4).update(name='a'*513)    # updateメソッドではバリデータを実施しないので更新される
1

nameフィールドはstring型のため、デフォルトでは512文字より大きい場合バリデータで制限されます。

**update_fields
パラメータ名にフィールド名をパラメータに更新値をセットします。複数設定が可能です。
戻り値
Rowオブジェクトを返します。エラーがある場合は ‘errors’ の辞書値に、フィールド名がキーでエラーメッセージが値の辞書型データが設定されます。 更新成功時は、’updated’ の辞書値に更新レコード数が設定されます。

よく似た機能で、 validate_and_insert() も存在します。

Set.nested_select([*fields, **attributes]) → Expressionインスタンス

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文になっています。これは遅延実行されることを示しています。 _select() に似ていますが、nested_selectは Expression オブジェクトを返します。

またnested_selectを使わない方法は、first()['id'] を使っていることに注意してください。 これは通常の select()Rows を返すためです。

nested_selectを使用した方が、より実行効率が高いことが分かると思います。

Set._select([*fields, **attributes]) → SQL文
Set._count([distinct=None]) → SQL文
Set._update(**update_fields) → SQL文
Set._delete() → SQL文

SQL文を返します。 SQL文の生成 も参照ください。

3.6.3. 関数へのエミュレーション

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クラスの 論理演算子 で記述すると次のようになります。

>>> print db(db.person.name.startswith('C') & db.person.name.endswith('O')).select()
person.id,person.name
6,Cedumoco
7,Ceduduco

[1]Pythonのinstance型オブジェクトではありませんが、簡単に説明するために インスタンス という言葉を使用しています。