データ取得

Posted by muchag | CakePHP 3.x | 2017-12-23 (土) 0:40:15


公式:データベースアクセス & ORM
公式:クエリービルダー
公式:データの取り出しと結果セット
公式:データの保存

【環境】
CakePHP: 3.5.8
php: 7.1.5
取得タイプ

SQL も参照のこと。


// 全行取得
$results = $articles->find()->all();

// 全行を配列に変換して取得
$results = $articles->find()->toArray();

// 先頭の1行だけ取得
$results = $articles->find()->first();
リレーション

公式:クエリービルダー 関連付くデータをロードする
contain キーワードを利用してリレーション設定を行う。

オプションでもメソッドでも設定可能。


// コントローラーやテーブルのメソッド内で

// find() のオプションとして
$query = $articles->find('all', ['contain' => ['Authors', 'Comments']]);

// クエリーオブジェクトのメソッドとして
$query = $articles->find('all');
$query->contain(['Authors', 'Comments']);
モデル名

上記で設定した、Authors, Comments はモデル名といえばモデル名なんだけど
Table クラスで設定したリレーションに基づくので
Table クラスでエイリアスを設定した場合は
エイリアス名を記述する。


$this->belongsTo('Hoges', [
    'foreignKey' => 'hoge_id',
    'joinType' => 'INNER',
    'className' => 'Plugin.Hoges'
]);

この例において、’Plugin.Hoges’ と書きたくなるところを
‘Hoges’ と書く。

入れ子(階層)

// 配列記法?
$query = $articles->find()->contain([
    'Authors' => ['Addresses'], 'Comments' => ['Authors']
]);

// ドット記法
$query = $articles->find()->contain([
    'Authors.Addresses',
    'Comments.Authors'
]);

// 3階層
$query = $products->find()->contain([
    'Shops.Cities.Countries',
]);
詳細設定

公式:クエリービルダー contain に条件を渡す
contain 内、無名関数にて対応。
引数が \Cake\ORM\Query $query。


// コントローラーやテーブルのメソッド内で
// 3.5.0 より前は、 contain(['Comments' => function () { ... }]) を使用

$query = $articles->find()->contain('Comments', function ($q) {
    return $q
        ->select(['body', 'author_id'])
        ->where(['Comments.approved' => true]);
});
ソート

公式:クエリービルダー 関連を含んだソート

再設定

クエリー上の contain を再設定する必要があるなら、第2引数に true を指定することができます。

公式:クエリービルダー 関連付くデータをロードする

現在のところ意味不明。

/vendor/cakephp/cakephp/src/ORM/Query.php

public function contain($associations = null, $override = false)
contain オプション

オプションでの設定でも、相当なことができた。


$query = $articles->find('all', ['contain' => [
	'Authors' => [
		'AuthorProfiles'
	],
	'Comments' => [
		'sort' => ['Comments.created' => 'DESC']
	]
]]);

3行目では、筆者のプロフィールテーブル(孫)へのリレーション設定。
6行目では、コメント登録日時降順でソート。
すごいね~。

SELECT
フィールド指定

テーブル名を省略すると、
自動的に当該テーブル(FROM に設定されるテーブル)のカラムとして認識される。


$query = $articles->find();
$query->select(['country']);

$query = $articles->find();
$query->select(['Users.country']);

SELECT DISTINCT Users.country AS `Users__country`

よって、普段からテーブル名を付記する癖を付ける方がよさそう。

入れ子(階層)

$query = $articles->find()->contain([
    'Authors.Addresses',
])
->select([
    'title',             // 第1層は、カラム名のみで OK
    'Authors.name',      // 子階層は、テーブル名を付ける
    'Addresses.address', // 孫階層であっても、このように記述
    'Authors.Addresses.address', // これは間違い
]);

8行目のような記述は間違い。

また、子を飛ばして孫だけ取得することもできない。
孫のカラムを必要とするなら、子のカラムも最低1つは取得すること。

この書き方は、リレーション先が1レコードに決まっているときしか使えない?
複数行ある場合(hasMany)のときは、「そんなカラムは知らん」って言われる気がする。

