🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
PDO

PDO(PHP Data Objects)はPHPのデータベース抽象化レイヤーです。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

Q&A

解決済

1回答

1823閲覧

PDOの検索対象カラムの追加と検索結果からのソートについて

mixpendable

総合スコア7

PDO

PDO(PHP Data Objects)はPHPのデータベース抽象化レイヤーです。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

0グッド

0クリップ

投稿2019/12/18 16:56

phpを勉強して一年ぐらいの初心者です。
質問内容やコード内容等、不慣れな箇所が多々あると思いますが、宜しくお願い致します。

以下コードのような検索ページをPDOで作成していますが、以下の点を実現しようと思考錯誤しています。

・検索キーワードが2つ以上でも検索できるようにしたい。
・検索対象のカラムを2つにしたい。現在contents1を検索対象にしていますが、contents2も検索対象にしたい。
・得られた検索結果をもとに、ORDER句(id順、日付順など)でソートしたい。
※今のコードではソートの最終更新日順、入手・作成日順のリンクを押しても、switch文のdefaultしか選択されないほかに全文検索した結果が表示されてしまいます。

コードが冗長的なところなどもあると思いますが、どうぞよろしくお願いいたします。

該当のソースコード

$maxrow = 50; $pagenum = 0; if(isset($_GET['pagenum']) && is_numeric($_GET['pagenum'])){ $pagenum = $_GET['pagenum']; } $startrow = $pagenum * $maxrow; //初期化 $err = ""; $key = ""; $condi = ""; $key = filter_input(INPUT_GET, 'key'); $key = mb_convert_kana($key,"KVas",'UTF-8'); $key = explode(" ", str_replace(" ", " ", trim($key))); $keys = array_filter($key ,function($val){ return $val != ''; }); try{ $pdo = new PDO("mysql:host=$hostname;dbname=$database;charset=utf8", $user, $pass); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //入力キーワード処理 foreach($keys as $key => $value){ $condi .= sprintf('AND contents1 LIKE :word%d', $key); } $ord=0;   if(isset($_GET['ord'])){$ord=$_GET['ord'];} switch($ord){ case 1: $ord2 = "order by getday desc,upd desc,id desc"; break; case 2: $ord2 = "order by upd desc,id desc"; break; default: $ord2 = "order by videoid desc"; break; } if($condi){ $videoSQL = "SELECT * FROM video WHERE 1 $condi $ord2"; }else{ $videoSQL = "SELECT * FROM video $ord2"; } //プリペアードステートメント $stmt = $pdo->prepare($videoSQL); //SQL文のプレースホルダーに値をバインド for($i=0; $i<count($keys); $i++){ //検索文字列の中のワイルドカード文字「_」「%」およびエスケープ文字「¥」をエスケープ $like = preg_replace('/[_%\\]/u', '\\$1', $keys[$i]); $stmt->bindValue(':word'.$i, '%' . $like . '%', PDO::PARAM_STR); } //実行 $stmt->execute(); $row_video = $stmt->fetchAll(PDO::FETCH_ASSOC); //ページング用にLIMIT句のSQLを作成 $stt = $pdo->prepare("$videoSQL LIMIT $startrow, $maxrow"); //SQL文のプレースホルダーに値をバインド for($i=0; $i<count($keys); $i++){ //検索文字列の中のワイルドカード文字「_」「%」およびエスケープ文字「¥」をエスケープ $like = preg_replace('/[_%\\]/u', '\\$1', $keys[$i]); $stt->bindValue(':word'.$i, '%' . $like . '%', PDO::PARAM_STR); } $stt->execute(); $limit_video = $stt->fetchAll(PDO::FETCH_ASSOC); if($stmt && $stt){ $tr_all = $stmt->rowCount(); $tp = ceil($tr_all/$maxrow)-1; } } catch(PDOException $e){ $err .= "<span class=\"red\">サーバエラーです</span>" . htmlspecialchars($e->getMessage()); } ?> <!DOCTYPE html> <html> <body> <p>検索結果</p> <?php if($err){ echo "<p>".$err."</p>"; }else{?> <?php if($tr_all){?> <p> <?php if($ord != 2){?><a href="<?php echo $_SERVER["PHP_SELF"];?>?ord=2">最終更新日順</a><?php }else{echo "最終更新日順";}?> | <?php if($ord != 1){?><a href="<?php echo $_SERVER["PHP_SELF"];?>?ord=1">撮影日順</a><?php }else{echo "撮影日順";}?> | <?php if($ord != 0){?><a href="<?php echo $_SERVER["PHP_SELF"];?>?ord=0">video No.順</a><?php }else{echo "video No.順";}?> </p> <form method="GET" action=""> <input type="text" name="key" /> <input type="submit" value="検索" /> </form> <p><?php if($condi){?> <?php echo $tr_all;?> 件中 <?php echo ($startrow + 1);?> ~ <?php echo min($startrow + $maxrow, $tr_all);?> 件を表示</p> <?php }?> <p> <?php if($pagenum){?> <a href="<?php echo $_SERVER["PHP_SELF"];?>?pagenum=0&ord=<?php echo $ord;?>">1</a> <?php }else{echo "1"; }?> <?php for($i=0;$i < $tp; $i++){ if(!($pagenum == ($i + 1))){ echo " <a href=\"".$_SERVER["PHP_SELF"]."?pagenum=".($i + 1)."&ord=".$ord."\">".($i + 2)."</a> |"; }else{ echo " ".($i + 2)." |"; } } ?> </p> <table> <tr> <td>内容</td> <td>video No.</td> <td>入手・作成(最終更新)</td> </tr> <?php foreach($limit_video as $row){?> <tr> <td> <a href="video_detail.php?id=<?php echo $row['id'];?>"> <?php echo $row['contents1'];?></a></td> <td><?php echo $row['videoid'];?></td> <td> <?php $getday = date("y/m/d",strtotime($row['getday'])); $upd = date("y/m/d",strtotime($row['upd'])); echo $getday."(".$upd . ")"; ?> </td> </tr> <?php }?> </table> <?php }else{?> <p class="l3">検索結果が0件です</p> <?php }?> <?php }?> </body> </html>

