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

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

ただいまの
回答率

89.10%

【PHPmySQL】チェックボックス・フリーワードでの検索条件の絞り込み方法

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 1,320

otokichi_

score 19

PHPmySQLでデータベースの検索結果を出力したいです。
接続はPDOで行いました。テーブル\(適当\)の画像は下に貼っておきます。

下記コードでは、フリーワードで入力した値は出力できる
ようになっています。チェックボックスや、フリーワードで
絞り込み検索する事が難しいです。

やりたい事を下部画像で言うと2つです。
・東京都にチェックを入れたら、所在地が東京都の行を出力する。

・東京都にチェックいれてフリーワードを入力すると、所在地が
東京かつフリーワードが含まれる行を出力する。

色々調べて挫折したため、ご教授いただけると大変助かります。
ご不備等ありましたら遠慮なくご指摘ください。
どうぞよろしくお願いいたします。

<\?php

\$database = 'mysql:dbname=trainning';
\$host = 'localhost';
\$username = 'trainning';
\$password = 'password';
\$table_name = 'tablename';

\$options = array\(
    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
\); 

try{

    \$pdo = new PDO\(\$database, \$username, \$password, \$options\);
    \$pdo->query\('SET NAMES utf8'\);
    print\('<br>'\);

    if \(\$pdo == null\){

        print\('接続に失敗しました。<br>'\);

    }

    \$pdo->query\('SET NAMES utf8'\);


}catch \(PDOException \$e\){
    print\('Error:'\.\$e->getMessage\(\)\);
    die\(\);
}

\?>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>search engine1\.1</title>

</head>

<body>

<center><h1>search engine1\.1</h1>

<form action="" method="get">

    <input type="checkbox" name="search1" value="東京都" ><label>東京都</label><br>
    <input type="checkbox" name="search2" value="大阪府"><label>大阪府</label><br>
    <input type="checkbox" name="search3" value="愛知県"><label>愛知県</label><br>
    <input type="checkbox" name="search4" value="福岡県"><label>福岡県</label><br>
    <input type="checkbox" name="search5" value="海外"><label>海外</label>

    <p><input type="search" name="search6" size="30" maxlength="255"placeholder="Search\.\.\.">
    <input type="submit" value="検索"></p>

</form>


</center>


<\?php
\$search_type_and = false;
\$raw_keyword = \$_GET\["search"\];
print\("検索したキーワード \[ "\.\$raw_keyword\." \]<br>"\);

