質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.47%

  • PostgreSQL

    1100questions

    PostgreSQLはオープンソースのオブジェクトリレーショナルデータベース管理システムです。 Oracle Databaseで使われるPL/SQLを参考に実装されたビルトイン言語で、Windows、 Mac、Linux、UNIX、MSなどいくつものプラットフォームに対応しています。

  • FuelPHP

    525questions

    FuelPHPは、軽量高速で開発が可能なPHPのWebアプリケーションフレームワークです。

[fuelPHP]アクションクエリの戻り値について

解決済

回答 3

投稿

  • 評価
  • クリップ 1
  • VIEW 3,544

dthani

score 130

fuelPHPで、updateして0件だったら、insertというプログラムを作りたいと思い、
アクションクエリを実行した際の戻り値について調べていたところ、
下記サイトを見つけたのですが、まだわかりません。
http://qiita.com/bonon0/items/f5abeee65edf5ca37758

まず、
updateを実行したとき、int型と書いていますが、それはInsetを実行した戻り値と同様、配列型でその中身がint型なのでしょうか?配列型ではなく単純なint型なのでしょうか?

Insert,delete,updateが失敗したときは、どの形式(配列?なんらかの数値?)で何が戻ってくるのでしょうか?
エラーコードや、エラーメッセージが取得されるのでしょうか?

また、fuelPHPで、ストアドプロシージャ、ストアドファンクションを実行するサンプル(戻り値取得も)などありますでしょうか?

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+2

PHPのDB操作のお約束として、INSERT、UPDATE、DELETEの戻り値は大抵、直前の操作で影響が及ぼされた行数です。

fuelPHPのコードを読んでみると、まず、
fuelphp-1.7.2/fuel/core/classes/db.phpの中で、
// Query types
    const SELECT =  1;
    const INSERT =  2;
    const UPDATE =  3;
    const DELETE =  4;

と定義しています。

PDOの利用では
fuelphp-1.7.2/fuel/core/classes/database/pdo/connection.php
        if ($type === \DB::SELECT)
        {
                        略
        }
        elseif ($type === \DB::INSERT)
        {
            // Return a list of insert id and rows created
            return array(
                $this->_connection->lastInsertId(),
                $result->rowCount(),
            );
        }
        else
        {
            // Return the number of rows affected
            return $result->errorCode() === '00000' ? $result->rowCount() : -1;
        }
となっていて、UPDATEの場合はelseになりますから
http://php.net/manual/ja/pdostatement.rowcount.php
PDOStatement::rowCount — 直近の SQL ステートメントによって作用した行数を返す
ので、intのみが返るのだと思います。

mysqliの利用では
        if ($type === \DB::SELECT)
        {
            // Return an iterator of results
            return new \Database_MySQLi_Result($result, $sql, $as_object);
        }
        elseif ($type === \DB::INSERT)
        {
            // Return a list of insert id and rows created
            return array(
                $this->_connection->insert_id,
                $this->_connection->affected_rows,
            );
        }
        else
        {
            // Return the number of rows affected
            return $this->_connection->affected_rows;
        }
となっていますので、
http://php.net/manual/ja/mysqli.affected-rows.php
mysqli::$affected_rows -- mysqli_affected_rows — 直前の MySQL の操作で変更された行の数を得る
やはり、intのみが返ると思います。

非推奨となったmysqlの利用でも
        if ($type === \DB::SELECT)
        {
            // Return an iterator of results
            return new \Database_MySQL_Result($result, $sql, $as_object);
        }
        elseif ($type === \DB::INSERT)
        {
            // Return a list of insert id and rows created
            return array(
                mysql_insert_id($this->_connection),
                mysql_affected_rows($this->_connection),
            );
        }
        else
        {
            // Return the number of rows affected
            return mysql_affected_rows($this->_connection);
        }
http://php.net/manual/ja/function.mysql-affected-rows.php
mysql_affected_rows — 一番最近の操作で変更された行の数を得る

