先日、クライアントさんからの依頼でWordPress+カスタムフィールドで構築されたサイトに絞り込み検索機能を実装する必要が出てきました。
googleで検索すると下記のサイトに説明がありましたが、シンプルな全文検索にしか対応してないようなので、
これを参考にしつつ、もう少し複雑な検索(OR検索とAND検索の組み合わせ)に対応したものを作ってみました。
参考 http://www.deluxeblogtips.com/2012/04/search-all-custom-fields.html
global $wpdb; //次のSQLに SQL_CACHE を含めているのは、レンタルサーバーによってはMySQLのクエリキャッシュの //デフォルト設定を無効にしているところがあるので、明示的にクエリキャッシュを有効にするように指定している。 $sql = "SELECT SQL_CACHE DISTINCT posts.ID FROM {$wpdb->posts} posts"; //チェックボックスorセレクトボックスの検索(複数のAND検索) //例えばcolorというフィールド名だとする。 //$_GET['color']にはカスタムフィールドの設定画面の選択肢の箇所で入力したラベルと値のうち、値の方が配列になって入ってくるとする。 if( count($_GET['color']) ) { $flg_query = true; $sql .= " INNER JOIN {$wpdb->postmeta} tbl_color ON tbl_color.post_id=posts.ID AND tbl_color.meta_key = 'color'"; $tmp_arr = array(); foreach ($_GET['color'] as $item) { //シリアライズ化された文字列の中を全文検索する $keyword = '%' . $wpdb->esc_like( '"' . trim($item) . '"' ) . '%'; $tmp_arr[] = "tbl_color.meta_value LIKE '{$keyword}'"; } $sql .= " AND (" . implode(" AND ", $tmp_arr) . ") "; //↓ちなみにOR検索の場合はこうする //$sql .= " AND (" . implode(" OR ", $tmp_arr) . ") "; } //複数のチェックボックスorセレクトボックスがある場合は、ここに追記していく。 //注意点として、選択肢の値を 1,2,3・・や a,b,c・・のように設定すると //他のフィールドと値が重複して正確な検索が出来ないので、全フィールドを通じてユニークな値にする必要がある。 $sql .= " WHERE posts.post_type='(※投稿タイプ名※)' AND posts.post_status='publish'"; //全文検索(AND検索) //全文検索用キーワード //$_GET['keyword']にはキーワードが半角スペース区切りで入っているとする。 $free_keyword = isset($_GET['keyword']) ? trim($_GET['keyword']) : ""; //「キーワード検索」 if( $free_keyword != "" ) { $arr_free_keyword = $free_keyword == "" ? array() : explode(" ", $free_keyword); $arr_free_keyword = array_unique($arr_free_keyword); if( count($arr_free_keyword) > 8 ) { //検索キーワード数の上限をとりあえず8個までにする。 //ここが無制限だと、意図的に膨大なキーワード数で検索を実行された時にサーバーの処理が追いつかなくなる危険性が生じるので。 $arr_free_keyword = array_slice($arr_free_keyword, 0, 8); } $tbl_cnt = 1; foreach ($arr_free_keyword as $_key) { $tbl = "key" . $tbl_cnt++; $keyword = '%' . $wpdb->esc_like( trim($_key) ) . '%'; $sql .= " AND (SELECT 1 FROM {$wpdb->postmeta} {$tbl} WHERE {$tbl}.post_id=posts.ID AND ( ({$tbl}.meta_key NOT LIKE '\_%' AND {$tbl}.meta_value LIKE '{$keyword}') OR posts.post_title LIKE '{$keyword}' OR posts.post_content LIKE '{$keyword}') LIMIT 1)"; //↑DISTINCTにしてもいいのだが、LIMIT 1 の方が速度が早いと思われるので(ただし未検証) } } $post_ids = $wpdb->get_col($sql); //検索でヒットした投稿データのIDが入る $cnt = count($post_ids); //投稿数 if($cnt) { $args = array( 'post_type' => '(※投稿タイプ名※)', 'post_status' => 'publish', 'post__in' => $post_ids, //ページングする場合は次のパラメーターをお好みで 'posts_per_page' => ****, 'offset' => ***, //ソートする場合は 次のパラメーターをお好みで 'meta_key' => '******', 'order' => "ASC", /* or DESC */ 'orderby' => "meta_value" ); $posts = get_posts($args); } else { $posts = array(); } 以下略
尚、テーブル wp_postmeta にインデックスを追加するとセレクトボックス・チェックボックスでの検索速度が向上すると思われます(ただしキーワードによる全文検索にはインデックスは効果ありません)。
例) ALTER TABLE wp_postmeta ADD INDEX idx_custom1 (post_id, meta_key(16), meta_value(16));
と、ここまで書いておいて言うのもなんですが、今回の記事で書いた方法は、小規模〜中規模のサイトまでしか通用しないということにご留意ください。
カスタムフィールドを追加していくとテーブル wp_postmeta の行数が等比級数的に増えていきます。
(私の知ってるクライアントでの例だとカスタムフィールド数が約60、登録ページ数が約30で wp_postmeta が約40万行でした。)
これだけ膨大なデータの中から検索を実行するわけなので、かなりの負荷になります。
最近、なんでもかんでもWordPress+カスタムフィールドで実装すようとするIT屋さんが少なくないようですが、データ数やアクセス数(PV)がある程度まで増えると負荷が増大して破綻するのは火を見るよりも明らかです。
データ数やアクセス数が大規模になりそうなサイトはWordPressに頼らずフルスクラッチで開発するべきです。