First of all, why you are querying for posts by your custom query? We have WP_Query
class and get_posts
function to do this.
$child_pages = $wpdb->get_results("SELECT * FROM $wpdb->posts WHERE post_parent = ".$post->ID." AND post_type="page" ORDER BY menu_order", 'OBJECT');
became
$pages = get_posts(array(
'post_type' => 'page',
'orderby' => 'menu_order'
'order' => 'ASC'
'posts_per_page' => -1,
));
Next, you should use get_page_children
function (get_page_children on codex) to generate tree list. get_page_children
does not query to database each time we want to generate list of children, but is using list of all posts which is stored in $pages
variable.
<ul>
<?php foreach ($pages as $page): ?>
<?php if ($page->post_parent == 0): ?>
<li>
<?php echo $page->post_title; ?>
<?php $children = get_page_children($page->ID, $pages); ?>
<ul>
<?php foreach ($children as $child): ?>
<?php if ($child->post_parent == $page->ID): ?>
<li>
<?php echo $child->post_title; ?>
<?php $granchildren = get_page_children($child->ID, $pages); ?>
<ul>
<?php foreach ($granchildren as $grandchild): ?>
<li><?php echo $grandchild->post_title; ?></li>
<?php endforeach; ?>
</ul>
</li>
<?php endif; ?>
<?php endforeach; ?>
</ul>
</li>
<?php endif; ?>
<?php endforeach ?>
</ul>
EDIT. I think that this will fit to your expectation now:
<?php $posts = get_posts(array('posts_per_page' => -1, 'post_type' => 'page')); ?>
<?php $children = get_page_children($post->ID, $posts); ?>
<?php foreach ($children as $child): ?>
<?php if ($child->post_parent == $post->ID): ?>
<div>
<h2><?php echo $child->post_title; ?></h2>
<ul>
<?php $grandchildren = get_page_children($child->ID, $posts); ?>
<?php foreach ($grandchildren as $grandchild): ?>
<li><?php echo $grandchild->post_title; ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php endforeach; ?>