試したこと

フォーム入力されたキーワードをセッション変数

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

まず前提。
インデントがあったりなかったりで非常に読みづらいです。
構文チェック及びコードフォーマット機能のあるエディタを利用するのはもちろん(できればIDEと呼ばれるものが望ましい)
自身でもインデントは意識してコードを組むようにしてください。

それに、提示コードには全角スペースが大量に入っています。
これでは回答者が手元で質問者さんの現象を正しく確認することができません。

※下記は全角スペース部分を除去し、Eclipseで整形した例

php

1<?php 2$maxrow = 50; 3$pagenum = 0; 4 5if (isset($_GET['pagenum']) && is_numeric($_GET['pagenum'])) { 6 $pagenum = $_GET['pagenum']; 7} 8$startrow = $pagenum * $maxrow; 9 10// 初期化 11$err = ""; 12$key = ""; 13$condi = ""; 14 15$key = filter_input(INPUT_GET, 'key'); 16$key = mb_convert_kana($key, "KVas", 'UTF-8'); 17$key = explode(" ", str_replace(" ", " ", trim($key))); 18$keys = array_filter($key, function ($val) { 19 return $val != ''; 20}); 21 22try { 23 $pdo = new PDO("mysql:host=$hostname;dbname=$database;charset=utf8", $user, $pass); 24 $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); 25 $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 26 // 入力キーワード処理 27 foreach ($keys as $key => $value) { 28 $condi .= sprintf('AND contents1 LIKE :word%d', $key); 29 } 30 31 $ord = 0; 32 if (isset($_GET['ord'])) { 33 $ord = $_GET['ord']; 34 } 35 switch ($ord) { 36 case 1: 37 $ord2 = "order by getday desc,upd desc,id desc"; 38 break; 39 case 2: 40 $ord2 = "order by upd desc,id desc"; 41 break; 42 default: 43 $ord2 = "order by videoid desc"; 44 break; 45 } 46 47 if ($condi) { 48 $videoSQL = "SELECT * FROM video WHERE 1 $condi $ord2"; 49 } else { 50 $videoSQL = "SELECT * FROM video $ord2"; 51 } 52 53 // プリペアードステートメント 54 $stmt = $pdo->prepare($videoSQL); 55 56 // SQL文のプレースホルダーに値をバインド 57 for ($i = 0; $i < count($keys); $i ++) { 58 59 // 検索文字列の中のワイルドカード文字「_」「%」およびエスケープ文字「¥」をエスケープ 60 $like = preg_replace('/[_%\\]/u', '\\$1', $keys[$i]); 61 $stmt->bindValue(':word' . $i, '%' . $like . '%', PDO::PARAM_STR); 62 } 63 // 実行 64 $stmt->execute(); 65 $row_video = $stmt->fetchAll(PDO::FETCH_ASSOC); 66 67 // ページング用にLIMIT句のSQLを作成 68 $stt = $pdo->prepare("$videoSQL LIMIT $startrow, $maxrow"); 69 // SQL文のプレースホルダーに値をバインド 70 for ($i = 0; $i < count($keys); $i ++) { 71 72 // 検索文字列の中のワイルドカード文字「_」「%」およびエスケープ文字「¥」をエスケープ 73 $like = preg_replace('/[_%\\]/u', '\\$1', $keys[$i]); 74 $stt->bindValue(':word' . $i, '%' . $like . '%', PDO::PARAM_STR); 75 } 76 $stt->execute(); 77 $limit_video = $stt->fetchAll(PDO::FETCH_ASSOC); 78 79 if ($stmt && $stt) { 80 $tr_all = $stmt->rowCount(); 81 $tp = ceil($tr_all / $maxrow) - 1; 82 } 83} catch (PDOException $e) { 84 $err .= "<span class=\"red\">サーバエラーです</span>" . htmlspecialchars($e->getMessage()); 85} 86 87?> 88<!DOCTYPE html> 89<html> 90<body> 91 <p>検索結果</p> 92<?php 93 94if ($err) { 95 echo "<p>" . $err . "</p>"; 96} else { 97 ?> 98 99<?php if($tr_all){?> 100<p> 101<?php if($ord != 2){?><a href="<?php echo $_SERVER["PHP_SELF"];?>?ord=2">最終更新日順</a><?php }else{echo "最終更新日順";}?> | 102<?php if($ord != 1){?><a href="<?php echo $_SERVER["PHP_SELF"];?>?ord=1">撮影日順</a><?php }else{echo "撮影日順";}?> | 103<?php if($ord != 0){?><a href="<?php echo $_SERVER["PHP_SELF"];?>?ord=0">video 104 No.順</a><?php }else{echo "video No.順";}?> 105</p> 106 107 <form method="GET" action=""> 108 <input type="text" name="key" /> <input type="submit" value="検索" /> 109 110 </form> 111 <p><?php if($condi){?> 112<?php echo $tr_all;?> 件中 113<?php echo ($startrow + 1);?><?php echo min($startrow + $maxrow, $tr_all);?> 件を表示</p> 114<?php }?> 115<p> 116 <?php if($pagenum){?> 117 <a 118 href="<?php echo $_SERVER["PHP_SELF"];?>?pagenum=0&ord=<?php echo $ord;?>">1</a> 119 <?php 120 121} else { 122 echo "1"; 123 } 124 ?> 125 <?php 126 for ($i = 0; $i < $tp; $i ++) { 127 if (! ($pagenum == ($i + 1))) { 128 echo " <a href=\"" . $_SERVER["PHP_SELF"] . "?pagenum=" . ($i + 1) . "&ord=" . $ord . "\">" . ($i + 2) . "</a> |"; 129 } else { 130 echo " " . ($i + 2) . " |"; 131 } 132 } 133 ?> 134</p> 135 136 <table> 137 <tr> 138 <td>内容</td> 139 <td>video No.</td> 140 <td>入手・作成(最終更新)</td> 141 </tr> 142<?php foreach($limit_video as $row){?> 143<tr> 144 <td><a href="video_detail.php?id=<?php echo $row['id'];?>"> 145<?php echo $row['contents1'];?></a></td> 146 <td><?php echo $row['videoid'];?></td> 147 <td> 148 <?php 149 $getday = date("y/m/d", strtotime($row['getday'])); 150 $upd = date("y/m/d", strtotime($row['upd'])); 151 echo $getday . "(" . $upd . ")"; 152 ?> 153</td> 154 </tr> 155<?php }?> 156</table> 157 158 159<?php }else{?> 160<p class="l3">検索結果が0件です</p> 161<?php }?> 162<?php }?> 163 164</body> 165</html>

