MW211 EXIT

devlog
PDO/続・MSSQLストアドプロシージャの戻り値取得
2021年11月01日
「警告: NULL 値は集計またはその他の SET 演算で削除されました。」が出ることにより
戻り値が取得できない問題についての解決策を考えた。

(案1)「SET ANSI_WARNINGS OFF;」で警告を無視する
  手っ取り早い方法だ。
  ただ、副作用が気になる。(例えば除数0エラーが検出できないとか)

(案2)「SUM(ISNULL(列, 0))」でNULLを0にして集計する
  確実な方法だ。
  ただ、処理時間が気になる。
  ちなみに「ISNULL(SUM(列), 0)」では効果はない。
  なお、NULLの集まりなのでNULLとして認識したい場合は、この案は採用できない。

(案3)戻り値を別ルートで取得する
  プレースホルダのoutputみたいな項目を通じて取得できないものか
  …と思ったが、できなさそうなので断念。

ということで、ひとまず(案2)が妥当なのだろう。(急を要するなら(案1)だけど)

なお、以下で検証ができる。
┌──────────────────────────────────────┐
│SELECT SUM([値]) FROM (VALUES (0),(NULL)) AS [表]([値]);                    │
└──────────────────────────────────────┘
分類:PDO
PDO/MSSQLストアドプロシージャの戻り値取得
2021年10月29日
┌──────────────────────────────────────┐
│$sth = $PDO->prepare(SQL文);                                                │
│$sth->execute();                                                            │
│$row = $sth->fetch(PDO::FETCH_ASSOC);                                       │
│echo $row['戻り値'];                                                        │
└──────────────────────────────────────┘
上記のような感じでストアドプロシージャ(SQL文)を実行した後
その戻り値を取得したい場合には、以下のようなSQL文とすればよい。
┌──────────────────────────────────────┐
│SET NOCOUNT ON;                                                             │
│DECLARE @戻り値 [int];                                                      │
│EXEC @戻り値 = [DB].[dbo].[ストアドプロシージャ];                           │
│SELECT '戻り値' = @戻り値;                                                  │
└──────────────────────────────────────┘
件数出力(*1)が雑音となるので「SET NOCOUNT ON;」が必要だ。
*1:例えば「(1 行処理されました)」など

雑音があると「The active result for the query contains no fields.」エラーに。

但し、警告文(*2)が出る場合には、そちらも雑音となるので
「SET ANSI_WARNINGS OFF;」も必要だ。
*2:例えば「警告: NULL 値は集計またはその他の SET 演算で削除されました。」など
┌──────────────────────────────────────┐
│SET NOCOUNT ON;                                                             │
│SET ANSI_WARNINGS OFF;                                                      │
│DECLARE @戻り値 [int];                                                      │
│EXEC @戻り値 = [DB].[dbo].[ストアドプロシージャ];                           │
│SELECT '戻り値' = @戻り値;                                                  │
└──────────────────────────────────────┘
分類:PDO
MSSQL/PDOとOracleリンクサーバと論理型
2020年11月30日
┌──────────────────────────────────────┐
│$sql = <<<___SQL___                                                         │
│SELECT [論理] FROM OPENQUERY(ORACLE, 'SELECT 0 AS "論理" FROM DUAL') AS [o] │
│___SQL___;                                                                  │
│$sth = PDO->prepare($sql);                                                  │
│$sth->execute();                                                            │
│$row = $sth->fetch(PDO::FETCH_ASSOC);                                       │
│if (!$row['論理']) {                                                        │
│    // 偽:偽のつもりで0を返却しているのでこちらを期待するのだが            │
│} else {                                                                    │
│    // 真:こちらになってしまう                                             │
│}                                                                           │
└──────────────────────────────────────┘
論理型はOracleでもMSSQLでも、「0」と「1」を用いるのが普通だ。
ところが、上記のようにPDOを用いて、リンクサーバ経由で
Oracleから偽のつもりで「0」を取得したら、PHP上では真となってしまった。

原因は以下の通り。
(1) PHPで数値型の「0」や「0.0」、文字列型の「0」は、偽(FALSE)扱いなのだが
    文字列型の「0.0」は真(TRUE)扱いとなってしまう
(2) PDOで値を取得すると、(基本的に)文字列型となる
(3) OPENQUERY()でOracleから0を取得すると、float型の「0.0」となってしまう
    整数ではなく小数のnumber型がOracleでは基本のため

よって、(2)と(3)の組み合わせにより、(1)の条件に適合してしまったようだ。

