Getting Crazy Creative
with WordPress Queries
Drew Jaynes
WordCamp Cape Town 2015
• Platform Engineer at 10up
• Docs Committer for WordPress core
• 4.2 Release Lead
• Developing with WordPress since 2009
• Slides: http://drewf.us/wcct
Hi, I’m Drew.
• Basics
• No-Nos
• Optimizations
• Creative Querying
Topics
Query Basics
The Loop
if ( have_posts() ) :
while ( have_posts() ) :
the_post();
...
endwhile;
endif;
The Loop: Internals
if ( $wp_query->have_posts() ) :
while ( $wp_query->have_posts() ) :
$wp_query->the_post();
...
endwhile;
endif;
WP_Query
// Query for the 7 latest, published posts.
$query = new WP_Query( array(
'posts_per_page' => 7,
'post_status' => 'publish'
) );
WP_Query: SQL
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts
WHERE 1=1
AND wp_posts.post_type = 'post'
AND wp_posts.post_status = 'publish'
ORDER BY wp_posts.post_date DESC
LIMIT 0, 7
• WP_Query wrapper
• Defaults: Filter suppression
• Defaults: No sticky posts
• Defaults: No found rows
• Array of results vs WP_Query instance
get_posts()
get_posts()
// Query for the 7 latest, published posts.
$query = get_posts( array(
'posts_per_page' => 7,
'post_status' => 'publish'
) );
get_posts(): SQL
SELECT wp_posts.ID FROM wp_posts
WHERE 1=1
AND wp_posts.post_type = 'post'
AND ( ( wp_posts.post_status = 'publish' ) )
ORDER BY wp_posts.post_date DESC
LIMIT 0, 7
• Action, not a filter
• Fires before the query actually runs
• Use query methods intead of top-level functio
e.g. $query->is_main_query()
pre_get_posts
pre_get_posts
/**
* Display both posts and pages in the home loop.
*
* @param WP_Query $query Main WP_Query instance.
*/
function pages_in_main_query( $query ) {
if ( ! is_admin() && is_home() ) {
$query->query_vars['post_type'] = array( 'post', 'page' );
}
}
add_action( 'pre_get_posts', 'pages_in_main_query' );
pre_get_posts: Original SQL
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts
WHERE 1=1
AND wp_posts.post_type = 'post'
AND (
wp_posts.post_status = ‘publish'
OR wp_posts.post_status = ‘private'
)
ORDER BY wp_posts.post_date DESC
LIMIT 0, 10
pre_get_posts: SQL
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts
WHERE 1=1
AND wp_posts.post_type IN ( 'post', 'page' )
AND (
wp_posts.post_status = ‘publish'
OR wp_posts.post_status = ‘private'
)
ORDER BY wp_posts.post_date DESC
LIMIT 0, 10
No-Nos
• Completely overrides the main query
• Very few valid use cases
• Custom page template archives
query_posts()
query_posts()
query_posts( array(
'post_type' => ‘page’,
‘post_per_page' => 4
) );
posts_per_page
get_posts( array(
‘posts_per_page’ => -1,
‘posts_per_page’ => 100
) );
Optimization
• update_post_term_cache
• update_post_meta_cache
• no_found_rows
• fields
• posts_per_page
Optimizing WP_Query
Optimizing WP_Query
$query = new WP_Query( array(
'update_post_term_cache' => false,
'update_post_meta_cache' => false,
'posts_per_page' => 100,
'no_found_rows' => true,
'fields' => 'ids'
) );
Creative Querying
WP_Query
• posts_* filters
• pre_get_posts action
WP_Query hooks
WP_Query orderby
$posts = get_posts( array(
'posts_per_page' => 15,
'orderby' => array(
'post_date' => 'DESC',
'post_title' => 'ASC'
),
) );
WP_Query orderby: SQL
SELECT wp_posts.ID FROM wp_posts
WHERE 1=1
AND wp_posts.post_type = 'post'
AND ( ( wp_posts.post_status = 'publish' ) )
ORDER BY wp_posts.post_date DESC, wp_posts.post_title ASC
LIMIT 0, 15
• Available 4.2+
• Order by independent meta clauses
• WP_Meta_Query, WP_User_Query,
WP_Comment_Query
WP_Query orderby: meta clauses
WP_Query orderby: meta clauses
$posts = get_posts( array(
'meta_query' => array(
'relation' => 'AND',
'state_clause' => array(
'key' => 'state',
'value' => 'Colorado'
),
'city_clause' => array(
'key' => 'city',
'compare' => 'EXISTS'
)
),
'orderby' => array(
'state_clause' => 'ASC',
'city_clause' => 'DESC'
)
) );
WP_Query orderby: meta clauses
$posts = get_posts( array(
'meta_query' => array(
'relation' => 'AND',
'state_clause' => array(
'key' => 'state',
'value' => 'Colorado'
),
'city_clause' => array(
'key' => 'city',
'compare' => 'EXISTS'
)
),
'orderby' => array(
'state_clause' => 'ASC',
'city_clause' => 'DESC'
)
) );
WP_Query orderby: SQL
SELECT wp_posts.ID FROM wp_posts
INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id )
INNER JOIN wp_postmeta AS mt1 ON ( wp_posts.ID = mt1.post_id )
WHERE 1=1
AND (
( wp_postmeta.meta_key = ‘state’
AND CAST( wp_postmeta.meta_value AS CHAR ) = ‘Colorado'
)
AND mt1.meta_key = ‘city'
)
AND wp_posts.post_type = 'post'
AND ( ( wp_posts.post_status = 'publish' ) )
GROUP BY wp_posts.ID
ORDER BY
CAST( mt1.meta_value AS CHAR ) ASC,
CAST( wp_postmeta.meta_value AS CHAR ) DESC
LIMIT 0, 5
WP_User_Query
WP_User_Query
// Query for users registered on a Tuesday.
$query = new WP_User_Query( array(
'date_query' => array(
array(
'column' => 'user_registered',
'dayofweek' => 3 // Tuesday
)
),
) );
Custom WP_Query Orderby
• Order results by a value in a custom table
• Adds an additional LEFT JOIN
WP_Query orderby with custom tables
Joining Custom Tables: JOIN
/**
* Join vote totals table to the 'books' custom post type query.
*
* @param string $join JOIN query clauses.
* @param WP_Query $query Current WP_Query instance.
* @return string The filtered JOIN clauses.
*/
function join_votes_table( $join, $query ) {
global $wpdb;
if ( ! is_admin() ) {
if ( isset( $query->query_vars['post_type'] )
&& 'books' == $query->query_vars[‘post_type']
) {
$votes = $wpdb->prefix . 'up_down_post_vote_totals';
$join .= "LEFT JOIN $votes ON $wpdb->posts.ID = $votes.post_id ";
}
}
return $join;
}
add_filter( 'posts_join', 'join_votes_table', 10, 2 );
Joining Custom Tables: ORDERBY
/**
* Order by vote totals descending, then post date descending.
*
* @param string $orderby ORDER BY query clauses.
* @param WP_Query $query Current WP_Query instance.
* @return string The filtered ORDER BY query clauses.
*/
function orderby_votes_and_date( $orderby, $query ) {
global $wpdb;
if ( ! is_admin() ) {
if ( isset( $query->query_vars['post_type'] )
&& 'books' == $query->query_vars[‘post_type']
) {
$votes = $wpdb->prefix . 'up_down_post_vote_totals';
$orderby = "$votes.vote_count_up DESC, $wpdb->posts.post_date DESC";
}
}
return $orderby;
}
add_filter( 'posts_orderby', 'orderby_votes_and_date', 10, 2 );
Joining Custom Tables: SQL
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts
WHERE 1=1
AND wp_posts.post_type = 'books'
AND ( wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private' )
ORDER BY wp_posts.post_date DESC
LIMIT 0, 10
Original SQL:
Joining Custom Tables: SQL
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts
LEFT JOIN wp_up_down_post_vote_totals
ON wp_posts.ID = wp_up_down_post_vote_totals.post_id
WHERE 1=1
AND wp_posts.post_type = 'books'
AND ( wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private' )
ORDER BY
wp_up_down_post_vote_totals.vote_count_up DESC,
wp_posts.post_date DESC
LIMIT 0, 10
SQL with the JOIN:
• WordPress Code Reference
• WP_Query Reference (Codex)
• Trunk wp-includes/
Resources
Questions?
Drew Jaynes | @DrewAPicture
Slides: http://drewf.us/wcct

Getting Creative with WordPress Queries

  • 1.
    Getting Crazy Creative withWordPress Queries Drew Jaynes WordCamp Cape Town 2015
  • 2.
    • Platform Engineerat 10up • Docs Committer for WordPress core • 4.2 Release Lead • Developing with WordPress since 2009 • Slides: http://drewf.us/wcct Hi, I’m Drew.
  • 3.
    • Basics • No-Nos •Optimizations • Creative Querying Topics
  • 4.
  • 5.
    The Loop if (have_posts() ) : while ( have_posts() ) : the_post(); ... endwhile; endif;
  • 6.
    The Loop: Internals if( $wp_query->have_posts() ) : while ( $wp_query->have_posts() ) : $wp_query->the_post(); ... endwhile; endif;
  • 7.
    WP_Query // Query forthe 7 latest, published posts. $query = new WP_Query( array( 'posts_per_page' => 7, 'post_status' => 'publish' ) );
  • 8.
    WP_Query: SQL SELECT SQL_CALC_FOUND_ROWSwp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND wp_posts.post_status = 'publish' ORDER BY wp_posts.post_date DESC LIMIT 0, 7
  • 9.
    • WP_Query wrapper •Defaults: Filter suppression • Defaults: No sticky posts • Defaults: No found rows • Array of results vs WP_Query instance get_posts()
  • 10.
    get_posts() // Query forthe 7 latest, published posts. $query = get_posts( array( 'posts_per_page' => 7, 'post_status' => 'publish' ) );
  • 11.
    get_posts(): SQL SELECT wp_posts.IDFROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND ( ( wp_posts.post_status = 'publish' ) ) ORDER BY wp_posts.post_date DESC LIMIT 0, 7
  • 12.
    • Action, nota filter • Fires before the query actually runs • Use query methods intead of top-level functio e.g. $query->is_main_query() pre_get_posts
  • 13.
    pre_get_posts /** * Display bothposts and pages in the home loop. * * @param WP_Query $query Main WP_Query instance. */ function pages_in_main_query( $query ) { if ( ! is_admin() && is_home() ) { $query->query_vars['post_type'] = array( 'post', 'page' ); } } add_action( 'pre_get_posts', 'pages_in_main_query' );
  • 14.
    pre_get_posts: Original SQL SELECTSQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND ( wp_posts.post_status = ‘publish' OR wp_posts.post_status = ‘private' ) ORDER BY wp_posts.post_date DESC LIMIT 0, 10
  • 15.
    pre_get_posts: SQL SELECT SQL_CALC_FOUND_ROWSwp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type IN ( 'post', 'page' ) AND ( wp_posts.post_status = ‘publish' OR wp_posts.post_status = ‘private' ) ORDER BY wp_posts.post_date DESC LIMIT 0, 10
  • 16.
  • 17.
    • Completely overridesthe main query • Very few valid use cases • Custom page template archives query_posts()
  • 18.
    query_posts() query_posts( array( 'post_type' =>‘page’, ‘post_per_page' => 4 ) );
  • 19.
  • 20.
  • 21.
    • update_post_term_cache • update_post_meta_cache •no_found_rows • fields • posts_per_page Optimizing WP_Query
  • 22.
    Optimizing WP_Query $query =new WP_Query( array( 'update_post_term_cache' => false, 'update_post_meta_cache' => false, 'posts_per_page' => 100, 'no_found_rows' => true, 'fields' => 'ids' ) );
  • 23.
  • 24.
  • 25.
    • posts_* filters •pre_get_posts action WP_Query hooks
  • 26.
    WP_Query orderby $posts =get_posts( array( 'posts_per_page' => 15, 'orderby' => array( 'post_date' => 'DESC', 'post_title' => 'ASC' ), ) );
  • 27.
    WP_Query orderby: SQL SELECTwp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND ( ( wp_posts.post_status = 'publish' ) ) ORDER BY wp_posts.post_date DESC, wp_posts.post_title ASC LIMIT 0, 15
  • 28.
    • Available 4.2+ •Order by independent meta clauses • WP_Meta_Query, WP_User_Query, WP_Comment_Query WP_Query orderby: meta clauses
  • 29.
    WP_Query orderby: metaclauses $posts = get_posts( array( 'meta_query' => array( 'relation' => 'AND', 'state_clause' => array( 'key' => 'state', 'value' => 'Colorado' ), 'city_clause' => array( 'key' => 'city', 'compare' => 'EXISTS' ) ), 'orderby' => array( 'state_clause' => 'ASC', 'city_clause' => 'DESC' ) ) );
  • 30.
    WP_Query orderby: metaclauses $posts = get_posts( array( 'meta_query' => array( 'relation' => 'AND', 'state_clause' => array( 'key' => 'state', 'value' => 'Colorado' ), 'city_clause' => array( 'key' => 'city', 'compare' => 'EXISTS' ) ), 'orderby' => array( 'state_clause' => 'ASC', 'city_clause' => 'DESC' ) ) );
  • 31.
    WP_Query orderby: SQL SELECTwp_posts.ID FROM wp_posts INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id ) INNER JOIN wp_postmeta AS mt1 ON ( wp_posts.ID = mt1.post_id ) WHERE 1=1 AND ( ( wp_postmeta.meta_key = ‘state’ AND CAST( wp_postmeta.meta_value AS CHAR ) = ‘Colorado' ) AND mt1.meta_key = ‘city' ) AND wp_posts.post_type = 'post' AND ( ( wp_posts.post_status = 'publish' ) ) GROUP BY wp_posts.ID ORDER BY CAST( mt1.meta_value AS CHAR ) ASC, CAST( wp_postmeta.meta_value AS CHAR ) DESC LIMIT 0, 5
  • 32.
  • 33.
    WP_User_Query // Query forusers registered on a Tuesday. $query = new WP_User_Query( array( 'date_query' => array( array( 'column' => 'user_registered', 'dayofweek' => 3 // Tuesday ) ), ) );
  • 34.
  • 35.
    • Order resultsby a value in a custom table • Adds an additional LEFT JOIN WP_Query orderby with custom tables
  • 36.
    Joining Custom Tables:JOIN /** * Join vote totals table to the 'books' custom post type query. * * @param string $join JOIN query clauses. * @param WP_Query $query Current WP_Query instance. * @return string The filtered JOIN clauses. */ function join_votes_table( $join, $query ) { global $wpdb; if ( ! is_admin() ) { if ( isset( $query->query_vars['post_type'] ) && 'books' == $query->query_vars[‘post_type'] ) { $votes = $wpdb->prefix . 'up_down_post_vote_totals'; $join .= "LEFT JOIN $votes ON $wpdb->posts.ID = $votes.post_id "; } } return $join; } add_filter( 'posts_join', 'join_votes_table', 10, 2 );
  • 37.
    Joining Custom Tables:ORDERBY /** * Order by vote totals descending, then post date descending. * * @param string $orderby ORDER BY query clauses. * @param WP_Query $query Current WP_Query instance. * @return string The filtered ORDER BY query clauses. */ function orderby_votes_and_date( $orderby, $query ) { global $wpdb; if ( ! is_admin() ) { if ( isset( $query->query_vars['post_type'] ) && 'books' == $query->query_vars[‘post_type'] ) { $votes = $wpdb->prefix . 'up_down_post_vote_totals'; $orderby = "$votes.vote_count_up DESC, $wpdb->posts.post_date DESC"; } } return $orderby; } add_filter( 'posts_orderby', 'orderby_votes_and_date', 10, 2 );
  • 38.
    Joining Custom Tables:SQL SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'books' AND ( wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private' ) ORDER BY wp_posts.post_date DESC LIMIT 0, 10 Original SQL:
  • 39.
    Joining Custom Tables:SQL SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts LEFT JOIN wp_up_down_post_vote_totals ON wp_posts.ID = wp_up_down_post_vote_totals.post_id WHERE 1=1 AND wp_posts.post_type = 'books' AND ( wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private' ) ORDER BY wp_up_down_post_vote_totals.vote_count_up DESC, wp_posts.post_date DESC LIMIT 0, 10 SQL with the JOIN:
  • 40.
    • WordPress CodeReference • WP_Query Reference (Codex) • Trunk wp-includes/ Resources
  • 41.
    Questions? Drew Jaynes |@DrewAPicture Slides: http://drewf.us/wcct