あと、下記、読んでください。

PDOStatement::rowCount

PDOStatement::rowCount() は 相当する PDOStatement オブジェクトによって実行された 直近の DELETE, INSERT, UPDATE 文によって作用した行数を返します。
中略
ほとんどのデータベースでは、PDOStatement::rowCount() は SELECT 文によって作用した行数を返しません。
代わりに、 PDO::query() を使って 意図する SELECT 文として同様の述部を持つ SELECT COUNT(*) 文を発行し、PDOStatement::fetchColumn() を使って返される行数を取得することができます。 そうすることで、アプリケーションは正しい動作をすることができます。

rowCount()ではなくSQLでcount()を実行しましょう。

回答本編

キーワード検索の件

まず、PHPは度外視してどういう結果を得たいか、そのためにどういうSQLを組むべきか、から考えると良いです。

・検索キーワードが2つ以上でも検索できるようにしたい。

・検索対象のカラムを2つにしたい。現在contents1を検索対象にしていますが、contents2も検索対象にしたい。

となると、コードから「スペースで区切られたワードを分割する」

①「それぞれのワードを全て含むデータを探す」
②「それぞれのワードをいずれか含むデータを探す」

a.contents1またはcontents2にそれらを含む?
b.contents1とcontents2にそれらを含む?

↑①②ab それぞれどっちになるか 組み合わせによってコードが変わります。

①-a

sql

1SELECT 2 * 3FROM 4 video 5WHERE 6 ( 7 contents1 LIKE '%word1%' 8 AND contents1 LIKE '%word2%' 9 AND contents1 LIKE '%word3%' 10 ) 11 OR 12 ( 13 contents2 LIKE '%word1%' 14 AND contents2 LIKE '%word2%' 15 AND contents2 LIKE '%word3%' 16 ) 17

②-a

sql

1SELECT 2 * 3FROM 4 video 5WHERE 6 ( 7 contents1 LIKE '%word1%' 8 AND contents1 LIKE '%word2%' 9 AND contents1 LIKE '%word3%' 10 ) 11 AND 12 ( 13 contents2 LIKE '%word1%' 14 AND contents2 LIKE '%word2%' 15 AND contents2 LIKE '%word3%' 16 ) 17

まあ、ORかANDが変わるくらいで、構成はほぼ変わりません。(②-a,②-bも考えてみてください)

で、決めたらDBに対して、PHPからではなくコマンドから直接実行して結果を確かめます。

これはPHPからHTML出力を組む時と同じで、まずは固定データを入れた静的な状態のコードを組んで想定の表示や結果になるかを確認し、
そこからPHPで同じコードを目指します。

そのコードになるまでexecuteもしません。
※というか、executeして想定の結果を得られるまでhtmlも書きません

そして、keyもいきなりGETで受け取るのではなく、固定値で試します。
スペースは全角も半角も受け入れるということで、そこは変わりなしと言う前提で、例えば下記。
※パターンは①-aを想定

php

1<?php 2$target_columns = ['contents1','contents2']; 3$where_condition = []; 4 5$key = "a b c d"; 6 7$key = mb_convert_encoding($key,'UTF-8','auto'); 8$key = mb_ereg_replace(" "," ",$key); 9 10if(trim($key) !== ""){ 11 $keys = explode(" ",$key); 12 13 foreach($target_columns as $column){ 14 $like_keyword = []; 15 foreach($keys as $keyword){ 16 $like_keyword[] = " {$column} LIKE '%{$keyword}%'\n"; 17 } 18 $where_condition[] = " 19( 20".implode(" AND ",$like_keyword)." 21) 22"; 23 } 24} 25 26$sql = " 27SELECT 28 * 29FROM 30 video 31"; 32if(count($where_condition) > 0){ 33 $sql .= " WHERE ".implode(" OR ",$where_condition); 34} 35$sql .= ";"; 36echo $sql;

ここができたら出来上がったSQLをDBに対して、PHPからではなくコマンドから直接実行して
始めに作ったSQLと同じ結果が出るかを確かめます。

あとはprepare,bindValueにあわせた形に整形していきます。

※構文エラーは出ませんが動作未確認です

php

1<?php 2$target_columns = ['contents1','contents2']; 3$where_condition = []; 4$keys = []; 5 6$key = "a b c d"; 7 8$key = mb_convert_encoding($key,'UTF-8','auto'); 9$key = mb_ereg_replace(" "," ",$key); 10 11if(trim($key) !== ""){ 12 $keys = explode(" ",$key); 13 14 foreach($target_columns as $column){ 15 $like_keyword = []; 16 foreach($keys as $num=>$keyword){ 17 $like_keyword[] = " {$column} LIKE :word{$num} \n"; 18 } 19 $where_condition[] = " 20( 21".implode(" AND ",$like_keyword)." 22) 23"; 24 } 25} 26 27$sql = " 28SELECT 29 * 30FROM 31 video 32"; 33if(count($where_condition) > 0){ 34 $sql .= " WHERE ".implode(" OR ",$where_condition); 35} 36$sql .= ";"; 37//echo $sql; 38 39try{ 40 //PDO接続部分省略 41 $stmt = $pdo->prepare($sql); 42 foreach($keys as $num=>$keyword){ 43 $stmt->bindValue(':word'.$num, '%' . $keyword . '%', PDO::PARAM_STR); 44 } 45 $stmt->execute(); 46 var_dump($stmt->fetchAll(PDO::FETCH_ASSOC)); 47 48}catch(PDOException $e){ 49 var_dump($e); 50}