ということで、MSSQLの世界ではbit型で論理型の代用をするので
SQL(MSSQL)上で以下のように変換してから、PDOで読み込むのがよいようだ。
┌──────────────────────────────────────┐
│SELECT CONVERT([bit], [論理]) AS [論理]~                                   │
└──────────────────────────────────────┘
分類:PDO、MSSQL
PHP/MSSQL連携
2015年08月08日
PHPからMSSQL(SQL Server)にアクセスできるようにするには
別途「Microsoft Drivers for PHP for SQL Server」dllを入手しアサインする

(1) Microsoft公式サイトから「SQLSRVxx.EXE」をダウンロードする
    https://www.microsoft.com/en-us/download/details.aspx?id=20098
    →以下のバージョンが取得可能(PHPやWindowsバージョンに合わせて選択)
      ・4.0(SQLSRV40.EXE)
      ・3.2(SQLSRV32.EXE)
      ・3.1(SQLSRV31.EXE)
      ・3.0(SQLSRV30.EXE)

(2) 上記EXEを実行すると、ファイルが取得(解凍)される
    途中で、解凍先を聞いてくるので任意のフォルダを選択

(3) 解凍された一式から「php_sqlsrv_xx.dll」と「php_pdo_sqlsrv_xx.dll」を
    条件に応じて一組だけ選び、
    extフォルダ(「C:\Program Files\PHP\ext」など)へ置く(コピーする)
    ┌────┬────────────┬──────────────┐
    │SQLSRV30│php_sqlsrv_53_ts.dll    │php_pdo_sqlsrv_53_ts.dll    │
    │        │php_sqlsrv_53_nts.dll   │php_pdo_sqlsrv_53_nts.dll   │
    │        ├────────────┼──────────────┤
    │        │php_sqlsrv_54_ts.dll    │php_pdo_sqlsrv_54_ts.dll    │
    │        │php_sqlsrv_54_nts.dll   │php_pdo_sqlsrv_54_nts.dll   │
    ├────┼────────────┼──────────────┤
    │SQLSRV31│php_sqlsrv_54_ts.dll    │php_pdo_sqlsrv_54_ts.dll    │
    │        │php_sqlsrv_54_nts.dll   │php_pdo_sqlsrv_54_nts.dll   │
    │        ├────────────┼──────────────┤
    │        │php_sqlsrv_55_ts.dll    │php_pdo_sqlsrv_55_ts.dll    │
    │        │php_sqlsrv_55_nts.dll   │php_pdo_sqlsrv_55_nts.dll   │
    ├────┼────────────┼──────────────┤
    │SQLSRV32│php_sqlsrv_54_ts.dll    │php_pdo_sqlsrv_54_ts.dll    │
    │        │php_sqlsrv_54_nts.dll   │php_pdo_sqlsrv_54_nts.dll   │
    │        ├────────────┼──────────────┤
    │        │php_sqlsrv_55_ts.dll    │php_pdo_sqlsrv_55_ts.dll    │
    │        │php_sqlsrv_55_nts.dll   │php_pdo_sqlsrv_55_nts.dll   │
    │        ├────────────┼──────────────┤
    │        │php_sqlsrv_56_ts.dll    │php_pdo_sqlsrv_56_ts.dll    │
    │        │php_sqlsrv_56_nts.dll   │php_pdo_sqlsrv_56_nts.dll   │
    ├────┼────────────┼──────────────┤
    │SQLSRV40│php_sqlsrv_7_ts_x64.dll │php_pdo_sqlsrv_7_ts_x64.dll │
    │        │php_sqlsrv_7_nts_x64.dll│php_pdo_sqlsrv_7_nts_x64.dll│
    │        ├────────────┼──────────────┤
    │        │php_sqlsrv_7_ts_x86.dll │php_pdo_sqlsrv_7_ts_x86.dll │
    │        │php_sqlsrv_7_nts_x86.dll│php_pdo_sqlsrv_7_nts_x86.dll│
    └────┴────────────┴──────────────┘

(4) 「php.ini」(「C:\Program Files\PHP\php.ini」など)に
    上記二つのDLLアサインを追記する
    ┌─────────────────┐
    │[PHP_SQLSRV_xx]                   │
    │extension=php_sqlsrv_xx.dll       │
    │[PHP_PDO_SQLSRV_xx]               │
    │extension=php_pdo_sqlsrv_xx.dll   │
    └─────────────────┘
    なお、角括弧の行(セクションマーカ)は単なる目印で影響を及ぼさないので
    記述しなくともよい(もしくは適当に周囲に合わせて記述する)

(5) 「phpinfo()」にて、以下が確認できればOK
    ・「PDO」の「PDO drivers」に「enabled」として「sqlsrv」が追加されていること
    ・「pdo_sqlsrv」(セクション)が追加されていること
