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

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

ただいまの
回答率

87.92%

Uncaught PDOException: SQLSTATE[HY000]: General error: 2031を解決したい

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 3,518
退会済みユーザー

退会済みユーザー

お世話になっています。

独自フレームワークを作成しており、PDOのinsert、update、delete文を簡易化したいなと思って
ConnectPdoクラスを作成していますが、update()のところでGeneral errorエラーが返ってきます。

Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 2031 in /var/www/html/myapp/app/core/lib/ConnectPdo.php:209 Stack trace: #0 

流れは、以下の形でcontrollerから引数を渡してあげれば、
pdoのupdate文を実行させるようにしています。
(現在は、テストのためcontrollerで実行していますが、行く行くはmodelからConnectPdoクラスを使って実装します)

function test()
{
    $pdo = new ConnectPdo;
    $pdo->connect_db();
    $table = 'test';
    $hash = array(
        'name'  => 'testtest',
        'title' => 'テストです'
    );
    $where = array('id' => 2, 'title' => 'fff');
    echo $pdo->update($table,$hash,$where);
}


このエラーから下記のコードより「return $prepare->execute();」の部分でエラーが発生しています。

<?php
    /**
     * update文
     * hashはmust
     * 
     * @param  string $table table name
     * @param  array  $hash  setするカラムと値
     * @param  array  $where where
     * @param  string $custom_cond カスタムwhere
     * @return [type] [description]
     */
    function update($table,$hash,$where=false,$custom_cond=false)
    {
        if(empty($hash)) return false;

        /*----------
         * set組み立て
         -----------*/
        $countHash    = count($hash);
        $array_keys   = array_keys($hash);
        $array_values = array_values($hash);

        $implode_hash = ':';
        $implode_hash .= implode(' :',$array_keys);

        $explode_hash = explode(' ',$implode_hash);

        $i = 0;
        $values = array();
        $sets_value = '';
        foreach($hash as $key => $value) {
            $values[$i] = $value;
            $sets_value .= $array_keys[$i] .' = ' .$explode_hash[$i];

            $i++;

            if($i == $countHash) break;

            $sets_value .= ',';
        }

        $sql = 'update ' .$table .' set ' .$sets_value;



        /*----------
         * where組み立て
         -----------*/
        if($where){
            $countWhere = count($where);

            $keys_where   = array_keys($where);
            $values_where = array_values($where);

            $keys_implode = ':';
            $keys_implode .= implode(' :',$keys_where);

            $keys_explode = explode(' ',$keys_implode);

            $wheres = '';
            $i = 0;

            foreach($where as $key => $value) {
                $wheres .= $keys_where[$i] .' = ' .$keys_explode[$i];
                $i++;

                if($i == $countWhere) break;

                $wheres .= ' and ';
            }

            $sql .= ' where ' .$wheres;
        }

        /*----------
         * PDO実行
         -----------*/
         //custom_condがあった場合
        if($custom_cond){
            $sql .= ' ' .$custom_cond;
            return $this->_pdo->query($sql);
        }

        //custom_condがなかった場合は、prepareでexecute
        $prepare = $this->_pdo->prepare($sql);
        $array_values = array_values($hash);

        // hash
        for($i=0; $i<$countHash; $i++) {
            if(is_numeric($array_values[$i])){
                $prepare->bindParam($explode_hash[$i],$array_values[$i],PDO::PARAM_INT);    
            }else{
                $prepare->bindParam($explode_hash[$i],$array_values[$i],PDO::PARAM_STR);
            }
        }

        // where
        if($where) {
            for($j=0; $j<$countWhere; $j++) {
                if(is_numeric($values_where[$j])){
                    $prepare->bindValue($keys_explode[$j],$values_where[$j],PDO::PARAM_INT);        
                }else{
                    $prepare->bindValue($keys_explode[$j],$values_where[$j],PDO::PARAM_STR);
                }
            }
        }

        return $prepare->execute();
    }


(エラーチェックなどは、後ほど実装していくつもりです。)

$whereのところで以下のようにすると、update文が実行できますが、
なぜ他のwhereの条件が入ってくると、実行されないのか。
なかなか解決できずにいます。

$where = array('id' => 2);

ちなみに、 echo $sql  を仕込むとsql文は以下の形で生成されます。

echo $sql;
//update test set name = :name,title = :title where id = :id and title = :title
return $prepare->execute();

コードが見辛くて申し訳ございません。
お力を貸していただければ幸いでございます。

よろしくお願いいたします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • m.ts10806

    2019/06/24 05:47

    bindParamとbindValueが混在しているのはどういった理由でしょうか?

    キャンセル

回答 2

checkベストアンサー

+1

理由としては、下記と思います。

PDOStatement::execute() をコールする際には、 文に渡すパラメータにはそれぞれ固有のパラメータマークを設定する必要があります。 エミュレーションモードが有効になっていない限り、 ひとつのプリペアドステートメントの中で、同じ名前のパラメータマークを 複数使用することはできません。

つまり「:title」が複数回出てきているため、PDO側のルールに引っかかっているということですね。
カラム名と合わせる必要はないので別名にするか、疑問符 (?) パラメータマークを利用してはいかがでしょうか。

それか、名前のパラメータマークを利用したいのであれば、特にupdateですし、Whereには「更新されないカラム」を指定されるのが通例ではないかなと思います。※一気に複数更新したいとかならまだ別ですが

あまり汎用的にしすぎるのも今回のような問題が起きることにもなりますので、なるべく通例に沿った作りのみを許容しておくようにしてもいいかもしれません。
どのように作ったとしてもイレギュラーというのは発生するものなので、イレギュラーをなるべく吸収しようと作った結果、あまり緩い作りになってしまってもフレームワークとしては良くありません。
イレギュラーはSQLを自分で作らせて直実行にしてもいいかもしれませんね。

個人的な意見ですが、私であればUPDATEとDELETEはWhereにキーのみ許容する作りを軸にします。

蛇足:
コメントで書きましたがbindParamとbindValueが混在する作りはあまり良くないと思います。
できればbindValueのみで統一しましょう。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/06/25 22:22

    返答が遅くなってしまい、申し訳ございません。

    前回に引き続き、ご回答ありがとうございます。
    まず、bindParamとbindValueに関しましてはエラーがなぜ発生したのか分からず、
    試しに実行してみたものを、こちらに転記してしまいました...
    やはり、bindParam or bindValueは統一した方が良いのですね。
    修正いたします。

    >つまり「:title」が複数回出てきているため....
    こちらに関しましても、リファレンスまで載せていただきありがとうございます!
    :titleが複数使われることによるエラーだとは思ってもみませんでした。

    先ほど試したところ、おっしゃる通りwhereでも指定していた「:title」が悪さをしていたようです!
    ありがとうございます!


    >Whereには「更新されないカラム」を指定されるのが通例ではないかなと思います
    こちら、存じ上げておりませんでした。
    まだまだ知らないことだらけで、お恥ずかしいです。

    いただいたアドバイスを参考に、修正を加えていきます!
    ありがとうございます!

    キャンセル

  • 2019/06/26 06:50

    解決されたようで何よりです

    >やはり、bindParam or bindValueは統一した方が良いのですね。

    統一された方が良いのはもちろん、各所で言われているようにbindValueを採用してください。(違いとかで調べるとでてきます)

    update,deleteについてはデータベースの特性、「プライマリーキー」「主キー」が関係してきます。
    利用者が自分以外にもいると変更される可能性のあるカラムが指定されると画面上はあるように見えて更新時に変わってた(つまり作業してる間に他の人が更新された)ということがあります。そうするとtitleのように更新されるカラムがあると「更新できない」現象が発生します。
    なので、変更されないプライマリーキーになっているカラムを指定することで担保します。もちろん削除されれば更新はできないわけですが、それとこれとは別問題で、更新時に「全く同じtitleのデータがあったら?」と考えると自ずと答えは出ますね。キーになってないカラムは同じ内容が存在できますので。

    キャンセル

  • 2019/06/26 12:33

    ご返答ありがとうございます。
    bindValueとbindParamの違いを確認しました。
    bindParamでは、変数の参照値がバインドされるので、bindParam()を実行後も値を変更できるということ、それに対しbindValueは変数の「値」が入るので、実行後の変更は利かない。ということがわかりました。
    ということは、mts10806さんのおっしゃる通り、bindValueを使用したほうが良いですね。
    ありがとうございます。


    >update,deleteについてはデータベースの特性....
    こちら、すごく勉強になります!!!
    DB周りは、これから勉強していこうと思って、とりあえずPDOを汎用的に使えるようにすることだけに注意が集中してましたが、データの更新は、特に気を遣わないといけないところでした。
    仰る通り、同じタイトルのデータがあったとき、他のユーザーの値まで、更新されるという可能性を考慮するべきでした。

    ということで、先日頂きました以下のご回答より、Whereに一意となるキーのみ許容するように修正しました。
    >個人的な意見ですが、私であればUPDATEとDELETEはWhereにキーのみ許容する作りを軸にします。


    mts10806さんのご回答は、経験の少ない私にも理解しやすく、どう実装すべきかまで教えてくださるので、とても参考になります!

    本当にありがとうございます!

    キャンセル

+1

おなじtitleで二箇所していしているからでは?

ラベル付きではなく、疑問符でprepare処理をすると楽だと思います

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/06/25 22:23

    mts10806さんにもご指摘いただきましたが、
    prepareは複数のパラメーターマークを使用できないんですね....

    そして、疑問符でもprepareを処理できるのですね!
    これまで、パラメーターマークを使用していたので、疑問符でも試してみます!
    ありがとうございます!

    キャンセル

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

  • ただいまの回答率 87.92%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

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