0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

$wpdb->prepare の書き方、実務で迷うところだけ。IN句・LIKE・テーブル名【保存版】

0
Posted at

先に、いちばん迷う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まわりのことを書いています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?