どちらのドライバーも、ステートメントを 1 回実行することも、繰り返し実行するステートメントを準備することもできます。 クエリを 1 回だけ実行する場合でも、ユーザーが指定した値にはパラメーターを使用してください。
実行APIは結果の取得方法とは別に選びましょう。 クエリを準備しても行は取得されず、結果をバッファリングしてもクエリがパラメータ化されるわけではありません。 以下の例では、読み取り専用のクエリを1回準備し、異なる値で2回実行し、次の実行前にそれぞれの結果を消費します。
| 実行スタイル | SQLSRV | PDO_SQLSRV |
|---|---|---|
| パラメーターなしでクエリを一度実行 | sqlsrv_query | PDO::query で結果を取得し、PDO::exec で影響を受けた行数を取得 |
| パラメータ付きで一度クエリを実行してください | sqlsrv_query($conn, $sql, $params) |
PDO::prepare() に続いて PDOStatement::execute() |
| 一度準備して、異なるパラメータで繰り返し実行してください | sqlsrv_prepare + sqlsrv_execute | PDO::prepare + PDOStatement::execute |
sqlsrv_query() は 1 回の呼び出しで準備と実行を行い、ステートメント リソースを返します。
sqlsrv_prepare() は、ステートメントリソースを、それを実行せずに返します。実行するたびに sqlsrv_execute() を呼び出してください。 PDOの query() は実行して PDOStatementを返しますが、 exec() は結果オブジェクトの代わりに影響を受けた行数を返します。 正常に実行された exec() は 0 を返す可能性があるため、失敗時の戻り値は if (!$count) ではなく === false と比較してください。
PDO::query() パラメータ値の配列を受け入れません。 そのオプション引数により、取得の構成を行います。 パラメータ化されたPDO_SQLSRVクエリには prepare() と execute()を使用します。
一度準備しておけば、ステートメントの処理を異なる値で再利用できます。 これは、サーバーが実行プランを再コンパイルしないことを保証するものではありません。 PDO_SQLSRV はデフォルトでプリペアード クエリを使用しており、PDO::query() についても同様です。その direct-query 属性によって、この設定を変更することができます。
一度準備して、繰り返し実行する
これらは別個の完全なコマンドラインプログラムです。 選択した拡張機能をインストールして読み込み、PHPプロセス環境で SQL_SERVER、 SQL_DATABASE、 SQL_USER、 SQL_PASSWORD を設定します。 SQL Serverテストデータベースとクライアントが信頼するサーバー証明書を使いましょう。 環境設定やパスワードレスの代替手段については、「 サーバーへの接続」を参照してください。
実際のデータベース名、ユーザー名、パスワードを環境変数に保存します。 どちらのプログラムも、PHP ドライバの ODBC 形式の接続処理で要求される通り、閉じ中括弧を 2 つ重ね、各値を中括弧で囲むことで、これらの値を 1 回エスケープしています。 接続値のエスケープはクエリパラメータのバインディングとは別物です。
これらのプログラムは、テーブルを作成したりデータを変更したりすることなく、2 つのインライン行に対して Transact-SQL (T-SQL) クエリを実行します。 各プログラムをそれぞれ独自の .php ファイルに保存し、php <file> で実行します。 サンプルデータベースは不要です。
SQLSRV
$productId を、&$productId を使って参照渡しします。 各 sqlsrv_execute() は現在の値を使います。
SQLSRV_FETCH_ASSOC 列名でキーされた配列を返します。
null結果はそれ以上行がないことを意味します。falseはエラーを示します。
<?php
$config = [];
foreach (['SQL_SERVER', 'SQL_DATABASE', 'SQL_USER', 'SQL_PASSWORD'] as $name) {
$value = getenv($name);
if ($value === false || $value === '') {
throw new RuntimeException("Set $name before running this example.");
}
$config[$name] = $value;
}
foreach (['SQL_DATABASE', 'SQL_USER', 'SQL_PASSWORD'] as $name) {
$config[$name] = '{' . str_replace('}', '}}', $config[$name]) . '}';
}
$conn = sqlsrv_connect($config['SQL_SERVER'], [
'Database' => $config['SQL_DATABASE'],
'UID' => $config['SQL_USER'],
'PWD' => $config['SQL_PASSWORD'],
'Encrypt' => true,
'TrustServerCertificate' => false,
'LoginTimeout' => 30,
'CharacterSet' => 'UTF-8',
]);
if ($conn === false) {
throw new RuntimeException(print_r(sqlsrv_errors(), true));
}
$sql = "SELECT Name FROM (VALUES (1, 'Bike'), (2, 'Helmet')) AS Product(ProductId, Name) WHERE ProductId = ?";
$productId = 1;
$stmt = null;
try {
$stmt = sqlsrv_prepare($conn, $sql, [&$productId], ['QueryTimeout' => 30]);
if ($stmt === false) {
throw new RuntimeException(print_r(sqlsrv_errors(), true));
}
foreach ([1 => 'Bike', 2 => 'Helmet'] as $productId => $expected) {
if (!sqlsrv_execute($stmt)) {
throw new RuntimeException(print_r(sqlsrv_errors(), true));
}
$names = [];
while (($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) !== null) {
if ($row === false) {
throw new RuntimeException(print_r(sqlsrv_errors(), true));
}
$names[] = $row['Name'];
}
if ($names !== [$expected]) {
throw new RuntimeException('Expected exactly one matching product.');
}
echo $names[0], PHP_EOL;
}
} finally {
if (is_resource($stmt)) {
sqlsrv_free_stmt($stmt);
}
sqlsrv_close($conn);
}
PDO_SQLSRV
$productId を bindParam() で一度バインドします。 各 execute() は現在の値を読み取ります。
PDO::PARAM_INT パラメータのPHP/PDOバインディング型を指定します。結果から取得される列のPHP型は制御しません。 データベース エラーが空の結果と誤認されることなく、実行を中断するように例外モードを設定します。
<?php
$config = [];
foreach (['SQL_SERVER', 'SQL_DATABASE', 'SQL_USER', 'SQL_PASSWORD'] as $name) {
$value = getenv($name);
if ($value === false || $value === '') {
throw new RuntimeException("Set $name before running this example.");
}
$config[$name] = $value;
}
foreach (['SQL_DATABASE', 'SQL_USER', 'SQL_PASSWORD'] as $name) {
$config[$name] = '{' . str_replace('}', '}}', $config[$name]) . '}';
}
$server = $config['SQL_SERVER'];
$database = $config['SQL_DATABASE'];
$pdo = new PDO(
"sqlsrv:Server=$server;Database=$database;Encrypt=true;"
. "TrustServerCertificate=false;LoginTimeout=30",
$config['SQL_USER'],
$config['SQL_PASSWORD'],
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::SQLSRV_ATTR_ENCODING => PDO::SQLSRV_ENCODING_UTF8,
]
);
$sql = "SELECT Name FROM (VALUES (1, 'Bike'), (2, 'Helmet')) AS Product(ProductId, Name) WHERE ProductId = ?";
$stmt = null;
try {
$stmt = $pdo->prepare($sql, [PDO::SQLSRV_ATTR_QUERY_TIMEOUT => 30]);
$productId = 1;
$stmt->bindParam(1, $productId, PDO::PARAM_INT);
foreach ([1 => 'Bike', 2 => 'Helmet'] as $productId => $expected) {
$stmt->execute();
$names = $stmt->fetchAll(PDO::FETCH_COLUMN);
if ($names !== [$expected]) {
throw new RuntimeException('Expected exactly one matching product.');
}
echo $names[0], PHP_EOL;
$stmt->closeCursor();
}
} finally {
$stmt = null;
$pdo = null;
}
各例では、次のように出力されます:
Bike
Helmet
PDO の execute() に渡された配列内の値は、PDO::PARAM_STR として扱われます。 SQLが整数パラメータ(たとえば、パラメータ化されたTOPカウント)を必要とする場合は、PDO::PARAM_INTをbindValue()と共に呼び出し、その後、配列なしでexecute()を呼び出します。
クエリを変更しずに値をバインドする
パラメータマーカーは、テーブル名、列名、キーワード、ソート方向ではなく、値を表します。
? の前後には引用符を付けないでください。 動的識別子または ASC/DESC については、信頼できないテキストを連結するのではなく、アプリケーションが管理する許可リストから選択してください。
INリストの場合は、値ごとに1つのマーカーを用意してください。
| Requirement | SQLSRV | PDO_SQLSRV |
|---|---|---|
| 1回の実行で値をバインドする | パラメータ配列を sqlsrv_query()に渡します。 |
prepare() の後に execute()に配列を渡すか、あらかじめ値をバインドします。 |
| 変数の現在の値を再利用する |
sqlsrv_prepare() に渡される配列に &$variable を追加します。 |
変数を指定して bindParam() を、次に配列を指定せずに execute() を呼び出します。 PHPは変数を参照で渡します。呼び出しサイトで & を追加しないでください。 |
| バインド時に値をバインドする | 実行には、パラメーター配列内の値を使用してください。 |
bindValue()を呼び出します。後で元の変数を変更しても、そのバウンド値は変わりません。 |
| 整数入力を指定する | 明示的な型が必要な場合は、 SQLSRV_PARAM_IN、 SQLSRV_PHPTYPE_INT、 SQLSRV_SQLTYPE_INT のパラメータディスクリプタを使用します。 |
PDO::PARAM_INT によるバインド。 ポジションのbindParam()と bindValue() インデックスは1ではなく0から始まります。 |
SQLを送信 NULL |
必要に応じて、明示的なSQL型を指定して PHP null をバインドします。 |
PDO::PARAM_NULL によるバインド。 |
| 出力パラメーターを受け取る |
SQLSRV_PARAM_OUT または SQLSRV_PARAM_INOUT を含む参照変数を使用します。 |
PDO::PARAM_INPUT_OUTPUTと必要な種類・長さのbindParam()を使いましょう。 |
プリペアド ステートメントの実行中は、参照されている変数を有効なまま保持してください。 SQLSRVプログラムはパラメータ配列を置き換えるのではなく、バウンド変数を変更します。 PDOプログラムはバインディング後に配列なしで意図的に execute() を呼び出します。そのパターンを配列ベースの実行と混同しないでください。
ストアド プロシージャからの出力値については、プロシージャの結果を完了させることなど、個別の処理が必要です。 これらの入力のみの例から出力パラメータの挙動を推測しないでください。 詳細については、「 方向性パラメータの使用」を参照してください。
実行およびフェッチ動作の設定
| 設定 | SQLSRV | PDO_SQLSRV |
|---|---|---|
| 実行タイムアウト | ステートメントのオプション配列内の QueryTimeout。 |
接続またはステートメントの PDO::SQLSRV_ATTR_QUERY_TIMEOUT。 |
| 名前付き結果キー |
SQLSRV_FETCH_ASSOC は sqlsrv_fetch_array() に渡されました。 |
PDO::FETCH_ASSOC フェッチメソッドに渡されるか、接続のデフォルトのフェッチモードとして設定されます。 |
| 順方向のみの結果 | デフォルトのカーソル、順序通りに取得する。 | デフォルトのカーソル; 順に取得します。 |
| クライアント側でバッファされた結果 |
Scrollable => SQLSRV_CURSOR_CLIENT_BUFFERED。 |
PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL と PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED。 |
| ダイレクト実行とプリペアード実行の比較 | PDO のダイレクト クエリ属性はありません。
sqlsrv_query() の統合呼び出しか、明示的な準備/実行のペアのいずれかを選択します。 |
PDO::SQLSRV_ATTR_DIRECT_QUERY ドライバーのデフォルトの準備実行ではなく直接実行を選択します。 |
| パラメータエミュレーション | PDO エミュレーションの属性はありません。 |
PDO::ATTR_EMULATE_PREPARES はdirect-query属性とは別です。 正しい型をバインドする代わりに使わないでください。 |
この例では、返される結果の数が少ないです。
fetchAll() は各行を PHP のメモリに読み込みます。結果の行数が多い場合は、fetch ループを使用します。 一致する結果が返されないクエリは、実行エラーではありません。 SQLSRVはnullからの結果終了をsqlsrv_fetch_array()として報告します。PDOはfalseからの結果終了をfetch()として報告します。 フェッチされた行内のSQL NULL はPHP nullのままです。
SELECT行カウントだけを成功判定にしないでください。 フォワードオンリー カーソルには、バッファ付きカーソルやスクロール可能カーソルのような行数のカウント機能はありません。 必要な行数を取って数えるか、別の COUNT クエリを使うのも良いでしょう。 カーソル固有のオプションや制限については 、「カーソルタイプを指定し行を選択してください」をご覧ください。
エラーの処理と結果の処理
SQLSRV の「準備」、「実行」、「フェッチ」の各操作を個別に確認してください。 準備が成功した場合であっても、実行が成功するとは限りません。実行時に、パラメーターの値、アクセス許可、またはステートメントそのものを拒否する可能性があります。
sqlsrv_errors()操作が結果を返したら、直ちにfalse取得します。 SQLSRVは多くの警告をデフォルトでエラーとして扱うため、コレクション全体を検査してください。
PDO では、例外モードにおいて接続エラーやデータベース エラーが発生した場合、PDOException がスローされます。 保護されたアプリケーション診断でSQLSTATEおよびドライバーエラー情報を検査し、HTTP応答で例外テキストや認証情報を返さないでください。
prepare() 成功したことは、そのステートメントがすでに実行された証拠ではありません。
| 症状: | アクション |
|---|---|
| すべての実行は最初の値を使います | SQLSRVの場合は、参照で変数をバインドします。 PDO では、値が変化する変数には bindParam() を使用するか、新しい値ごとに bindValue() を呼び出します。 |
TOPカウントのような数値パラメータは却下されます |
必要な整数型を明示的にバインドします。 PDO execute([$value]) は、PHPの値が整数であっても文字列パラメータを使用します。 |
| 2 つ目のコマンドは、処理中の結果を報告します | 接続を再利用する前に、結果を処理するか、リリースします。 PDOの場合は、残りの結果が不要なときに使う closeCursor() を使いましょう。 |
| 期待出力パラメータは変わっていません | すべての手順の結果を完了させ、パラメーターの方向、PHP の型、および出力バッファの長さを確認します。 |
| メモリ使用量は結果サイズに比例して増加します |
fetchAll() や不要なクライアント側のバッファを、順次フェッチに置き換えてください。 |
そのサンプル コードでは、finally ブロック内でステートメントを解放し、接続を閉じています。 彼らは取引を始めません。 複数の書き込みをまとめてコミットするか、すべてロールバックする必要がある場合は、明示的なトランザクションを使用してください。 プリペアドステートメントを再利用しても書き込みはトランザクションにまとめられません。
ターゲットと実装の違い
prepare/execute API と PHP 参照のルールは、拡張機能と PHP/PDO に属しています。 SQLターゲットはどのT-SQL文や型をサポートするかを決定し、ODBCが基盤となる通信を行います。 一度準備すれば繰り返し作業を減らすことはできますが、特定のサーバー実行計画や速度向上を保証するわけではありません。
入力バインディングパターンはSQL Server、Azure SQL Database、Azure SQL Managed Instance、Azure Synapse Analytics、Microsoft FabricのSQL Database、Microsoft FabricのWarehouseに適用されます。 これらのプログラムはSQL Serverに対してSQL認証を使用します。 Azure onboardingの場合は、Microsoft Entra認証を優先してください。 Fabric の製品は両方ともこれが必要であり、Warehouse ではさらに、Multiple Active Result Sets (MARS) を無効にする必要があります。 SQL Serverコードを再利用する前に、選択したターゲットのスキーマ変更、トランザクション、ストアドプロシージャのサポートを確認してください。 エンドポイントと認証の違いについては、AzureおよびFabric接続を参照してください。