Objective: Query and loop through a page's third-level "grandchildren" in a way that allows pagination.

Here's the code that I'm using (pagination excluded):

        // Get the ID of the first generation
        $gen1_ids = $post->ID;

        // Query for second generation IDs
        $gen2 = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE $wpdb->posts.post_parent IN ($gen1_ids) AND $wpdb->posts.post_type = 'page' AND $wpdb->posts.post_status = 'publish' ORDER BY $wpdb->posts.ID ASC" ); // Test ordering by title

        // Implode the results for further use
        $gen2_ids = implode( $gen2,', ' );

        // Now, query for third generation IDs using second generation IDs
        $gen3 = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE $wpdb->posts.post_parent IN ($gen2_ids) AND $wpdb->posts.post_type = 'page' AND $wpdb->posts.post_status = 'publish' ORDER BY $wpdb->posts.ID ASC" );

        $args = array(
          'post__in' => $gen3,
          'post_type' => 'page',
          'post_status' => 'publish',
          'posts_per_page' => 5,
          'paged' => get_query_var('paged')

        $results = null;
        $results = new WP_Query( $args );

        if( $results->have_posts() ) { 
            while ( $results->have_posts() ) : $results->the_post(); ?>
                    <a href="<?php the_permalink(); ?>" class="img"><img src="#"></a>
                    <div class="text">
                        <h3><a href="<?php the_permalink() ?>"><?php the_title(); ?></a></h3>
        <?php endwhile;
        } /* end if */ ?>

     <?php wp_reset_query();  // Restore global post data stomped by the_post(). ?>

I've adapted the code from an answer to a support question two years back on the WordPress.org forums. The code is working.

Is there a way that this can be done better?

1 Answer 1


You're on the right track. You can do this with just one MySQL query instead of two by joining on the post_parent. Drop this function into your functions.php file, then in your template you can replace your WP_Query call with $results = wpse_84810_get_grandchildren();, change your if clause to be if( $results && $results->have_posts() ), then carry on with the rest of your template code.

function wpse_84810_get_grandchildren( $grandparent_id = false ) {
    global $wpdb;

    if ( !$grandparent_id )
        $grandparent_id = get_the_ID();

    $grandchildren_ids = $wpdb->get_col( $wpdb->prepare( "SELECT p1.ID FROM {$wpdb->posts} AS p1 INNER JOIN {$wpdb->posts} AS p2 ON p1.post_parent = p2.ID WHERE p2.post_parent = %d", $grandparent_id ) );

    if ( $grandchildren_ids ) {
        return new WP_Query( array(
            'post__in' => $grandchildren_ids,
            'post_type' => 'page',
            'post_status' => 'publish',
            'posts_per_page' => 5,
            'paged' => get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1
        ) );

    return false;
  • That looks solid to me! Thank you! (I have to wait to award bounty). For my reference, why does the if clause need to be updated to check for $results and $results->have_posts()? Commented Feb 12, 2013 at 21:31
  • If the post has no grandchildren, this function will return false. If you try to call the object method have_posts() on false, you'll get an error, so the if-clause addition is a safety net for that scenario Commented Feb 12, 2013 at 21:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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