2026年6月23日
2026年6月23日
WordPressの$wpdbでSQLを実行する方法|get_results・insert・prepareの使い方
WordPress のデータベースは、グローバル変数 $wpdb(wpdb クラスのオブジェクト)から操作できます。投稿やユーザーなら WP_Query や get_posts()、get_user_meta() などの関数を使うのが基本ですが、プラグインで作った独自のテーブルを読み書きするときや、集計のように関数では書きにくい処理では、$wpdb で SQL を直接実行します。
この記事のメソッドと戻り値は、WordPress の公式リファレンス(developer.wordpress.org の wpdb クラス)と wp-includes/class-wpdb.php で確認し、コード例は WordPress 7.1 で動作を確かめています(2026年10月時点)。
$wpdb を使う前に
関数の中では global 宣言が必要
$wpdb はグローバル変数なので、関数やメソッドの中で使うときは global $wpdb; を書きます。テーマの functions.php やプラグインのファイルは WordPress が読み込んだ後に動くため、そのまま使えます。
<?php
function myplugin_count_published_posts() {
global $wpdb;
return (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s AND post_status = %s",
'post',
'publish'
)
);
}
テーブル名は wp_ と直接書かない
テーブル名の接頭辞(wp_ の部分)は wp-config.php の $table_prefix でサイトごとに変えられます。wp_posts のように直接書くと、接頭辞が違うサイトでは動きません。
| 書き方 | 意味 |
|---|---|
$wpdb->posts、$wpdb->postmeta、$wpdb->users、$wpdb->options など |
WordPress 標準のテーブル名(接頭辞付き) |
$wpdb->prefix . 'my_table' |
自分で作ったテーブル名(マルチサイトでは今のサイトの接頭辞になる) |
$wpdb->base_prefix . 'my_table' |
マルチサイトで、全サイト共通のテーブルを使う場合 |
データを読み取る
読み取りには、結果の形に合わせて4つのメソッドを使い分けます。
| メソッド | 返すもの | 使う場面 |
|---|---|---|
get_results( $sql, $output ) |
全行の配列 | 一覧を取る |
get_row( $sql, $output ) |
1行だけ | ID で1件を取る |
get_col( $sql ) |
1列分の値の配列 | ID の一覧だけ欲しい |
get_var( $sql ) |
1つの値 | 件数や合計を取る |
$output には次の定数を指定できます。省略すると OBJECT です。
OBJECT: 各行をオブジェクトで返す($row->titleで読む)ARRAY_A: 各行を列名がキーの連想配列で返す($row['title'])ARRAY_N: 各行を番号の配列で返すOBJECT_K:get_results()のみ。1列目の値をキーにした配列で返す
例として、独自テーブル {接頭辞}myplugin_items(列は id、title、price、created_at)を読みます。
<?php
global $wpdb;
$table = $wpdb->prefix . 'myplugin_items';
// 価格が 1000 以上の行を新しい順に 10 件(連想配列で受け取る)
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT id, title, price FROM %i WHERE price >= %d ORDER BY created_at DESC LIMIT %d",
$table,
1000,
10
),
ARRAY_A
);
foreach ( $rows as $row ) {
echo esc_html( $row['title'] ) . ':' . (int) $row['price'] . "円\n";
}
// ID が 5 の行を 1 件(オブジェクトで受け取る)
$item = $wpdb->get_row(
$wpdb->prepare( "SELECT * FROM %i WHERE id = %d", $table, 5 )
);
if ( null === $item ) {
// 該当する行が無い
}
// 行数だけを取る
$total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table}" );
get_row() と get_var() は、該当する行が無いと null を返します。get_results() は、行が無いときは空の配列を返します。
データを書き込む(insert・update・delete)
1行の追加・更新・削除には専用のメソッドがあります。値は自動でエスケープされるので、prepare() は使いません。3番目以降の引数で、値の型を %s(文字列)、%d(整数)、%f(小数)で指定します。順番は渡した配列の順番と同じです。
追加: insert
<?php
global $wpdb;
$table = $wpdb->prefix . 'myplugin_items';
$result = $wpdb->insert(
$table,
array(
'title' => 'サンプル商品',
'price' => 1980,
'created_at' => current_time( 'mysql' ),
),
array( '%s', '%d', '%s' )
);
if ( false === $result ) {
error_log( 'insert に失敗: ' . $wpdb->last_error );
} else {
$new_id = $wpdb->insert_id; // AUTO_INCREMENT で振られた ID
}
insert() は追加した行数(通常は 1)を、失敗すると false を返します。追加した行の ID は直後の $wpdb->insert_id で取れます。
更新: update
<?php
$updated = $wpdb->update(
$table,
array( 'price' => 2480 ), // 変更する列と値
array( 'id' => $new_id ), // WHERE の条件
array( '%d' ), // 変更する値の型
array( '%d' ) // 条件の値の型
);
if ( false === $updated ) {
error_log( 'update に失敗: ' . $wpdb->last_error );
} elseif ( 0 === $updated ) {
// エラーではないが、変わった行が無い(条件に合う行が無い、または同じ値だった)
}
update() は変更した行数を返します。0 はエラーではないので、失敗の判定は false === $updated のように厳密に比べます。
削除: delete
<?php
$deleted = $wpdb->delete( $table, array( 'id' => $new_id ), array( '%d' ) );
update() と delete() の条件は、配列の要素がすべて AND でつながり、= で比べます。IN や < などの条件が必要なときは、次の query() と prepare() で書きます。
任意の SQL を実行する: query
query() は、どんな SQL でも実行できます。戻り値は次のとおりです。
SELECT・INSERT・UPDATE・DELETEなど: 対象になった行数CREATE・ALTER・TRUNCATE・DROP:true- エラー:
false
<?php
global $wpdb;
$table = $wpdb->prefix . 'myplugin_items';
// 作成から 90 日を過ぎた行をまとめて削除する
$count = $wpdb->query(
$wpdb->prepare(
"DELETE FROM %i WHERE created_at < %s",
$table,
gmdate( 'Y-m-d H:i:s', time() - 90 * DAY_IN_SECONDS )
)
);
query() で SELECT を実行した場合、結果の行は $wpdb->last_result に入りますが、読み取りは前の節の get_results() などを使うほうが分かりやすくなります。
prepare で値を安全に埋め込む
利用者の入力やリクエストの値を SQL に入れるときは、文字列を連結せず、prepare() のプレースホルダーを使います。連結すると SQL インジェクションの原因になります。
<?php
// 悪い例: 入力値をそのまま連結している
$rows = $wpdb->get_results( "SELECT * FROM {$table} WHERE title = '" . $_GET['q'] . "'" );
// よい例: プレースホルダーで渡す
$q = isset( $_GET['q'] ) ? sanitize_text_field( wp_unslash( $_GET['q'] ) ) : '';
$rows = $wpdb->get_results(
$wpdb->prepare( "SELECT * FROM %i WHERE title = %s", $table, $q )
);
prepare() の決まりは次のとおりです。
- プレースホルダーは
%s(文字列)、%d(整数)、%f(小数)、%i(テーブル名・列名などの識別子。WordPress 6.2 以降)。 %sを引用符で囲まない('%s'ではなく%s)。引用符はprepare()が付けます。- SQL の中に
%の文字そのものを書くときは%%と書く(例:DATE_FORMAT(created_at, '%%Y-%%m'))。 - 値は1つずつ引数で渡すか、配列1つにまとめて渡す(混ぜない)。
prepare()は SQL を実行しません。返ってきた文字列をget_results()やquery()に渡します。
LIKE で部分一致を検索する
LIKE の % は SQL 文に直接書かず、値の側に付けて渡します。利用者の入力に含まれる % や _ がワイルドカードとして働かないよう、先に esc_like() を通します。
<?php
$like = '%' . $wpdb->esc_like( $q ) . '%';
$rows = $wpdb->get_results(
$wpdb->prepare( "SELECT id, title FROM %i WHERE title LIKE %s", $table, $like )
);
IN 句に配列を渡す
IN ( ... ) に複数の値を渡すときは、値の数だけプレースホルダーを作ります。
<?php
$ids = array( 3, 8, 15 );
$placeholders = implode( ', ', array_fill( 0, count( $ids ), '%d' ) );
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT id, title FROM %i WHERE id IN ( {$placeholders} )",
array_merge( array( $table ), $ids )
)
);
$ids が空の配列だと IN () となって SQL の構文エラーになるので、空のときは実行しないようにします。
エラーを確かめる
$wpdb は SQL のエラーで例外を投げません。WordPress はデータベースに接続するときに mysqli_report( MYSQLI_REPORT_OFF ) を実行しているため、PHP 8.1 以降でも try / catch ではエラーを捕まえられません。戻り値が false かどうかを見て、次のプロパティで内容を確かめます。
| プロパティ | 内容 |
|---|---|
$wpdb->last_error |
直前のクエリのエラーメッセージ(エラーが無ければ空文字) |
$wpdb->last_query |
直前に実行した SQL |
$wpdb->insert_id |
直前の insert() などで振られた ID |
$wpdb->rows_affected |
直前の書き込みで変わった行数 |
<?php
$result = $wpdb->query( $sql );
if ( false === $result ) {
error_log( sprintf( 'SQL エラー: %s / SQL: %s', $wpdb->last_error, $wpdb->last_query ) );
}
wp-config.php で WP_DEBUG と WP_DEBUG_DISPLAY がどちらも true のときは、データベースのエラーが画面にも表示されます。本番サイトでは画面に出さず、WP_DEBUG_LOG でログに書き出す設定にします。
独自テーブルを作る・インデックスを付ける
独自テーブルは、プラグインを有効化したときに dbDelta() で作るのが WordPress の標準的な方法です。インデックスも CREATE TABLE の中に書いておけば一緒に作られ、後から列やインデックスを足したときも、同じ関数で差分が反映されます。
<?php
/**
* Plugin Name: My Plugin Items
*/
register_activation_hook( __FILE__, 'myplugin_create_table' );
function myplugin_create_table() {
global $wpdb;
$table = $wpdb->prefix . 'myplugin_items';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE {$table} (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
title varchar(191) NOT NULL DEFAULT '',
price int(11) NOT NULL DEFAULT 0,
created_at datetime NOT NULL,
PRIMARY KEY (id),
KEY price (price),
KEY created_at (created_at)
) {$charset_collate};";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
}
dbDelta() は SQL の書き方に厳しく、公式ドキュメント(Plugin Handbook の Creating Tables with Plugins)では次の書き方が求められています。
- 列の定義は1行に1つずつ書く
PRIMARY KEYと(id)の間は半角スペースを2つ空ける- インデックスは
INDEXではなくKEYと書き、KEY 名前 (列名)の形にする - 列名を引用符やバッククォートで囲まない
- 列の型は小文字、
CREATE TABLEなどの SQL のキーワードは大文字で書く - 長さを指定できる型には長さを書く(例:
int(11))
テーブルの変更をバージョンごとに管理する方法は、WordPressのカスタムテーブルをSCHEMA_VERSIONで管理する方法で説明しています。
使うときの注意
- 先に標準の関数で書けないか確かめる: 投稿・メタデータ・オプションを
$wpdbで直接書き換えると、オブジェクトキャッシュが更新されず、save_postなどのフックも動きません。標準テーブルへの書き込みはwp_update_post()やupdate_post_meta()を使います。 - DELETE や UPDATE は件数を確かめてから実行する: 同じ条件で
SELECT COUNT(*)を実行し、対象の行数を確かめてから書き換えます。本番では事前にデータベースをバックアップします。 - 重いクエリの結果は保存して使い回す:
$wpdbは結果をリクエストをまたいで保存しません。集計のように時間がかかる結果は、set_transient()やwp_cache_set()で保存しておくと、毎回の実行を避けられます。 - 出力するときはエスケープする: データベースから取り出した値を HTML に出すときは
esc_html()やesc_attr()を通します。prepare()は SQL 用のエスケープで、HTML 用ではありません。
まとめ
- 関数の中では
global $wpdb;を書き、テーブル名は$wpdb->postsや$wpdb->prefix . 'テーブル名'で組み立てる - 読み取りは
get_results()・get_row()・get_col()・get_var()、1行の書き込みはinsert()・update()・delete()、それ以外はquery() - 外から来た値は
prepare()のプレースホルダー(%s・%d・%f・%i)で渡す。LIKEはesc_like()、INは値の数だけプレースホルダーを作る - エラーは例外では来ない。戻り値の
falseと$wpdb->last_errorで確かめる - 独自テーブルとインデックスは、有効化時に
dbDelta()で作る