//文字入力があれば実行
if\(null != \$raw_keyword\){

    if\(isset\(\$_GET\["page"\]\) && preg_match\("/\^\[0-9\]\+\$/", \$_GET\["page"\]\)\){
        \$page_number = \(int\)\$_GET\["page"\];
        if\(\$page_number <= 0\) \$page_number = 1;
    }
    else{
        \$page_number = 1;
    }
    \$page_articles = 10; // 1ページに表示する件数\(定数\)
    \$offset = \(\$page_number - 1\) \* \$page_articles; // オフセットを計算


    \$keywords = explode\(" ", trim\(str_replace\(" ", " ", \$raw_keyword\)\)\);

      \$target_columns = array\(

    "番号",
    "企業名",
    "ヨミガナ",
    "上場企業",
    "所在地",
    "資本金",
    "売上高",
    "業種",
    "事業内容",
    "従業員数",
    "備考欄",
    "PDFURL"

    \);

    \$where = array\(\);


      foreach\(\$target_columns as \$index_column => \$column\){ // カラムのループ
        \$tmp = array\(\);
        foreach\(\$keywords as \$index_keyword => \$keyword\){ // キーワードのループ
            \$tmp\[\] = "{\$column} LIKE :keyword{\$index_column}_{\$index_keyword}";
        }

        /\* AND検索なら、すべてのキーワードが含まれるデータを対象とする
           上のループで作ったLIKE文の配列を " AND " で繋げる \*/
        \$where\[\] = join\(\$search_type_and \? " AND " : " OR ", \$tmp\);
      }

    \$query = "SELECT SQL_CALC_FOUND_ROWS \* FROM {\$table_name} WHERE " \. join\(" OR ", \$where\)\.
             " LIMIT {\$offset}, {\$page_articles}";

    \$stmt = \$pdo->prepare\(\$query\);

    foreach\(\$target_columns as \$index_column => \$column\){
      foreach\(\$keywords as \$index_keyword => \$keyword\){
          if\(!\$stmt->bindValue\(":keyword{\$index_column}_{\$index_keyword}", "%{\$keyword}%"\)\) {
              exit\("Failed to bind value: " \. \$keyword\);
          }
      }
    }


    \$stmt->execute\(\);

    \$data_meta = null;

    if\(\$stmt_all = \$pdo->query\("SELECT FOUND_ROWS\(\) AS 'all'"\)\){
        \$data_meta = \$stmt_all->fetch\(\);


    }

    if\(!\$data_meta\){
        exit\("Failed to query FOUND_ROWS\(\)\."\);
    }

    \$all_data_length = \(int\)\$data_meta\["all"\];



    if\(\$all_data_length > \$offset\){
        \$cnt = 1;
        \$start = \$offset \+ 1; // 開始位置
        \$end = min\(\$offset \+ \$page_articles, \$all_data_length\); // 終了位置
        \$current_offset = \$start == \$end \? \$start : "{\$start}~{\$end}"; // 現在の位置

        print "\[{\$page_number}ページ目の検索結果\]\\n";
        print "{\$all_data_length} 件中 {\$current_offset} 件を表示しています。\\n\\n<br>";


        //ここで保存 \$session変数とかでセッションとかで保存すれば


        while\(\$data = \$stmt->fetch\(PDO::FETCH_ASSOC\)\){
            //print \$cnt\+\+ \. "つ目の検索結果:\\n";
            //print_r\(\$data\);

            \$pdf = \$data\['PDFURL'\];
            //ファイル名の後ろから4文字を削除
            \$pdf2 = substr\(\$pdf, 46, -4\);

            //urlを接続しながら吐き出し
            \$url = "<a href='";

            print\(\$url\.\$pdf\."'>"\.\$pdf2\."</a><br>"\);
            //print\("<br>"\.\$data\['PDFURL'\]\."<br>"\);
        }


    }
    else{
        print " 検索条件に該当する企業はありませんでした。
        条件を変更してもう一度検索してください。";
        /\*
            while\(\$data = \$stmt->fetch\(PDO::FETCH_ASSOC\)\){
            print \$cnt\+\+ \. "つ目の検索結果:\\n";
            //print_r\(\$data\);  //

            \$pdf = \$data\['PDFURL'\];
            //ファイル名の後ろから4文字を削除
            //pdf2 = substr\(\$pdf, 34, -4\);

            //urlを接続しながら吐き出し
            //\$url = "<a href='";

            //print\(\$url\.\$data\['備考欄'\]\."'>"\.\$pdf2\."</a><br>"\);
            print\("<br>"\.\$data\['PDFURL'\]\."<br>"\);


        }
        \*/
    }
}
\?>

      <p>現在 
        <\?php

        echo \$page_number; 
        \$maxpage = ceil\(\$all_data_length / \$page_articles\);
        //print\(\$maxpage\);


        \?> 
        ページ目です。</p><br>

        <\?php 

            if \(1 < \$page_number\){
              print\("<a href='\?search={\$raw_keyword}&page="\.\(\$page_number-1\)\."'>前のページへ | </a>"\);

            }


            if \(\$page_number < \$maxpage\){

              print\("<a href='\?search={\$raw_keyword}&page="\.\(\$page_number\+1\)\."'>次のページへ</a>"\);

            }


        \?>

</body>
</html>

イメージ説明

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • kei344

    2017/08/23 19:11

    「何」が「どのように」わからないのか、コードのどの部分で詰まっているのかなどを、出ているエラーなどと併せて、具体的に記述されたほうが回答を得られやすいと思います。

    キャンセル

  • otokichi_

    2017/08/23 22:55

    やりたい事は追記いたしました。何がどのように...。根本的にわかってないのかもしれませんね。

    キャンセル

  • kei344

    2017/08/23 23:02

    「したいこと」も必要ですが、「どこまでできているのか」と、「何をしたときに」「どうなると思って」「どうなったのか(ならなかったのか)」をかかれてはいかがでしょう。

    キャンセル

回答 2

checkベストアンサー

+1

2種類の独立な条件を用いた絞り込み検索の実装というケースになります。質問者様の場合、$where変数が第一条件(フリーワード検索)なので、新たに$where_areasという変数で第二条件である所在地の絞り込みを実装しました。提示コードをできるだけ継承し、手続き的に書いています。以下のポイントを確認しておいてください。

  • ユーザーがチェックした都道府県名を配列としてPHPに投げる際、inputタグのname属性は area[] のような形でパラメータ名を指定することにより、$_GET["area"] が配列になる
  • 第一条件と第二条件はAND検索になるよう、where句の組み立てを行う