一緒ですね。

これらは
fuelphp-1.7.2/fuel/core/classes/database/query.phpのexecute()内
$result = $db->query($this->_type, $sql, $this->_as_object);
で呼び出されていて、そのあと
return $result;
されるので、つまりUPDATE(とDELETE)の戻り値はintのみです。

INSERTの場合は上記のように

array(int insert_id, int affected_rows)となるようです。
insert_idは
http://php.net/manual/ja/mysqli.insert-id.php
このあたりを見ていただけると分かりますが、

直前のクエリで更新された AUTO_INCREMENT フィールドの値を返します。接続での直前のクエリがない場合や クエリが AUTO_INCREMENT の値を更新しなかった場合は ゼロを返します。---
となっています。


Insert,delete,updateが失敗したときは、どの形式(配列?なんらかの数値?)で何が戻ってくるのでしょうか? 
エラーコードや、エラーメッセージが取得されるのでしょうか?

mysqliを例にすると、
http://php.net/manual/ja/mysqli.affected-rows.php

正の整数が返された場合、それは変更された行数かあるいは取得された行数を 示します。ゼロが返された場合、それは UPDATE 文でレコードが更新されなかったか WHERE 条件に当てはまる行がなかった、またはクエリが実行されなかったことを 示します。-1 は、クエリがエラーを返したことを示します。

となります。PDOでもUPDATE、DELETEの場合、ステータスコードが正常でない場合はfuelphp側で-1を返すようになっていますね。

さらにコードを読んでみると分かりますが、SQL自体が実行できない場合にはDatabase_Exceptionがthrowされるようです。

ですので、これをcatchしてgetMessage()すれば、問題の内容が確認できるようですね。


投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/04/17 23:18

    ご回答ありがとうございます。

    お手数ですが、下記の具体的な取得方法をお教え下さい。

    さらにコードを読んでみると分かりますが、SQL自体が実行できない場合にはDatabase_Exceptionがthrowされるようです。

    ですので、これをcatchしてgetMessage()すれば、問題の内容が確認できるようですね。

    キャンセル

  • 2015/04/17 23:20

    try{}catch(){}ですね。

    コードを書くので別スレッドにします。

    キャンセル

checkベストアンサー

0

PHPとしては

try {
    $query = DB::select()->from('○○○');
    $query->where('id', 1);
    $result = $query->excute();
} catch(Database_Exception $e){
  //try{}内の処理でDatabase_Exceptionが投げられたらここにくる。PHPの処理は続行される。
    echo $e->getMessage();    // ←エラー内容が表示される
    echo $e->getTraceAsString();  // ←スタックトレースが表示される
} catch(ErrorException $e){
    //ErrorExceptionが投げられたらこっち
      :
} catch(Exception $e){
    //Exceptionが投げられたらこっち
}

こういうのがセオリーです。

Database_ExceptionはFuelExceptionを継承していて、FuelExceptionはPHP標準のExceptionを継承しています。

ただ、fuelPHPのフレームワーク自体でtry{}catch(){}しているようなので、エラーの内容を知りたいだけなら特に自前でtry{}catch(){}しなくてもエラーハンドリングすることは出来ますね。

ですからエラーがあった時にフレームワークの処理を停止せずに何か処理を続行したい時などに、自前でtry{}catch(){}を実行することになると思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/04/18 00:01

    ご回答ありがとうございます。

    すみませんが、そのような知識はどういう書籍やWebサイトで学んでいるのでしょうか?
    fuelPHPの電子本や、fuelPHPの公式サイトには載っておりません。
    よければ教えてください。

    キャンセル

  • 2015/04/18 00:04

    もう、コードを読んでます。慣れると読めるものですよ。

    キャンセル

  • 2015/04/18 00:10

    そうなんですか。良いコーディング方法も学べそうですね。
    ありがとうございます。

    キャンセル

0

行が無かったらinsert、行が有ったらupdate こういう処理を upsert と呼んだりします。
質問内容とは少しずれますが、lucker さんがFuelPHPの戻り値について詳しく書いているので私は簡単に upsert の事を書きましょう。


こういう処理はFuelPHPで結果を受け取らずに、SQL文だけで解決することができます。

まずSQL文です。

テーブル
CREATE TABLE sample(
   id int NOT NULL,
   message Text
);
挿入+更新クエリ(upsertクエリ)
WITH updated AS (UPDATE sample SET message='updated!' WHERE id=1 RETURNING *)
INSERT INTO sample(id, message) SELECT 1, 'inserted!' WHERE NOT EXISTS(SELECT * FROM updated);

この upsert クエリを実行することで、「updateして0件だったらinsert」が実現できます。
upsertクエリを実行すると、1度目はmessageが'inserted!'、idが1の行が挿入され、2度実行するとmessageが'updated!'に書き変わります。

UPDATE文で挿入されたデータをRETURNING句で受け取っています。RETURNING句を使うと更新された行数ではなく、更新された行を受け取ることができます。
WITH句はその結果に updated というエリアス名をつけています。
updatedを参照して、中身が空なら更新対象はなかった、あれば更新対象があったことがわかります。

INSERT文にはSELECT文の結果を挿入する機能があります。
NOT EXISTS句を使ってupdatedの結果が空ならINSERTが実行されるようにしています。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/04/17 23:04

    ご回答ありがとうございます。
    ただ、上記のupsertはselect文を実行してしまうので、データ件数が多い場合(数万件以上)は実行速度が遅くなりますよね。
    よって、updateして処理件数が0件だったら、insertさせる方で考えています。それもその部分をストアドで実行すれば高速で処理できますので。

    キャンセル

  • 2015/04/17 23:19

    いえ、データの件数による速度の劣化はupdateとinsertを別々に処理した場合とほとんど同じだと認識しています。
    それどころかFuelPHPで結果を取得した場合と比較すればずっと早い可能性があります。

    upsertクエリで行われているselect文は、あくまでupdateの結果(更新された1レコード、無ければ0レコード)を取得しているので、現在のテーブルの列数には影響を受けません。update文のWHERE句には適切なインデックスを張る必要がありますが、そこにかかるコストは通常update文を実行する場合と同じです。

    FuelPHPでupdate対象が無ければinsertをする場合より早いというのは、上記のクエリは1度しかクエリを実行しないためです。
    update時にクライアントで1度結果を受け取ってくるということは、insert時には合計で2度のクエリが実行されることになり、クライアントーサーバー間の通信が2度発生することになります。
    このコストは単純に余剰なコストですから削除したい部分なのです。

    upsertクエリのメリット、デメリットは以下のようなものです
    メリット:1度のクエリでinsertとupdateを同時に行える
    デメリット:文法が難解で可読性が低い

    これは計測しないと正しいことが分からないのであまり自信を持って言えないのですが、ストアドプロシージャにした場合と同程度のパフォーマンスになると思います。

    キャンセル

  • 2015/04/17 23:21

    あ、だからといってストアドよりこちらを・・・とかそういう話ではないです。

    ストアドは正しいアプローチだと思っています。

    キャンセル

  • 2015/04/17 23:41

    ご回答ありがとうございます。
    upsertって、そんなに実行速度が速いんですね。
    わかりにくい書き方で実行速度が遅いと思っていたので、敬遠していました。
    ありがとうございます。

    キャンセル

関連した質問

同じタグがついた質問を見る

  • PostgreSQL

    1100questions

    PostgreSQLはオープンソースのオブジェクトリレーショナルデータベース管理システムです。 Oracle Databaseで使われるPL/SQLを参考に実装されたビルトイン言語で、Windows、 Mac、Linux、UNIX、MSなどいくつものプラットフォームに対応しています。

  • FuelPHP

    525questions

    FuelPHPは、軽量高速で開発が可能なPHPのWebアプリケーションフレームワークです。