$schema->resultset("Thing")->search({ ... })->prefetch('last_event')->all
####
SELECT me.*, thing_max_event.* FROM thing me
LEFT JOIN thing_event thing_max_event ON thing_max_event.id = (
SELECT TOP 1 id FROM thing_event
WHERE thing_event.thing_id = me.id
ORDER BY timestamp DESC
)
WHERE ...
####
package App::Schema::ResultSet::ThingEvent;
sub last_rs {
my $self = shift;
return $self->search_rs(undef,{
order_by => { -desc => $self->me . 'timestamp' },
rows => 1,
# per conversation with ribasushi, this should be safe for a single row query
unsafe_subselect_ok => 1
});
}
####
package App::Schema::Result::Thing;
__PACKAGE__->might_have( last_event => 'MyApp::Schema::Result::ThingEvent', sub {
my ($rsrc, $us, $them)= @{$_[0]}{'self_resultsource','self_alias','foreign_alias'};
my $guard= $rsrc->schema->set_limit_dialect_scope_guard('Top'); # use "SELECT TOP x FROM ..." syntax for subquery
return {
"$them.id" => { '=' =>
$rsrc->schema->resultset('ThingEvent')->alias('thing_max_event')->search({
'thing_max_event.thing_id' => { -ident => "$us.id" },
})->last_rs->columns(['thing_max_event.id'])->as_query
}
};
});
####
package App::Schema;
=head2 set_limit_dialect_scope_guard
{
my $dialect_scope= $schema->set_limit_dialect_scope_guard('Top');
# make DBIC searches with "SELECT TOP $n" syntax
...
}
# back to normal
By default, DBIC generates ugly nested queries with a row_id range selected in
the outer where clause. This is actually the canonical way to do it on
SQL Server, but can create all sorts of trouble with namespace between the
different layers of the query. Sometimes for sub-queries it is nice to have
DBIC generate "SELECT TOP 1 foo FROM bar" instead of the usual mess. DBIC has
a C<< sql_maker->limit_dialect >> option for this, but there is no way to
constrain it to a single statement.
This "scope guard" object allows you to control the exact scope in which you
want DBIC to use the alternate dialect without worrying about exceptions.
=cut
# This scope guard object sets the "limit_dialect" of the storage engine back to whatever
# it was originally when the object goes out of scope.
{package MSSQL_LimitDialectFlipper;
sub new {
my ($class, $storage, $value)= @_;
my $self= { storage => $storage, prev_value => $storage->sql_maker->limit_dialect };
Scalar::Util::weaken($self->{storage});
$storage->sql_maker->limit_dialect($value);
bless $self, $class
}
sub DESTROY {
$_[0]{storage}->sql_maker->limit_dialect($_[0]{prev_value}) if $_[0]{storage};
}
}
sub set_limit_dialect_scope_guard {
my ($self, $new_dialect_value)= @_;
MSSQL_LimitDialectFlipper->new($self->storage, $new_dialect_value);
}
####
$schema->resultset("Thing")->join('last_event_of_type1','last_event_of_type2')
->where('last_event_of_type1.timestamp' => { '>=', $cutoff1 },
'last_event_of_type2.timestamp' => { '<', $cutoff2 });