ここで想定通りに動いてようやく、GETの出番です。
GETということはURLに出ているはずで、a b c dというキーワードを送ったらURLエンコードがかかってa+b%E3%80%80c+dのようになっていると思います。
もしecho $_GET['key'];として、a+b%E3%80%80c+dと出たのでしたら、スペースの処理の前にurldecode()しておく必要があります。

ソートの件

SQLとしては前項と同じく静的SQLを作るところから始めますので省略します。
つまり、GETも後です。
固定でGETに相当する情報を用意してSQLを作ります。

※GETするとしても下記のようなコードから確認していきましょう

php

1 $ord = 0; 2 3 if (isset($_GET['ord'])) { 4 echo "get ord:".$_GET['ord'].PHP_EOL; 5 $ord = $_GET['ord']; 6 } 7 8 switch($ord){ 9 case 1: 10 echo "1です"; 11 break; 12 case 2: 13 echo "2です"; 14 break; 15 default: 16 echo "それ以外です"; 17 }

そのうえで懸念。
下記、ちゃんと出力されていますか?

php

1<?php if($tr_all){?> 2<p> 3<?php if($ord != 2){?><a href="<?php echo $_SERVER["PHP_SELF"];?>?ord=2">最終更新日順</a><?php }else{echo "最終更新日順";}?> | 4<?php if($ord != 1){?><a href="<?php echo $_SERVER["PHP_SELF"];?>?ord=1">撮影日順</a><?php }else{echo "撮影日順";}?> | 5<?php if($ord != 0){?><a href="<?php echo $_SERVER["PHP_SELF"];?>?ord=0">video No.順</a><?php }else{echo "video No.順";}?> 6</p>

出力されているとして、$_GET['ord']は想定の値が渡ってきていますか?

いずれにしても、検索ワードがURLパラメータにつけられていないので、リンクを押すと全件での表示順が切り替わるだけです。
keyがあったら&key=$keyつけましょう。

ご確認ください。

投稿2019/12/18 21:04

編集2019/12/18 21:11
m.ts10806

総合スコア80875

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mixpendable

2019/12/19 00:05

たいへん分かりやすく親切なご回答をいただきまして、ありがとうございます! 見づらいコードで誠に申し訳ございませんでした。見やすいように整形していただき感謝です。 いただいた回答を参考に修正したいと思います。 ところで誠に恐縮ですが、いただいた回答について教えていただきたい点があります。 ① SQLをDBに対して、PHPからではなくコマンドから直接実行して結果を確めるというのは、どのように行うものでしょうか。 ② ソートに関するSQLは、回答部のtry~catch内に記述するのでしょうか。
m.ts10806

2019/12/19 00:23

① どのような形でも良いです。 コマンドプロンプトやターミナルからログインして実行するか、DBツール(MySQLならワークベンチとか)使うとかphpMyAdminとか。 ②はい。基本的にSQLはtry-catch内で実行します。
mixpendable

2019/12/19 11:59

回答ありがとうございます! おかげさまでコード修正が進みそうです。 またなにかありましたら、どうぞよろしくお願いいたしますm(_ _)m
m.ts10806

2019/12/19 18:57

特段追加でアドバイスがあるわけではないですが、この質問が「解決済み」となる基準は明確にしておいてください。
mixpendable

2019/12/20 14:25

ご返信遅れて申し訳ありません。 検索部分とソート部分が無事動きました! あとは、ページングの処理だけですのでなんとかなりそうです。 解決済みとさせていただきます。ありがとうございました。
m.ts10806

2019/12/20 14:40

解決されたようで何よりです。 念のため自身の質問に追記するか何かして、「どのように組んだか」 残しておいてください。 今後の自身ですとか同じ悩みを抱えた誰かのヒントにもなります。 https://teratail.com/help/question-tips#questionTips4-2
mixpendable

2019/12/20 19:58

ページング処理はできていませんが、今後のために、解決できた検索とソートのところまで残しておきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問