Nette Documentation Preview

syntax
SQL アプローチ
**********

.[perex]
Nette Database は 2 つのやり方を用意しています。SQL のクエリを自分で書くか(SQL アプローチ)、自動的に生成させるか([Explorer |explorer]をご覧ください)です。SQL アプローチはクエリを完全に思いどおりにしつつ、それが安全に組み立てられることを保証します。

.[note]
データベースへの接続と設定の詳しい話は [接続と設定 |guide#接続と設定]の章にあります。


基本のクエリ
======

データベースへの問い合わせには `query()` メソッドを使います。これはクエリの結果を表す [ResultSet |api:Nette\Database\ResultSet]オブジェクトを返します。クエリが失敗すると、このメソッドは[例外を投げます|exceptions]。クエリの結果は `foreach` のループで回せますし、[補助のメソッド |#データの取り出し]のどれかを使えます。

```php
$result = $database->query('SELECT * FROM users');

foreach ($result as $row) {
	echo $row->id;
	echo $row->name;
}
```

SQL のクエリに値を安全に入れるには、パラメータ化されたクエリを使います。Nette Database ではこれがきわめて簡単で、SQL のクエリのうしろにコンマと値を足すだけです。

```php
$database->query('SELECT * FROM users WHERE name = ?', $name);
```

パラメータが複数あるときは 2 通りの書き方があります。SQL のクエリとパラメータを交互に並べられます。

```php
$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age);
```

あるいは先に SQL のクエリ全体を書いて、そのあとにすべてのパラメータを並べられます。

```php
$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age);
```


SQL インジェクションからの保護
===================

なぜパラメータ化されたクエリを使うことが大事なのでしょうか。それは SQL インジェクションと呼ばれる攻撃から守ってくれるからです。この攻撃では、攻撃者が自分の SQL のコマンドを差し込んで、データベースのデータにアクセスしたり壊したりできてしまいます。

.[warning]
**変数を SQL のクエリに直接入れては決していけません。** いつもパラメータ化されたクエリを使ってください。それが SQL インジェクションから守ってくれます。

```php
// ❌ 危険なコード - SQL インジェクションに対して脆弱です
$database->query("SELECT * FROM users WHERE name = '$name'");

// ✅ 安全なパラメータ化されたクエリ
$database->query('SELECT * FROM users WHERE name = ?', $name);
```

[起こりうるセキュリティリスク |security]も知っておいてください。


クエリの書き方
=======


WHERE の条件
-----------

`WHERE` の条件は連想配列で書けます。キーは列の名前、値は比較するデータです。Nette Database は値の型をもとに、いちばんふさわしい SQL の演算子を自動的に選びます。

```php
$database->query('SELECT * FROM users WHERE', [
	'name' => 'John',
	'active' => true,
]);
// WHERE `name` = 'John' AND `active` = 1
```

キーの中で比較の演算子をはっきり指定することもできます。

```php
$database->query('SELECT * FROM users WHERE', [
	'age >' => 25,          // > 演算子を使います
	'name LIKE' => '%John%', // LIKE 演算子を使います
	'email NOT LIKE' => '%example.com%', // NOT LIKE 演算子を使います
]);
// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%'
```

Nette は `null` の値や配列といった特別な場合も自動的に扱います。

```php
$database->query('SELECT * FROM products WHERE', [
	'name' => 'Laptop',         // = 演算子を使います
	'category_id' => [1, 2, 3], // IN を使います
	'description' => null,      // IS NULL を使います
]);
// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL
```

否定の条件には `NOT` 演算子を使います。

```php
$database->query('SELECT * FROM products WHERE', [
	'name NOT' => 'Laptop',         // != 演算子を使います
	'category_id NOT' => [1, 2, 3], // NOT IN を使います
	'description NOT' => null,      // IS NOT NULL を使います
	'id NOT' => [],                 // 飛ばされます
]);
// WHERE `name` != 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL
```

既定では条件は `AND` 演算子でつながれます。これは [?or のプレースホルダ |#SQL の組み立てのヒント]で変えられます。


ORDER BY の規則
--------------

`ORDER BY` の句は配列で書けます。キーに列を指定し、真偽値で昇順(`true`)か降順(`false`)かを示します。

```php
$database->query('SELECT id FROM author ORDER BY', [
	'id' => true, // 昇順
	'name' => false, // 降順
]);
// SELECT id FROM author ORDER BY `id`, `name` DESC
```


データの挿入(INSERT)
---------------

レコードの挿入には SQL の `INSERT` コマンドを使います。

```php
$values = [
	'name' => 'John Doe',
	'email' => 'john@example.com',
];
$database->query('INSERT INTO users ?', $values);
$userId = $database->getInsertId();
```

`getInsertId()` メソッドは最後に挿入された行の ID を返します。データベースによっては(PostgreSQL など)、ID を生成するシーケンスの名前をパラメータとして `$database->getInsertId($sequenceId)` のように指定する必要があります。

パラメータとしては[特別な値 |#特別な値]、たとえばファイル、DateTime オブジェクト、enum 型も渡せます。

複数のレコードを一度に挿入します。

```php
$database->query('INSERT INTO users ?', [
	['name' => 'User 1', 'email' => 'user1@mail.com'],
	['name' => 'User 2', 'email' => 'user2@mail.com'],
]);
```

複数レコードの INSERT はずっと速くなります。多くの個別のクエリの代わりに、データベースのクエリが 1 つだけ実行されるからです。

**セキュリティに関する注意:** 検証していないデータを `$values` として決して使わないでください。[起こりうるリスク |security#列を安全に扱う]を知っておいてください。


データの更新(UPDATE)
---------------

レコードの更新には SQL の `UPDATE` コマンドを使います。

```php
// ひとつのレコードを更新します
$values = [
	'name' => 'John Smith',
];
$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1);
```

影響を受けた行の数は `$result->getRowCount()` が返します。

`UPDATE` では `+=` と `-=` の演算子を使えます。

```php
$database->query('UPDATE users SET ? WHERE id = ?', [
	'login_count+=' => 1, // login_count を増やします
], 1);
```

レコードがすでにあれば更新し、なければ挿入する例です。`ON DUPLICATE KEY UPDATE` の手法を使います。

```php
$values = [
	'name' => $name,
	'year' => $year,
];
$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?',
	$values + ['id' => $id],
	$values,
);
// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978)
//   ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978
```

Nette Database が、配列のパラメータが SQL のコマンドのどの文脈で使われているかを見分けて、それに応じた SQL のコードを組み立てていることに注目してください。最初の配列からは `(id, name, year) VALUES (123, 'Jim', 1978)` を組み立て、2 つめは `name = 'Jim', year = 1978` の形に変えました。これは [#SQL の組み立てのヒント]の節で詳しく説明します。


データの削除(DELETE)
---------------

レコードの削除には SQL の `DELETE` コマンドを使います。消された行の数を得る例です。

```php
$count = $database->query('DELETE FROM users WHERE id = ?', 1)
	->getRowCount();
```


SQL の組み立てのヒント
--------------

ヒントとは、パラメータの値をどう SQL の式に変えるかを指定する、SQL のクエリの中の特別なプレースホルダです。

| ヒント     | 説明                                              | 自動的に使われる場面
|-----------|-------------------------------------------------|-----------------------------
| `?name`   | テーブルや列の名前を入れるのに使います              | -
| `?values` | `(key, ...) VALUES (value, ...)` を生成します     | `INSERT ... ?`、`REPLACE ... ?`
| `?set`    | 代入 `key = value, ...` を生成します              | `SET ?`、`KEY UPDATE ?`
| `?and`    | 配列の条件を `AND` でつなぎます                    | `WHERE ?`、`HAVING ?`
| `?or`     | 配列の条件を `OR` でつなぎます                     | -
| `?order`  | `ORDER BY` の句を生成します                       | `ORDER BY ?`、`GROUP BY ?`

`?name` のプレースホルダは、テーブルや列の名前を動的にクエリに入れるのに使います。Nette Database はそのデータベースの流儀に従って識別子を正しく引用符で囲みます(MySQL ならバッククォートで囲みます)。

```php
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table);
// SELECT `name` FROM `users` WHERE id = 1 (MySQL の場合)
```

**注意:** `?name` のプレースホルダは検証済みのテーブル名と列名にだけ使ってください。さもないと[セキュリティ上の弱点 |security#動的な識別子]を招きます。

ほかのヒントはふつう指定する必要はありません。Nette は SQL のクエリを組み立てるときに賢く自動判別するからです(表の 3 列めをご覧ください)。とはいえ、たとえば条件を `AND` ではなく `OR` でつなぎたい場面で使えます。

```php
$database->query('SELECT * FROM users WHERE ?or', [
	'name' => 'John',
	'email' => 'john@example.com',
]);
// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com'
```


特別な値
----

よくあるスカラーの型(string、int、bool)のほかに、パラメータとして特別な値も渡せます。

- ファイル: `fopen('image.gif', 'r')` はファイルの中身をバイナリとして入れます
- 日付と時刻: `DateTimeInterface` のオブジェクトはデータベースの書式に変換されます
- enum 型: `enum` のインスタンスはその値に変換されます
- SQL のリテラル: `Connection::literal('NOW()')` で作られ、そのままクエリに入ります

```php
$database->query('INSERT INTO articles ?', [
	'title' => 'My Article',
	'published_at' => new DateTimeImmutable, // または new DateTime
	'content' => fopen('image.png', 'r'),
	'state' => Status::Draft,
]);
```

`datetime` のデータ型を本来は持たないデータベース(SQLite や Oracle など)では、`DateTime` と `DateTimeImmutable` のオブジェクトは、[データベースの設定|configuration]の `formatDateTime` の項目で指定された値に変換されます(既定値は `U`、つまり Unix タイムスタンプです)。


SQL のリテラル
---------

生の SQL のコードを値として渡し、文字列として扱われたりエスケープされたりしないようにしたい場合があります。そのために `Nette\Database\SqlLiteral` クラスのオブジェクトを使います。これは `Connection::literal()` メソッドで作ります。

```php
$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	'year >' => $database::literal('YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR())
```

あるいは次のようにも書けます。

```php
$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('year > YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR())
```

SQL のリテラルはパラメータを含められます。

```php
$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('year > ? AND year < ?', $min, $max),
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017)
```

これで面白い組み合わせが作れます。

```php
$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('?or', [
		'active' => true,
		'role' => $role,
	]),
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin')
```


データの取り出し
========


SELECT のクエリの近道
--------------

データの取り出しを簡単にするために、`Connection` は `query()` の呼び出しと、そのあとの `fetch*()` の呼び出しをひとつにまとめた近道をいくつか用意しています。これらのメソッドは `query()` と同じパラメータ、つまり SQL のクエリと省略できるパラメータを受け取ります。`fetch*()` メソッドの詳しい説明は[下 |#fetch()]にあります。

| `fetch($sql, ...$params): ?Row`       | クエリを実行し、最初の行を `Row` オブジェクトとして、なければ `null` を返します。
| `fetchAll($sql, ...$params): array`   | クエリを実行し、すべての行を `Row` オブジェクトの配列として返します。
| `fetchPairs($sql, ...$params): array` | クエリを実行し、連想配列(キー => 値の組)を返します。
| `fetchField($sql, ...$params): mixed` | クエリを実行し、最初の行の最初の列の値を返します。
| `fetchList($sql, ...$params): ?array` | クエリを実行し、最初の行を添字の配列として、なければ `null` を返します。

例です。

```php
// fetchField() - 最初のセルの値を返します
$count = $database->query('SELECT COUNT(*) FROM articles')
	->fetchField();
```


`foreach` - 行を順に回す
--------------------

クエリを実行すると [ResultSet|api:Nette\Database\ResultSet]オブジェクトが返され、結果をいくつかの方法で回せます。クエリを実行して行を取り出すいちばん簡単な方法は、`foreach` のループで回すことです。この方法はメモリをもっとも節約します。データを 1 行ずつ取り出し、結果全体を一度にメモリへ読み込まないからです。

```php
$result = $database->query('SELECT * FROM users');

foreach ($result as $row) {
	echo $row->id;
	echo $row->name;
	// ...
}
```

.[note]
`ResultSet` は一度しか回せません。何度も回す必要があるなら、まず `fetchAll()` メソッドなどでデータを配列に読み込まなければなりません。


fetch(): ?Row .[method]
-----------------------

行を `Row` オブジェクトとして返します。行がもうなければ `null` を返します。内部のポインタを次の行へ進めます。

```php
$result = $database->query('SELECT * FROM users');
$row = $result->fetch(); // 最初の行を読み込みます
if ($row) {
	echo $row->name;
}
```


fetchAll(): array .[method]
---------------------------

`ResultSet` に残っているすべての行を `Row` オブジェクトの配列として返します。

```php
$result = $database->query('SELECT * FROM users');
$rows = $result->fetchAll(); // すべての行を読み込みます
foreach ($rows as $row) {
	echo $row->name;
}
```


fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method]
---------------------------------------------------------------------------------------

結果を連想配列として返します。第 1 引数はキーとして使う列、第 2 引数は値として使う列を指定します。

```php
$result = $database->query('SELECT id, name FROM users');
$names = $result->fetchPairs('id', 'name');
// [1 => 'John Doe', 2 => 'Jane Doe', ...]
```

第 1 パラメータ(`$key`)だけを渡すと、行全体(`Row` オブジェクト)が値として使われます。

```php
$rows = $result->fetchPairs('id');
// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...]
```

キーが重なった場合は最後の行の値が使われます。キーに `null` を使うと、ゼロから始まる添字の配列になり、キーの衝突が起きません。

```php
$names = $result->fetchPairs(null, 'name');
// [0 => 'John Doe', 1 => 'Jane Doe', ...]
```


fetchPairs(Closure $callback): array .[method]
----------------------------------------------

代わりに、行ごとに処理するコールバックを渡せます。コールバックはひとつの値か、キーと値の組を返せます。

```php
$result = $database->query('SELECT * FROM users');
$items = $result->fetchPairs(fn($row) => "$row->id - $row->name");
// ['1 - John', '2 - Jane', ...]

// コールバックはキーと値の組の配列を返すこともできます:
$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]);
// ['John' => 46, 'Jane' => 21, ...]
```


fetchField(): mixed .[method]
-----------------------------

今の行の最初の列の値を返します。行がもうなければ `null` を返します。内部のポインタを次の行へ進めます。

```php
$result = $database->query('SELECT name FROM users');
$name = $result->fetchField(); // 最初の行から name を読み込みます
```


fetchList(): ?array .[method]
-----------------------------

行を添字の配列として返します。行がもうなければ `null` を返します。内部のポインタを次の行へ進めます。

```php
$result = $database->query('SELECT name, email FROM users');
$row = $result->fetchList(); // ['John', 'john@example.com']
```


getRowCount(): ?int .[method]
-----------------------------

直前の `UPDATE` または `DELETE` のクエリで影響を受けた行の数を返します。`SELECT` のクエリでは結果の行の数を返します。ただしこれは常に分かるとは限らず、その場合このメソッドは `null` を返します。


getColumnCount(): ?int .[method]
--------------------------------

`ResultSet` の列の数を返します。


クエリの情報
======

デバッグのために、最後に実行されたクエリの情報を取り出せます。

```php
echo $database->getLastQueryString();   // SQL のクエリを出力します

$result = $database->query('SELECT * FROM articles');
echo $result->getQueryString();    // SQL のクエリを出力します
echo $result->getTime();           // 実行にかかった時間を秒で出力します
```

結果を HTML の表として表示するには次のようにします。

```php
$result = $database->query('SELECT * FROM articles');
$result->dump();
```

`ResultSet` は列の型の情報も提供します。

```php
$result = $database->query('SELECT * FROM articles');
$types = $result->getColumnTypes();

foreach ($types as $column => $type) {
	echo "$column is of type $type"; // たとえば 'id is of type int'
}
```


クエリのログ
------

クエリのログを独自に作れます。`onQuery` イベントは、実行されたクエリごとに呼ばれるコールバックの配列です。

```php
$database->onQuery[] = function ($database, $result) use ($logger) {
	$logger->info('Query: ' . $result->getQueryString());
	$logger->info('Time: ' . $result->getTime());

	if ($result->getRowCount() > 1000) {
		$logger->warning('Large result set: ' . $result->getRowCount() . ' rows');
	}
};
```

SQL アプローチ

Nette Database は 2 つのやり方を用意しています。SQL のクエリを自分で書くか(SQL アプローチ)、自動的に生成させるか(Explorerをご覧ください)です。SQL アプローチはクエリを完全に思いどおりにしつつ、それが安全に組み立てられることを保証します。

データベースへの接続と設定の詳しい話は 接続と設定の章にあります。

基本のクエリ

データベースへの問い合わせには query() メソッドを使います。これはクエリの結果を表す ResultSetオブジェクトを返します。クエリが失敗すると、このメソッドは例外を投げます。クエリの結果は foreach のループで回せますし、補助のメソッドのどれかを使えます。

$result = $database->query('SELECT * FROM users');

foreach ($result as $row) {
	echo $row->id;
	echo $row->name;
}

SQL のクエリに値を安全に入れるには、パラメータ化されたクエリを使います。Nette Database ではこれがきわめて簡単で、SQL のクエリのうしろにコンマと値を足すだけです。

$database->query('SELECT * FROM users WHERE name = ?', $name);

パラメータが複数あるときは 2 通りの書き方があります。SQL のクエリとパラメータを交互に並べられます。

$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age);

あるいは先に SQL のクエリ全体を書いて、そのあとにすべてのパラメータを並べられます。

$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age);

SQL インジェクションからの保護

なぜパラメータ化されたクエリを使うことが大事なのでしょうか。それは SQL インジェクションと呼ばれる攻撃から守ってくれるからです。この攻撃では、攻撃者が自分の SQL のコマンドを差し込んで、データベースのデータにアクセスしたり壊したりできてしまいます。

変数を SQL のクエリに直接入れては決していけません。 いつもパラメータ化されたクエリを使ってください。それが SQL インジェクションから守ってくれます。

// ❌ 危険なコード - SQL インジェクションに対して脆弱です
$database->query("SELECT * FROM users WHERE name = '$name'");

// ✅ 安全なパラメータ化されたクエリ
$database->query('SELECT * FROM users WHERE name = ?', $name);

起こりうるセキュリティリスクも知っておいてください。

クエリの書き方

WHERE の条件

WHERE の条件は連想配列で書けます。キーは列の名前、値は比較するデータです。Nette Database は値の型をもとに、いちばんふさわしい SQL の演算子を自動的に選びます。

$database->query('SELECT * FROM users WHERE', [
	'name' => 'John',
	'active' => true,
]);
// WHERE `name` = 'John' AND `active` = 1

キーの中で比較の演算子をはっきり指定することもできます。

$database->query('SELECT * FROM users WHERE', [
	'age >' => 25,          // > 演算子を使います
	'name LIKE' => '%John%', // LIKE 演算子を使います
	'email NOT LIKE' => '%example.com%', // NOT LIKE 演算子を使います
]);
// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%'

Nette は null の値や配列といった特別な場合も自動的に扱います。

$database->query('SELECT * FROM products WHERE', [
	'name' => 'Laptop',         // = 演算子を使います
	'category_id' => [1, 2, 3], // IN を使います
	'description' => null,      // IS NULL を使います
]);
// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL

否定の条件には NOT 演算子を使います。

$database->query('SELECT * FROM products WHERE', [
	'name NOT' => 'Laptop',         // != 演算子を使います
	'category_id NOT' => [1, 2, 3], // NOT IN を使います
	'description NOT' => null,      // IS NOT NULL を使います
	'id NOT' => [],                 // 飛ばされます
]);
// WHERE `name` != 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL

既定では条件は AND 演算子でつながれます。これは ?or のプレースホルダで変えられます。

ORDER BY の規則

ORDER BY の句は配列で書けます。キーに列を指定し、真偽値で昇順(true)か降順(false)かを示します。

$database->query('SELECT id FROM author ORDER BY', [
	'id' => true, // 昇順
	'name' => false, // 降順
]);
// SELECT id FROM author ORDER BY `id`, `name` DESC

データの挿入(INSERT)

レコードの挿入には SQL の INSERT コマンドを使います。

$values = [
	'name' => 'John Doe',
	'email' => 'john@example.com',
];
$database->query('INSERT INTO users ?', $values);
$userId = $database->getInsertId();

getInsertId() メソッドは最後に挿入された行の ID を返します。データベースによっては(PostgreSQL など)、ID を生成するシーケンスの名前をパラメータとして $database->getInsertId($sequenceId) のように指定する必要があります。

パラメータとしては特別な値、たとえばファイル、DateTime オブジェクト、enum 型も渡せます。

複数のレコードを一度に挿入します。

$database->query('INSERT INTO users ?', [
	['name' => 'User 1', 'email' => 'user1@mail.com'],
	['name' => 'User 2', 'email' => 'user2@mail.com'],
]);

複数レコードの INSERT はずっと速くなります。多くの個別のクエリの代わりに、データベースのクエリが 1 つだけ実行されるからです。

セキュリティに関する注意: 検証していないデータを $values として決して使わないでください。起こりうるリスクを知っておいてください。

データの更新(UPDATE)

レコードの更新には SQL の UPDATE コマンドを使います。

// ひとつのレコードを更新します
$values = [
	'name' => 'John Smith',
];
$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1);

影響を受けた行の数は $result->getRowCount() が返します。

UPDATE では +=-= の演算子を使えます。

$database->query('UPDATE users SET ? WHERE id = ?', [
	'login_count+=' => 1, // login_count を増やします
], 1);

レコードがすでにあれば更新し、なければ挿入する例です。ON DUPLICATE KEY UPDATE の手法を使います。

$values = [
	'name' => $name,
	'year' => $year,
];
$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?',
	$values + ['id' => $id],
	$values,
);
// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978)
//   ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978

Nette Database が、配列のパラメータが SQL のコマンドのどの文脈で使われているかを見分けて、それに応じた SQL のコードを組み立てていることに注目してください。最初の配列からは (id, name, year) VALUES (123, 'Jim', 1978) を組み立て、2 つめは name = 'Jim', year = 1978 の形に変えました。これは SQL の組み立てのヒントの節で詳しく説明します。

データの削除(DELETE)

レコードの削除には SQL の DELETE コマンドを使います。消された行の数を得る例です。

$count = $database->query('DELETE FROM users WHERE id = ?', 1)
	->getRowCount();

SQL の組み立てのヒント

ヒントとは、パラメータの値をどう SQL の式に変えるかを指定する、SQL のクエリの中の特別なプレースホルダです。

ヒント 説明 自動的に使われる場面
?name テーブルや列の名前を入れるのに使います
?values (key, ...) VALUES (value, ...) を生成します INSERT ... ?REPLACE ... ?
?set 代入 key = value, ... を生成します SET ?KEY UPDATE ?
?and 配列の条件を AND でつなぎます WHERE ?HAVING ?
?or 配列の条件を OR でつなぎます
?order ORDER BY の句を生成します ORDER BY ?GROUP BY ?

?name のプレースホルダは、テーブルや列の名前を動的にクエリに入れるのに使います。Nette Database はそのデータベースの流儀に従って識別子を正しく引用符で囲みます(MySQL ならバッククォートで囲みます)。

$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table);
// SELECT `name` FROM `users` WHERE id = 1 (MySQL の場合)

注意: ?name のプレースホルダは検証済みのテーブル名と列名にだけ使ってください。さもないとセキュリティ上の弱点を招きます。

ほかのヒントはふつう指定する必要はありません。Nette は SQL のクエリを組み立てるときに賢く自動判別するからです(表の 3 列めをご覧ください)。とはいえ、たとえば条件を AND ではなく OR でつなぎたい場面で使えます。

$database->query('SELECT * FROM users WHERE ?or', [
	'name' => 'John',
	'email' => 'john@example.com',
]);
// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com'

特別な値

よくあるスカラーの型(string、int、bool)のほかに、パラメータとして特別な値も渡せます。

  • ファイル: fopen('image.gif', 'r') はファイルの中身をバイナリとして入れます
  • 日付と時刻: DateTimeInterface のオブジェクトはデータベースの書式に変換されます
  • enum 型: enum のインスタンスはその値に変換されます
  • SQL のリテラル: Connection::literal('NOW()') で作られ、そのままクエリに入ります
$database->query('INSERT INTO articles ?', [
	'title' => 'My Article',
	'published_at' => new DateTimeImmutable, // または new DateTime
	'content' => fopen('image.png', 'r'),
	'state' => Status::Draft,
]);

datetime のデータ型を本来は持たないデータベース(SQLite や Oracle など)では、DateTimeDateTimeImmutable のオブジェクトは、データベースの設定formatDateTime の項目で指定された値に変換されます(既定値は U、つまり Unix タイムスタンプです)。

SQL のリテラル

生の SQL のコードを値として渡し、文字列として扱われたりエスケープされたりしないようにしたい場合があります。そのために Nette\Database\SqlLiteral クラスのオブジェクトを使います。これは Connection::literal() メソッドで作ります。

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	'year >' => $database::literal('YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR())

あるいは次のようにも書けます。

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('year > YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR())

SQL のリテラルはパラメータを含められます。

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('year > ? AND year < ?', $min, $max),
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017)

これで面白い組み合わせが作れます。

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('?or', [
		'active' => true,
		'role' => $role,
	]),
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin')

データの取り出し

SELECT のクエリの近道

データの取り出しを簡単にするために、Connectionquery() の呼び出しと、そのあとの fetch*() の呼び出しをひとつにまとめた近道をいくつか用意しています。これらのメソッドは query() と同じパラメータ、つまり SQL のクエリと省略できるパラメータを受け取ります。fetch*() メソッドの詳しい説明はにあります。

fetch($sql, ...$params): ?Row クエリを実行し、最初の行を Row オブジェクトとして、なければ null を返します。
fetchAll($sql, ...$params): array クエリを実行し、すべての行を Row オブジェクトの配列として返します。
fetchPairs($sql, ...$params): array クエリを実行し、連想配列(キー ⇒ 値の組)を返します。
fetchField($sql, ...$params): mixed クエリを実行し、最初の行の最初の列の値を返します。
fetchList($sql, ...$params): ?array クエリを実行し、最初の行を添字の配列として、なければ null を返します。

例です。

// fetchField() - 最初のセルの値を返します
$count = $database->query('SELECT COUNT(*) FROM articles')
	->fetchField();

foreach – 行を順に回す

クエリを実行すると ResultSetオブジェクトが返され、結果をいくつかの方法で回せます。クエリを実行して行を取り出すいちばん簡単な方法は、foreach のループで回すことです。この方法はメモリをもっとも節約します。データを 1 行ずつ取り出し、結果全体を一度にメモリへ読み込まないからです。

$result = $database->query('SELECT * FROM users');

foreach ($result as $row) {
	echo $row->id;
	echo $row->name;
	// ...
}

ResultSet は一度しか回せません。何度も回す必要があるなら、まず fetchAll() メソッドなどでデータを配列に読み込まなければなりません。

fetch(): ?Row

行を Row オブジェクトとして返します。行がもうなければ null を返します。内部のポインタを次の行へ進めます。

$result = $database->query('SELECT * FROM users');
$row = $result->fetch(); // 最初の行を読み込みます
if ($row) {
	echo $row->name;
}

fetchAll(): array

ResultSet に残っているすべての行を Row オブジェクトの配列として返します。

$result = $database->query('SELECT * FROM users');
$rows = $result->fetchAll(); // すべての行を読み込みます
foreach ($rows as $row) {
	echo $row->name;
}

fetchPairs(string|int|null $key = null, string|int|null $value = null)array

結果を連想配列として返します。第 1 引数はキーとして使う列、第 2 引数は値として使う列を指定します。

$result = $database->query('SELECT id, name FROM users');
$names = $result->fetchPairs('id', 'name');
// [1 => 'John Doe', 2 => 'Jane Doe', ...]

第 1 パラメータ($key)だけを渡すと、行全体(Row オブジェクト)が値として使われます。

$rows = $result->fetchPairs('id');
// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...]

キーが重なった場合は最後の行の値が使われます。キーに null を使うと、ゼロから始まる添字の配列になり、キーの衝突が起きません。

$names = $result->fetchPairs(null, 'name');
// [0 => 'John Doe', 1 => 'Jane Doe', ...]

fetchPairs(Closure $callback)array

代わりに、行ごとに処理するコールバックを渡せます。コールバックはひとつの値か、キーと値の組を返せます。

$result = $database->query('SELECT * FROM users');
$items = $result->fetchPairs(fn($row) => "$row->id - $row->name");
// ['1 - John', '2 - Jane', ...]

// コールバックはキーと値の組の配列を返すこともできます:
$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]);
// ['John' => 46, 'Jane' => 21, ...]

fetchField(): mixed

今の行の最初の列の値を返します。行がもうなければ null を返します。内部のポインタを次の行へ進めます。

$result = $database->query('SELECT name FROM users');
$name = $result->fetchField(); // 最初の行から name を読み込みます

fetchList(): ?array

行を添字の配列として返します。行がもうなければ null を返します。内部のポインタを次の行へ進めます。

$result = $database->query('SELECT name, email FROM users');
$row = $result->fetchList(); // ['John', 'john@example.com']

getRowCount(): ?int

直前の UPDATE または DELETE のクエリで影響を受けた行の数を返します。SELECT のクエリでは結果の行の数を返します。ただしこれは常に分かるとは限らず、その場合このメソッドは null を返します。

getColumnCount(): ?int

ResultSet の列の数を返します。

クエリの情報

デバッグのために、最後に実行されたクエリの情報を取り出せます。

echo $database->getLastQueryString();   // SQL のクエリを出力します

$result = $database->query('SELECT * FROM articles');
echo $result->getQueryString();    // SQL のクエリを出力します
echo $result->getTime();           // 実行にかかった時間を秒で出力します

結果を HTML の表として表示するには次のようにします。

$result = $database->query('SELECT * FROM articles');
$result->dump();

ResultSet は列の型の情報も提供します。

$result = $database->query('SELECT * FROM articles');
$types = $result->getColumnTypes();

foreach ($types as $column => $type) {
	echo "$column is of type $type"; // たとえば 'id is of type int'
}

クエリのログ

クエリのログを独自に作れます。onQuery イベントは、実行されたクエリごとに呼ばれるコールバックの配列です。

$database->onQuery[] = function ($database, $result) use ($logger) {
	$logger->info('Query: ' . $result->getQueryString());
	$logger->info('Time: ' . $result->getTime());

	if ($result->getRowCount() > 1000) {
		$logger->warning('Large result set: ' . $result->getRowCount() . ' rows');
	}
};