おまけですが、検索結果を表示するページに都道府県の選択状態が反映されるようにしています。また、流し読みしただけでも、受け取った文字列をそのまま出力するなどの問題点が見受けられます。プロダクトとして運用する際は十分ご注意下さいね。以下の点を注意しておきます。

  • ページ数を求める過程のどこかでゼロ除算が発生しているっぽい
  • formタグのmethod属性はgetではなくGETにしましょう
  • "ファイル名の後ろから4文字を削除" している箇所で、substr関数の第2引数は0のはず
  • 海外の判定が面倒なのでテーブル設計を見直したほうがいい(たとえばJIS都道府県コードで管理する等)
  • LIKE句においてワイルドカードのエスケープがされていない
<?php

$database = 'mysql:dbname=trainning';
$host = 'localhost';
$username = 'trainning';
$password = 'password';
$table_name = 'tablename';

$options = array(
    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
);

try{

    $pdo = new PDO($database, $username, $password, $options);
    $pdo->query('SET NAMES utf8');
    print('<br>');

    if ($pdo == null){

        print('接続に失敗しました。<br>');

    }

    $pdo->query('SET NAMES utf8');


}catch (PDOException $e){
    print('Error:'.$e->getMessage());
    die();
}

/* 追加箇所1: GETパラメータにareasがあればそれを使う */
$areas = !empty($_GET["area"]) && is_array($_GET["area"]) ? array_values($_GET["area"]) : array();

?>

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>search engine1.1</title>

</head>

<body>

    <center><h1>search engine1.1</h1>

        <form action="" method="get">

            <?php
                /* 追加箇所2: 予め都道府県のリストを用意しておく */
                $predefined_areas = array("東京都", "大阪府", "愛知県", "福岡県", "北海道");
                foreach($predefined_areas as $area):
                    $checked = in_array($area, $areas) ? "checked" : "";
            ?>
                    <input type="checkbox" name="area[]" value="<?=$area?>" <?=$checked?>><label><?=$area?></label><br>
            <?php
                endforeach;
            ?>

            <p><input type="search" name="search" size="30" maxlength="255"placeholder="Search...">
                <input type="submit" value="検索"></p>

        </form>


    </center>


    <?php
    $search_type_and = false;
    $raw_keyword = $_GET["search"];
    print("検索したキーワード [ ".$raw_keyword." ]<br>");

    //文字入力があれば実行
    if(null != $raw_keyword){

        if(isset($_GET["page"]) && preg_match("/^[0-9]+$/", $_GET["page"])){
            $page_number = (int)$_GET["page"];
            if($page_number <= 0) $page_number = 1;
        }
        else{
            $page_number = 1;
        }
        $page_articles = 10; // 1ページに表示する件数(定数)
        $offset = ($page_number - 1) * $page_articles; // オフセットを計算


        $keywords = explode(" ", trim(str_replace(" ", " ", $raw_keyword)));

        $target_columns = array(

    "番号",
    "企業名",
    "ヨミガナ",
    "上場企業",
    "所在地",
    "資本金",
    "売上高",
    "業種",
    "事業内容",
    "従業員数",
    "備考欄",
    "PDFURL"

    );

        $where = array();

        foreach($target_columns as $index_column => $column){ // カラムのループ
            $tmp = array();
            foreach($keywords as $index_keyword => $keyword){ // キーワードのループ
                $tmp[] = "{$column} LIKE :keyword{$index_column}_{$index_keyword}";
            }

            /* AND検索なら、すべてのキーワードが含まれるデータを対象とする
               上のループで作ったLIKE文の配列を " AND " で繋げる */
            $where[] = join($search_type_and ? " AND " : " OR ", $tmp);
        }

        /* 追加箇所3: エリアの一覧を利用してSQL文を組み立てる */
        $where_area = array();
        foreach(array_keys($areas) as $key) {
            $where_area[] = "`所在地` LIKE :area{$key}";
        }

        /* 追加箇所4: 2種類の条件があるので、いいかんじにwhere句にする */
        /*           prepare() 前まで書き直しています。 */
        $conditions = array();
        if($where){
            $condition[] = "(" . join(" OR ", $where) . ")";
        }
        if($where_area){
            $condition[] = "(" . join(" OR ", $where_area) . ")";
        }

        $conditionString = $condition ? join(" AND ", $condition) : "1";
        $query = "SELECT SQL_CALC_FOUND_ROWS * FROM {$table_name} WHERE {$conditionString} LIMIT {$offset}, {$page_articles}";

        $stmt = $pdo->prepare($query);

        foreach($target_columns as $index_column => $column){
            foreach($keywords as $index_keyword => $keyword){
                if(!$stmt->bindValue(":keyword{$index_column}_{$index_keyword}", "%{$keyword}%")) {
                    exit("Failed to bind value: " . $keyword);
                }
            }
        }

        /* 追加箇所5: エリアの一覧をバインド */
        foreach($areas as $key => $area) {
            if(!$stmt->bindValue(":area{$key}", "{$area}%")) {
                exit("Failed to bind area");
            }
        }


        $stmt->execute();

        $data_meta = null;

        if($stmt_all = $pdo->query("SELECT FOUND_ROWS() AS 'all'")){
            $data_meta = $stmt_all->fetch();


        }

        if(!$data_meta){
            exit("Failed to query FOUND_ROWS().");
        }

        $all_data_length = (int)$data_meta["all"];



        if($all_data_length > $offset){
            $cnt = 1;
            $start = $offset + 1; // 開始位置
            $end = min($offset + $page_articles, $all_data_length); // 終了位置
            $current_offset = $start == $end ? $start : "{$start}~{$end}"; // 現在の位置

            print "[{$page_number}ページ目の検索結果]\n";
            print "{$all_data_length} 件中 {$current_offset} 件を表示しています。\n\n<br>";


            //ここで保存 $session変数とかでセッションとかで保存すれば


            while($data = $stmt->fetch(PDO::FETCH_ASSOC)){
                //print $cnt++ . "つ目の検索結果:\n";
                //print_r($data);

                $pdf = $data['PDFURL'];
                //ファイル名の後ろから4文字を削除
                $pdf2 = substr($pdf, 0, -4);

                //urlを接続しながら吐き出し
                $url = "<a href='";

                print($url.$pdf."'>".$pdf2."</a><br>");
                //print("<br>".$data['PDFURL']."<br>");
            }


        }
        else{
            print " 検索条件に該当する企業はありませんでした。
        条件を変更してもう一度検索してください。";
            /*
                while($data = $stmt->fetch(PDO::FETCH_ASSOC)){
                print $cnt++ . "つ目の検索結果:\n";
                //print_r($data);  //

                $pdf = $data['PDFURL'];
                //ファイル名の後ろから4文字を削除
                //pdf2 = substr($pdf, 34, -4);

                //urlを接続しながら吐き出し
                //$url = "<a href='";

                //print($url.$data['備考欄']."'>".$pdf2."</a><br>");
                print("<br>".$data['PDFURL']."<br>");


            }
            */
        }
    }
    ?>

    <p>現在
        <?php

        echo $page_number;
        $maxpage = ceil($all_data_length / $page_articles);
        //print($maxpage);


        ?>
        ページ目です。</p><br>

    <?php

    if (1 < $page_number){
        print("<a href='?search={$raw_keyword}&page=".($page_number-1)."'>前のページへ | </a>");

    }


    if ($page_number < $maxpage){

        print("<a href='?search={$raw_keyword}&page=".($page_number+1)."'>次のページへ</a>");

    }


    ?>

