💾 WP_Query Too Slow? Go Direct!
WP_Query is convenient but slow for complex reports. $wpdb runs raw SQL. Faster, more powerful, full control.
📝 Basic $wpdb
global $wpdb;
// Get single value
$user_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->users}");
// Get single row
$user = $wpdb->get_row("SELECT * FROM {$wpdb->users} WHERE ID = 123");
// Get all results (array of objects)
$users = $wpdb->get_results("SELECT * FROM {$wpdb->users} LIMIT 10");
// Get column
$emails = $wpdb->get_col("SELECT user_email FROM {$wpdb->users}");
// Insert (safe, auto-escaped)
$wpdb->insert(
$wpdb->postmeta,
[
'post_id' => 123,
'meta_key' => 'custom_field',
'meta_value' => 'Hello World'
]
);
// Update
$wpdb->update(
$wpdb->posts,
['post_title' => 'New Title'],
['ID' => 123]
);
🎯 Complex Queries
// Top 10 products by sales
$top_products = $wpdb->get_results("
SELECT p.ID, p.post_title, SUM(oi.meta_value) as total_sales
FROM {$wpdb->posts} p
JOIN {$wpdb->postmeta} oi ON p.ID = oi.post_id
WHERE p.post_type = 'product'
AND p.post_status = 'publish'
AND oi.meta_key = '_order_total'
GROUP BY p.ID
ORDER BY total_sales DESC
LIMIT 10
");
// Search with prepared statement (safe!)
$search = '%' . $wpdb->esc_like($search_term) . '%';
$results = $wpdb->get_results($wpdb->prepare("
SELECT * FROM {$wpdb->posts}
WHERE post_title LIKE %s
AND post_status = 'publish'
", $search));
✅ Best Practices
- Always use $wpdb->prepare() for user input (prevents SQL injection)
- Use $wpdb->prefix for table names (multisite compatible)
- Check for errors: if ($wpdb->last_error) { … }
- For reports, consider caching results with transients
“WP_Query took 5 seconds for report. Raw SQL with $wpdb took 0.2 seconds. 25x faster. Know when to bypass WP_Query.”