分類:PHP、PDO
PDO/LIKE検索
2012年04月10日
┌──────────────────────────────────────┐
│SELECT 列 FROM 表 WHERE 検索列 LIKE '%検索値%';                             │
└──────────────────────────────────────┘
LIKE検索を行う場合、ベタだと上記のような感じだが、
PDOを使う場合は下記のような感じ。
┌──────────────────────────────────────┐
│SELECT 列 FROM 表 WHERE 検索列 LIKE :LIKE検索文字;                          │
└──────────────────────────────────────┘
で、プレースホルダに以下のような感じで「%」毎文字列としてbindしてあげる。
┌──────────────────────────────────────┐
│$sth->bindValue(':LIKE検索文字', '%'.$検索値.'%', PDO::PARAM_STR);          │
└──────────────────────────────────────┘

注意すべきなのは、検索文字の特殊文字をエスケープしてあげねばならぬこと。
「\」「%」「_」の先頭に「\」をつけてあげれよい。

「preg_replace()」で置換する例としては以下のような感じだ。
┌──────────────────────────────────────┐
│$検索値 = preg_replace('/\\\\/', '\\\\\\\\', $検索値);  // \→\\            │
│$検索値 = preg_replace('/%/'   , '\%'      , $検索値);  // %→\%            │
│$検索値 = preg_replace('/_/'   , '\_'      , $検索値);  // _→\_            │
└──────────────────────────────────────┘

なおこれらは、「standard_conforming_strings」の設定とは別の話だから注意。

ってことで、初心者が陥りやすいミスはこんな感じ。
┌─┬────────────────────────────────────┐
│  │$sql = 'SELECT * FROM 表 WHERE 列 LIKE %:like%';                        │
│×│$sth = $PDO->prepare($sql);                                             │
│  │$sth->bindValue(':like', '値', PDO::PARAM_STR);                         │
├─┼────────────────────────────────────┤
│  │$sql = 'SELECT * FROM 表 WHERE 列 LIKE :like';                          │
│○│$sth = $PDO->prepare($sql);                                             │
│  │$sth->bindValue(':like', '%値%', PDO::PARAM_STR);                       │
└─┴────────────────────────────────────┘
分類:PDO
PDO/トランザクションとPDO
2012年03月16日
PDOでトランザクション処理を行う場合、
「PDO::beginTransaction()」などで処理を開始したりするわけだが、
注意すべきことがある。

通常、トランザクション処理中にSQLエラーが発生すると
最後にCOMMITしても自動でROLLBACKしてくれる。

だが、PDOレベルでエラーが発生するとSQLエラーを検出せずCOMMITしてしまう。

例えば、プレースホルダの記述ミスなんかがPDOレベルのエラーだ。

例えば、以下のようなSQL文で、文中に誤りがあったとする。
┌──────────────────────────────────────┐
│UPDATE "表" SET "列" = :プレースホルダ;                                     │
└──────────────────────────────────────┘

以下の場合は、SQL文としては成立し、SQLエラーとなるのでROLLBACKしてくれる。
┌──────────────────────────────────────┐
│UPDATE "表" SET "列×" = :プレースホルダ;                                   │
└──────────────────────────────────────┘

ところが、以下の場合、プレースホルダ不正によりSQL文成立まで至らないので
ROLLBACKしてくれない。
┌──────────────────────────────────────┐
│UPDATE "表" SET "列" = :プレースホルダ×;                                   │
└──────────────────────────────────────┘
分類:PDO、注意
PHP/SQLインジェクション対策における%と_
2012年02月14日
LIKE演算子とセットで使うワイルドカードの「%」と「_」。
「'」とかと同じものだから、所定の手続きをとれば上手い具合に
やってくれそうな錯覚におそわれる。

例えば、addslashes()では何もしてくれない。

検索文字中に「%」や「_」が含まれている場合には
自力で「\%」や「\_」にしなきゃならない

「WHERE name LIKE %サクラ\%オー%」みたいになればよい

ついでに「\」も「\\」にしなければならない。
分類:PHP、PDO
PDO/SQL発行のまとめ
2011年11月29日
まずは、以下のような感じでオブジェクトを作る。
#「…」の部分は各環境で違うので省略
┌──────────────────────────────────────┐
│$dbh = new PDO(…);                                                         │
└──────────────────────────────────────┘

その後、ざっと以下のような方法がある
┌──────────────────────────────────────┐
│$dbh->exec('DELETE FROM 表;');                                              │
├──────────────────────────────────────┤
│foreach ($dbh->query('SELECT * FROM 表') as $row) {                         │
├──────────────────────────────────────┤
│$sth = $newPDO->prepare('DELETE FROM 表 WHERE ID = :id;');                  │
│$sth->bindValue(':id', $id, PDO::PARAM_INT);                                │
│$sth->execute();                                                            │
├──────────────────────────────────────┤
│$sth = $newPDO->prepare('SELECT * FROM 表 WHERE ID = :id;');                │
│$sth->bindValue(':id', $id, PDO::PARAM_INT);                                │
│$sth->execute();                                                            │
│foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) {                        │
└──────────────────────────────────────┘

まず、exec()メソッド、単純に更新系SQLを実行したい場合にはこちらで。
戻り値は処理結果(何件作用したか)だけなので、SELECT文の参照系SQLには使えない。

参照系SQLの場合は、query()メソッドで。
戻り値に行毎に配列でデータが返ってくる。

SQL文中に変数を使いたい場合は、prepare()メソッドを使う。
これでオブジェクトを作り、bindValue()で変数を指定し、execute()で実行。
fetchAll()を使うとデータを取得できる。(fetch()なら一件ずつ取得)
更新系SQLなら、execute()までで終わり。

ややこしいので、PDOを使うならprepare()メソッドを使う方式に統一して、
気が向いたらショートカット的にexec()やquery()を使うって感じだろうか。
#query()はexec()も兼ねているっぽいけど…
分類:PDO
PDO/件数を取得する方法
2011年11月11日
「SELECT 列 FROM 表 WHERE 条件;」みたいな元SQL文があるとして、
その件数を取得したい場合がある。

ま、単純にはこんな感じで囲ってやれば、
たいていの場合は対応できる(元SQLにDISTINCTとかが絡んでてもOK)のだけど…。
┌──────────────────────────────────────┐
│SELECT COUNT(*) FROM (SELECT 列 FROM 表 WHERE 条件) AS dummy;               │
└──────────────────────────────────────┘

でも、単に件数を取得するだけのためにいちいちSQL文を作成したくはない。
そんなのPDOでなんとかしてくれないの、ってことで代替案。
#例によって「$sth->execute();」の直後の処理って想定でお話を進めます

Countというキーワードを元に「columnCount()」ってメソッドにぶち当たったのだが
これは単にSELECT文に指定した列数を取得するっていう、畑違いのメソッドだった。

さて、気を取り直して大本命の「rowCount()」ってメソッド。
┌──────────────────────────────────────┐
│$count = $sth->rowCount();                                                  │
└──────────────────────────────────────┘
でもこれって、「INSERT、UPDATE、DELETE」といった更新系処理の実行件数であり
「SELECT」の件数としてはあいまいな感じの扱いなのだ。
#どうやら、読み込み完了前に件数を算定する恐れがあるっぽいみたい

ま、信頼性が低いのであれば致し方ない。
ってことで、結論としては「fetchAll()」メソッドで全件取得しちゃって、
「count()」関数で数えちゃえばってことになりました。
┌──────────────────────────────────────┐
│$count = count($sth->fetchAll());                                           │
└──────────────────────────────────────┘
これでもコーディング量的には前出の「rowCount()」メソッドと比べても
そんなに大差ないのでいいのではないでしょうか
分類:PDO
PDO/fetch()とfetchAll()
2011年11月09日
PDOには、「fetch()」という一件だけ読み込むメソッドと
「fetchAll()」という一気に全件読み込むメソッドがある。

で、以下はほぼ等価(「foreach()」の「$row」を「&$row」にすれば尚更)
┌──────────────────────────────────────┐
│while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {                              │
│  :                                                                        │
│}                                                                           │
├──────────────────────────────────────┤
│foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) {                        │
│  :                                                                        │
│}                                                                           │
└──────────────────────────────────────┘
#前提条件とかはあんまり気にせず「$sth->execute();」とかを
  実行した直後だと思ってください。

該当全件を読み込んで処理をするというのは結構多発する処理なので
どっちにするか悩むところかも。

ただ後者だと、以下みたいに関数化(メソッド化)して分離できるからいいかも。
┌──────────────────────────────────────┐
│function get() {                                                            │
│  :                                                                        │
│  return $sth->fetchAll(PDO::FETCH_ASSOC);                                  │
│}                                                                           │
│- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - │
│foreach ($this->get() as $row) {                                            │
│  :                                                                        │
│}                                                                           │
└──────────────────────────────────────┘

たいていは「fetch()」的な関数(「mysql_fetch_assoc()」とか)から
入る(経験する)ので、「fetchAll()」は上級編なのかも。
分類:PDO
前へ 1 2 次へ