8

I have a homepage displaying the home.php template, containing 2 sidebars with widgets in them.

The main query still pulls in the standard 10 posts, but since I'm not displaying these, I'd like to eliminate the query being made to the database entirely. If needs be, an empty post loop will do as I am not using the main loop in my home.php template.

How would I do this? I could use pre_get_posts to minimise and reduce the query, but that still leaves me with a very fast query, how do I eliminate it entirely?

  • 1
    This question seems to be worth reading. But if you asked me, I would use my own template and set it as static home page from settings, because while it would be possible, it's just not worth it. More interesting reading here. Either all that or I completely missed the point of your question. – N00b May 9 '16 at 13:58
7

The posts_request filter

Skimming through the WP_Query we find this part of interest:

if ( !$q['suppress_filters'] ) {
    /**
     * Filter the completed SQL query before sending.
     *
     * @since 2.0.0
     *
     * @param array    $request The complete SQL query.
     * @param WP_Query &$this   The WP_Query instance (passed by reference).
     */
      $this->request = apply_filters_ref_array( 'posts_request', 
          array( $this->request, &$this ) );
   }

   if ( 'ids' == $q['fields'] ) {
       $this->posts = $wpdb->get_col( $this->request );
       $this->posts = array_map( 'intval', $this->posts );
       $this->post_count = count( $this->posts );
       $this->set_found_posts( $q, $limits );
       return $this->posts;
   }

We might try to eliminate the main home request through the posts_request filter. Here's an example:

add_filter( 'posts_request', function( $request, \WP_Query $q )
{
    // Target main home query
    if ( $q->is_home() && $q->is_main_query() )
    {
        // Our early exit
        $q->set( 'fields', 'ids' );

        // No request
        $request = '';
    }

    return $request;    

}, PHP_INT_MAX, 2 );

where we force the 'fields' => 'ids' for early exit.

The posts_pre_query filter (WP 4.6+)

We could also use the new posts_pre_querysrc filter available in WordPress 4.6+

add_filter( 'posts_pre_query', function( $posts, \WP_Query $q )
{
    if( $q->is_home() && $q->is_main_query() )
    {
        $posts = [];
        $q->found_posts = 0;
    }
    return $posts;
}, 10, 2 );

This filter makes it possible to skip the usual database queries to implement a custom posts injection instead.

I just tested this and noticed that this will not prevent sticky posts, opposite to the posts_request approach.

Check out the ticket #36687 for more info and the example there by @boonebgorges.

  • Very similar to what I just wrote, but I didn't spot the fields ids part, that knocked another 2 queries off, thanks! – Tom J Nowell May 9 '16 at 15:34
  • 1
    ok great, sometimes it comes to mind how nice it would be to have an easy way to exit WP_Query early, e.g. through an argument like 'skip_query' => true or even through a filter, but then I realise how easily that could mess up sites around the internet, in so many ways ;-) @TomJNowell – birgire May 9 '16 at 15:40
  • There is a filter split_the_query just below the lines, do exactly the same but I wonder it does not reduce the number of queries! – Sumit May 9 '16 at 15:50
  • Great solution, even I missed 'fields' => 'ids', and I use it a lot ;-) – Pieter Goosen May 9 '16 at 16:08
2

Here is a neat trick I learned from @birgire, we can halt the main query by appending AND where 0=1 to the WHERE clause of the SQL query. This might still result in one db query, but it will surely stop the main query from querying posts

add_filter( 'posts_where', function ( $where, \WP_Query $q )
{
    if (    $q->is_home()
         && $q->is_main_query()
    ) {
        $where .= ' AND where 0 = 1';
    }

    return $where;
}, 10, 2 ); 

You can also just try to replace the WHERE clause with where 0 = 1

$where = ' where 0 = 1';

instead of

$where .= ' AND where 0 = 1';

Unfortunately, I do not have time to test anything, but this should be a nice starting point

  • This sounds like it's the closest I can get to what I asked for, or at least, the most heavily optimised query, I'll consider this while investigating if the query itself can be eradicated – Tom J Nowell May 9 '16 at 15:08
  • +1 for sharing this idea. But I tested it and it reduce the time by 2 ms :) – Sumit May 9 '16 at 15:10
  • I wish I could remember where I learned it from, but this is also used by core ;-) I think it would be nice to also ignore stickies $q->set( 'ignore_sticky_posts', true ); just in case. – birgire May 9 '16 at 15:24
  • @birgire Great spot. Even with my solution, you still get stickies back from the main query ;-) – Pieter Goosen May 9 '16 at 16:09
2

For reference, before: 45q, after: 42q

The code is very similar to the code used by @birgire

function _tomjn_home_cancel_query( $query, \WP_Query $q ) {
    if ( !$q->is_admin() && !$q->is_feed() && $q->is_home() && $q->is_main_query() ) {
        $query = false;
        $q->set( 'fields', 'ids' );
    }
    return $query;
}
add_filter( 'posts_request', '_tomjn_home_cancel_query', 100, 2 );

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.