先に、いちばん迷う3つの答えを書きます。$wpdb->prepare で実務でつまずくのは、だいたいこの3か所です。
- IN句:プレースホルダを、配列の数だけ動的に作る
- LIKE:
$wpdb->esc_like()を通してから%sで渡す - テーブル名:プレースホルダにできない。
{$wpdb->prefix}を使う
基本の %s / %d / %f は迷わないので、上の3つを中心に、コピペできる形で置いておきます。
基本:値はプレースホルダで渡す
まず土台です。値を文字列連結せず、必ずプレースホルダに渡します。
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_author = %d AND post_status = %s",
$author_id,
$status
)
);
プレースホルダは3種類。文字列は %s、整数は %d、浮動小数は %f。この3つで、値の連結は避けられます。ここまでは、たいてい問題ありません。
IN句:プレースホルダを数だけ作る
いちばん質問が多いところです。IN (%s) に配列を渡しても、うまくいきません。要素の数だけプレースホルダを動的に作ります。
// ❌ NG:これは効かない
$wpdb->prepare("... WHERE id IN (%d)", $ids);
// ✅ OK:要素数ぶんの %d を組み立ててから渡す
$ids = [12, 45, 78];
$placeholders = implode(',', array_fill(0, count($ids), '%d'));
$sql = $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE ID IN ($placeholders)",
$ids // 配列をそのまま可変引数として渡す
);
$rows = $wpdb->get_results($sql);
array_fill で %d,%d,%d を作り、それをSQLに埋めてから、値の配列を渡します。$ids が空だと IN () になって構文エラーになるので、空チェックは先に入れておきます。
if (empty($ids)) {
return []; // IN () を避ける
}
LIKE:esc_like を通してから %s
LIKE検索は、二段構えです。まず $wpdb->esc_like() で % や _ をエスケープし、そのうえで %s に渡します。
// ✅ OK:esc_like → ワイルドカードを付ける → %s
$keyword = $wpdb->esc_like($search);
$like = '%' . $keyword . '%';
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_title LIKE %s",
$like
)
);
なぜ二段かというと、ユーザーの入力に % や _ が含まれていると、それがLIKEのワイルドカードとして働いて、意図しない検索になるからです。esc_like で中の記号を無効化し、検索用のワイルドカード % は自分で外側に付ける。この順番です。
テーブル名:プレースホルダにできない
意外と知られていないのが、これです。%s で渡せるのは値だけで、テーブル名やカラム名は渡せません。
// ❌ NG:テーブル名をプレースホルダにはできない
$wpdb->prepare("SELECT * FROM %s WHERE id = %d", $table, $id);
// ✅ OK:テーブル名は $wpdb のプロパティ / prefix で組む
$table = $wpdb->prefix . 'my_plugin_items';
$wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $id);
$wpdb->posts や $wpdb->prefix は、自分で定義した安全な文字列なので、そのまま埋めて大丈夫です。ユーザー入力からテーブル名やカラム名を組む場合は、プレースホルダが使えないぶん、事前に許可リスト(想定するカラム名だけを通す)で弾きます。
// 動的カラム名は、許可リストで検証してから埋める
$allowed = ['created_at', 'updated_at', 'score'];
$orderby = in_array($order_col, $allowed, true) ? $order_col : 'created_at';
$wpdb->prepare("SELECT * FROM {$table} ORDER BY {$orderby} LIMIT %d", $limit);
よくある取り違え
最後に、細かいけれど踏みやすいところを。
%s の周りに、自分でクォートを付けないこと。prepare が自動でクォートを足すので、'%s' と書くと二重になります。素の %s で渡します。
%d に文字列を渡さないこと。IDのつもりで文字列を渡すと、意図がぼやけます。整数は %d、文字列は %s を、値の型に合わせます。
prepare の結果を、さらに連結しないこと。prepare で作った安全なSQLに、後から別の変数を文字列連結すると、そこだけ穴になります。埋めたい値は、全部 prepare の引数で渡し切ります。
早見でまとめ
- 値は
%s/%d/%f。自分でクォートは付けない - IN句は
array_fillでプレースホルダを数だけ作る。空配列は先に弾く - LIKEは
esc_likeを通してから、ワイルドカードを外付けして%s - テーブル名・カラム名はプレースホルダ不可。
$wpdb->prefixで組む/許可リストで検証 -
prepareの結果に、後から値を連結しない
prepare は、慣れると迷わなくなりますが、IN句とLIKEとテーブル名の3つだけは、毎回どう書くか一瞬考えます。自分用のメモとして置いておきます。役に立ったらストックして、SQLを書くとき見返してください。
ふだんはraplsworks.comで、WordPressプラグイン開発やClaude Codeまわりのことを書いています。