[Dbix-class] Use of sub-select in joins
"Dieter Späth"
SHAD0WRUNNER at gmx.de
Fri May 4 14:11:12 GMT 2007
-------- Original-Nachricht --------
Datum: Fri, 04 May 2007 22:02:21 +0930
Von: Jon Schutz <jon+dbix at youramigo.com>
An: dbix-class at lists.rawmode.org
Betreff: [Dbix-class] Use of sub-select in joins
> I have a problem, and a solution. I would appreciate comments/advice on
> the solution, and then I'd be happy to write up the outcomes for
> addition to DBIx::Class::Manual::Joining or thereabouts.
>
> I have a data table which records start/stop events corresponding to
> certain items. The query of interest is to find out, at any particular
> time, which items were in a START state.
>
> The formulation of the query is as follows:
>
> a. find the latest event prior to the given date (using max(date) and
> group by event_type) for each item in a sub-select
>
> b. find all rows that match on item and date = max(date)
>
> Example:
>
> mysql> select * from events;
> +----+---------+------------+------------+
> | id | item_id | date | event_type |
> +----+---------+------------+------------+
> | 37 | 25 | 1178175844 | START |
> | 40 | 25 | 1178175845 | START |
> | 43 | 25 | 1178175850 | STOP |
> | 38 | 26 | 1178175844 | START |
> | 42 | 26 | 1178175846 | START |
> | 44 | 26 | 1178175851 | STOP |
> | 39 | 27 | 1178175845 | START |
> | 41 | 28 | 1178175846 | START |
> +----+---------+------------+------------+
>
> SQL that works:
>
> SELECT * from events t1,
> (SELECT item_id, max(date) AS mdate
> FROM events
> WHERE date <= 1178175846
> GROUP BY item_id) t2
> WHERE t1.item_id = t2.item_id
> AND t1.date = mdate
> AND t1.event_type = 'START'
>
> or equivalently, using JOIN,
>
> SELECT * from events t1
> JOIN (SELECT item_id, max(date) AS mdate
> FROM events
> WHERE date <= 1178175846
> GROUP BY item_id) t2
> ON t1.item_id = t2.item_id
> AND t1.date = mdate
> WHERE t1.event_type = 'START'
Wouldn't it be easier with:
SELECT *
FROM EVENTS t1
WHERE t1.event_type = 'START'
AND t1.date <= xxx
AND NOT EXISTS (SELECT 0
FROM EVENTS t2
WHERE t1.date < t2.date
AND t2.date <= xxx
AND t2.event_type = 'STOP')
>
> to produce:
>
> +----+---------+------------+------------+---------+------------+
> | id | item_id | date | event_type | item_id | mdate |
> +----+---------+------------+------------+---------+------------+
> | 40 | 25 | 1178175845 | START | 25 | 1178175845 |
> | 42 | 26 | 1178175846 | START | 26 | 1178175846 |
> | 39 | 27 | 1178175845 | START | 27 | 1178175845 |
> | 41 | 28 | 1178175846 | START | 28 | 1178175846 |
> +----+---------+------------+------------+---------+------------+
>
> To do the sub-select in DBIx::Class, I saw the recent advice on the list
> of Marc Mims who posted this:
>
> > You can create a result source for your query:
> >
> > package MySchema::Whatever;
> > use base qw/DBIx::Class/;
> >
> > __PACKAGE__->table('DUMMY');
> > __PACKAGE__->add_columns(qw/a b c v/);
> > __PACKAGE__->result_source_instance->name(\<<'');
> > (select a, b, c, 'var1' as v from table1
> > where d='value'
> > union select a, b, c, 'var2' as v from table2
> > where d='value')
>
> First problem - my date in the sub-select is a variable, so putting this
> as a static class declaration won't fly.
>
> Second problem - I'm using deploy() to bootstrap the system, which falls
> over when trying to create the table DUMMY. (By the way, using
> deploy({source_names => \@tables}) doesn't work - I know, I should raise
> a bug report...).
>
> So, I took the following approach:
>
> 1. Separately (i.e. not in the normal schema path, to avoid it being
> picked up by load_classes() and subsequently deploy()), I have the
> DateSelector class:
>
> package My::Schema::DateSelector;
> use base qw/DBIx::Class/;
>
> __PACKAGE__->load_components(qw/Core/);
>
> __PACKAGE__->table('DUMMY');
> __PACKAGE__->add_columns(qw/item_id mdate/);
>
> 2. After deploy(), I load the DateSelector class into the schema:
>
> # DateSelector is a dummy table so must bypass the deploy operation
> $self->{schema}->register_class('DateSelector',
> 'My::Schema::DateSelector');
>
> 3. When I need to do the query, I define the sub-select and the join
> relation:
>
> # Create table for subquery
>
> Local::Backlinks::Schema::DateSelector->result_source_instance->name(qq{
> (SELECT item_id, max(date) as mdate
> FROM events
> WHERE \`date\` <= $date
> GROUP BY item_id)
> });
>
> my $events = $schema->resultset('Events');
> $events->result_source->add_relationship('selector' =>
> 'My::Schema::DateSelector', {
> 'foreign.item_id' => 'self.item_id',
> 'foreign.mdate' => 'self.date',
> });
>
> 4. And finally, execute the query:
>
> my @current_items = $events->search({ event_type => 'START' },
> { join => 'selector' },
>
> )->related_resultset('item_id')->all;
>
>
> And it works! The resulting SQL is basically the JOIN version shown
> above.
>
> So, it's a bit of mucking around, but worth it in the end, because in
> reality I have another 5 joins happening after this one to get to the
> final object I want, and DBIx::Class takes care of those transparently
> through a series of related_resultset and search_related calls. I've
> left that out of the above to keep the explanation simple.
>
> Is this the best way or can it be done more simply?
>
> Is it safe to be playing with result_source_instance->name and
> add_relationship dynamically?
>
> TO anyone who has read down this far - Thanks!
>
> --
>
> Jon
>
>
> _______________________________________________
> List: http://lists.rawmode.org/cgi-bin/mailman/listinfo/dbix-class
> Wiki: http://dbix-class.shadowcatsystems.co.uk/
> IRC: irc.perl.org#dbix-class
> SVN: http://dev.catalyst.perl.org/repos/bast/trunk/DBIx-Class/
> Searchable Archive:
> http://www.mail-archive.com/dbix-class@lists.rawmode.org/
--
FastJack
"Feel free" - 10 GB Mailbox, 100 FreeSMS/Monat ...
Jetzt GMX TopMail testen: http://www.gmx.net/de/go/topmail
More information about the Dbix-class
mailing list