今のところは、そういう場合は、contain 内で select する方法で解決した。

DISTINCT

$query = $articles->find();
$query->select(['Users.country'])
      ->distinct(['Users.country']);

SELECT DISTINCT Users.country AS `Users__country`
集計
COUNT

$query = $articles->find();
$query->select(['count' => $query->func()->count('*')]);

SELECT COUNT(*) AS count
その他
  • sum() 合計を算出します。引数はリテラル値として扱われます。
  • avg() 平均値を算出します。引数はリテラル値として扱われます。
  • min() カラムの最小値を算出します。引数はリテラル値として扱われます。
  • max() カラムの最大値を算出します。引数はリテラル値として扱われます。
  • count() 件数を算出します。引数はリテラル値として扱われます。
  • concat() 2つの値を結合します。引数はリテラルだとマークされない限り、 バインドパラメーターとして扱われます。
  • coalesce() Coalesce を算出します。引数はリテラルだとマークされない限り、 バインドパラメーターとして扱われます。
  • dateDiff() 2つの日にち/時間の差を取得します。引数はリテラルだとマークされない限り、 バインドパラメーターとして扱われます。
  • now() 『time』 もしくは 『date』 を取得します。引数で現在の時刻もしくは日付のどちらを 取得するのかを指定できます。
  • extract() SQL 式から特定の日付部分(年など)を返します。
  • dateAdd() 日付式に単位時間を追加します。
  • dayOfWeek() SQL の WEEKDAY 関数を呼ぶ FunctionExpression を返します。

公式:クエリービルダー SQL 関数を使う

WHERE

$query = $articles->find();
$query->where(['name' => 'goku']);
LIKE

$query = $articles->find();
$query->where(['name LIKE' => '%goku%']);
AND

andWhere メソッドも存在するらしい。


$query = $articles->find();
$query->where(['first_name' => 'goku', 'last_name' => 'son']);

$query->where(['first_name' => 'goku']);
      ->where(['last_name' => 'son']);
OR

orWhere メソッドも存在するらしい。


$query = $articles->find();
$query->where('OR' => [['first_name' => 'goku'], ['last_name' => 'son']]);
IN

公式:クエリービルダー IN 句を自動生成する


$query = $articles->find();
$query->where('id IN' => [1, 3, 5]);
その他

結構まんま書ける。


$query = $articles->find()
->where('hoge IS NULL')
->where('moge IS NOT NULL')
->where('hoge >' => 10);
GROUP BY

$query = $articles->find();
$query = ->group(['Users.country', 'Users.gender']);

GROUP BY
    Users.country,
    Users.gender
WITH ROLLUP

GROUP BY -> WITH ROLLUP


$query = $articles->find();
$query = ->group(['Users.country', 'Users.gender'])
         ->epilog('WITH ROLLUP');

Raph’s world:CakePHP 3 ORM – with rollup(2015-09-20)
こちらには、上記のようにするように書いてあったけど
これではエラーになる。

500エラーだけど、原因は探っていない。

その上に書いてあった、下記の書式で誤魔化せた。
GROUP BY -> WITH ROLLUP


$query = $articles->find();
$query = ->group(['Users.country', 'Users.gender WITH ROLLUP']);

GROUP BY
    Users.country,
    Users.gender WITH ROLLUP

但し!
抽出カラムと GROUP BY 指定カラムが同一でなければエラーになる。

Error: SQLSTATE[42000]: Syntax error or access violation: 1055 Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column ‘database_name.Users.id’ which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

CakePHP3 では、JOIN したテーブルの id カラムを自動的に抽出カラムに加えるので
最初から抽出カラムに指定した上で、GROUP BY 対象にも指定する必要がある。

ORDER BY

$query = $articles->find();
$query = ->order(['Users.country' => 'ASC', 'Users.gender' => 'DESC']);

ORDER BY
    Users.country ASC
    Users.gender DESC
キャッシュ

クエリキャッシュ 参照。

CakePHP 3.x | 2017-12-23 (土) 0:40:15 |

コメントはまだありません »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment