Hi @dotty:
The answer from @sorich87 is correct, but I thought I'd elaborate a bit. I've coded a class for you called (verbosely) LatestPostPerPostTypeQuery and you can use it in the loop in place of WP_Query, like so:
<ul>
<?php $args = array('post_type'=>'events,winners,offers'); ?>
<?php $query = new LatestPostPerPostTypeQuery($args); ?>
<?php while($query->have_posts()): $query->the_post(); ?>
<li><?php the_title(); ?></li>
<?php endwhile; ?>
</ul>
Below is the code for the LatestPostPerPostTypeQuery class which you can copy into your theme's functions.php file (or use the code in a plugin if you like.) The nice thing about this alternative to WP_Query is it does a single query to the database instead of one for each of the three post types like you'd be forced to use when using WP_Query() directly.
<?php
class LatestPostPerPostTypeQuery extends WP_Query {
var $flag;
function __construct($args=array()) {
$this->LatestPostPerPostTypeQuery($args);
}
function LatestPostPerPostTypeQuery($args=array()) {
if (isset($args['post_type']) && !is_array($args['post_type']))
$args['post_type'] = explode(',',$args['post_type']);
$this->flag = true;
parent::query($args);
}
static function on_load() {
add_filter('posts_join',array(__CLASS__,'posts_join'),10,2);
}
static function posts_join($join,$query) {
if (isset($query->flag)) {
global $wpdb;
$join .=<<<SQL
INNER JOIN (
SELECT post_type,MAX(post_date) AS post_date
FROM {$wpdb->posts}
GROUP BY post_type) max_date ON
max_date.post_type={$wpdb->posts}.post_type AND
max_date.post_date={$wpdb->posts}.post_date
SQL;
}
return $join;
}
}
LatestPostPerPostTypeQuery::on_load();
I've also posted a standalone file on Gist allowing you grab the source code and drop the example into a file in the root of your website to call it directly from the browser to watch it in action: