I want to do something like this

$query_args = array(
    'post_type' => array( 'cpt_1', 'cpt_2' ),
    'post__in' => array( 1, 3, 5, 7 )

where I can specify which post type the post__in applies to.

For example, I want to get all the posts from cpt_1, but I only want the cpt_2 ones that are specified in post__in. Is there an SQL hook (like posts_where) that can help me accomplish this? If not I guess I'll have to do two queries?

1 Answer 1


WP_Query is not capable of that kind of logic without a lot of help. There are a few things you can do though:

  1. Just run two distinct queries, one for each post type. This is simple and quick but your posts aren't "mixed" together. I don't know if that is what you want. You'd then need two separate Loops as well. I am sure you can set this up so I am not posting code for it.

  2. Run multiple queries and combine them.

    $post_ids = new WP_Query(array(
      // query for one post type
      'fields' => 'ids',
      // other parameters
    $post_ids_2 = new WP_Query(array(
      // query for the others
      'fields' => 'ids',
      // other parameters
    // join them
    $post_ids = $post_ids->posts + $post_ids_2->posts;
    $posts_qry = new WP_Query(array('post__in'=> $posts_ids));

    There is a performance issue with this technique as you need to run three queries to make it work. Your posts are intermingled and you can use one Loop to display them.

  3. Write a lot of SQL-- ie. a UNION (Code stolen form another post so it is illustrative only. You will need to alter it.)

    $sql = "(SELECT ID FROM {$wpdb->posts} WHERE post_type = 'books' AND post_status = 'publish' LIMIT 3)
    (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'magazines' AND post_status = 'publish' LIMIT 2)
    (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'videos' AND post_status = 'publish' LIMIT 1)";
    // be sure to add any other conditions you need
    $ids = $wpdb->get_col($sql);
  4. Now the fun part-- filters. You will need post_fields, posts_join, and posts_where to do this, or posts_clauses. The latter is by far the simplest.

    function post_in_one_type($clauses) {
      global $wpdb;
      $clauses['fields'] .= ', cpt2.*';
      $clauses['join'] .= ", {$wpdb->posts} as cpt2 ";
      $where = preg_match(
        "/ AND (.*)/",
      $cpt2_where = str_replace($wpdb->posts,'cpt2',$matches[1]);
      $cpt2_where = str_replace('cpt_1','cpt_2',$cpt2_where).' AND cpt2.ID IN (1, 3, 5, 7) ';
      $clauses['where'] = ' AND ('.$matches[1].') || ('.$cpt2_where.') ';
      return $clauses;
    $args = array(
        'post_type' => array( 'cpt_1' )
    $q = new WP_Query($args);

    It is nearly impossible to test that effectively so no promises. I would try the other solutions first and stick with them unless you really see a performance hit that you just cannot bear.



  • Thank you for the very comprehensive answer. #2 with some caching looks like the sensible option for now, will optimise to #4 later.
    – djb
    Sep 3, 2013 at 9:46

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.