</body>
</html>

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

+1

phpで任意のグルーピングをした選択肢にチェックボックスを利用するなら
nameを揃えて「\[\]」を末尾につけるとよいでしょう

<form method="post">
<input type="checkbox" name="search\[\]" value="東京都" ><label>東京都</label><br>
<input type="checkbox" name="search\[\]" value="大阪府"><label>大阪府</label><br>
<input type="checkbox" name="search\[\]" value="愛知県"><label>愛知県</label><br>
<input type="checkbox" name="search\[\]" value="福岡県"><label>福岡県</label><br>
<input type="checkbox" name="search\[\]" value="海外"><label>海外</label><br>
<input type="submit" value="go">
</form>
<\?PHP
\$search=filter_input\(INPUT_POST,'search',FILTER_DEFAULT,\['flags'=>FILTER_REQUIRE_ARRAY\]\);
print_r\(\$search\);
\?>


これだけで相当楽になります
フリーワードの検索は完全一致ではない場合効率がそうとう悪くなりますので注意下さい
PDOをご利用でしたらあとはprepare処理にデータを渡すだけです

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/08/23 22:57

    ご回答ありがとうございます。これはグルーピングしてる場合ですよね??
    頑張ってやってみたのですが思うようにできなくて残念です。。。

    キャンセル

  • 2017/08/24 09:18

    $raw_keyword = $_GET["search"];
    としているのを見る限り、search1-6をここで受け取ろうとしているのではないのですか?
    一般にそのような処理の仕方がグルーピングの一つの手法となります

    難しいようならもっと簡素な例から始められることをお勧めします。
    たとえばフォームから値をうけとるだけとか
    所定の値つかってDBからデータを抽出するだけとか・・・

    キャンセル

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

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

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