[Dbix-class] Get a list of parent objects based on child object properties (possible n00bish question)

David Ihnen davidi at norchemlab.com
Wed Feb 4 01:29:23 GMT 2009


Mon-Chaio Lo wrote:
> Hello,
>
>   
Mon-Chiao!  Its a pleasure to see you online.  Much has happened since I 
interviewed with you at Cardomain and was almost (but not quite) hired.  
(just as well with their downsize)  I see you too have skedaddled from 
Seattle - enjoying the winter sunshine down california?  Flagstaff is 
treating me tolerably.  Anyway.   Your queries.

> I couldn't find the answer to this question in the documentation, so I
> turn to the community.  I have the following example tables:
>
> projects
> --------
> pid  name    status
>  1   proj1   completed
>  2   proj2   pending
>  3   proj3   completed
>
> project_attributes
> -------------------
> pid   aid   vid   active
>  1     1     1      1
>  1     1     2      1
>  2     1     3      1
>  3     1     4      1
>  3     2     6      0
>
> attributes
> ----------
> aid   name     created_date
>  1    attr1    Jan 1
>  2    attr2    Jan 2
>
> attribute_values
> ----------------
> aid  vid   name
>  1    1    value_for_attr1
>  1    2    another_value_for_attr_1
>  1    3    ...
>  2    6    ...
>
> I'm trying to find all completed projects and their active attributes.
> So I have the following code:
>
> my $proj_rs = $schema->resultset('Project')->search( { status =>
> 'completed' } ); my $rs = $proj_rs->search_related(
> 'project_attributes', { active => 1 }, { prefetch => [qw(attributes
> values)] } ); while ( my $proj_attribute = $rs->next() ) {
>     ...
> }
>
> Now I have a list of project_attribute objects.  What I've accomplished
> is to get a list of project_attribute objects whose parent project is
> completed.  What I'd like to do instead is get a list project objects,
> but only those projects that are completed have at least one active
> child attribute.  I know I can do that with code that looks like:
>
> my $proj_rs = $schema->resultset('Project')->search( {
> project_attributes.active => 1 }, { join => ['project_attributes'] } );
>
> Here's the thing.  I've already specified the table relationships in the
> schema objects. 
But not which ones you're using at the moment for this query.  And the 
fact that your relationships don't define your restrictions is a problem 
in this regard.
>  Now I'm specifying them again in the search.  So now if
> I rename the project_attributes table, I need to change things in
> multiple places, namely the Schema representing that table, as well as
> all the explicit joins I have in my search() code.
>
> Is there a way around this?  Simply stated, I'd like to get back a list
> of parent objects whose child objects exhibit a specific property,
> without resorting to explicit join statement arguments to the search
> method.  Oh, and the first code chunk only calls one SQL statement, so
> it'd be nice to minimize the number of calls to the DB as well.
>
>   
Hm.  What you would do in manual sql amount to defining a new relation, 
one called 'active_attributes' maybe.  That has a compound join 
condition (aid = aid and active = 1)

FROM projects p
JOIN project_attributes pa ON (pa.pid = p.pid and pa.active = 1)

Which I would *expect* to define as

__PACKAGE__->has_many('active_attributes', 
'DB::Schema::project_attribute', { 'foreign.pid' => 'self.pid', 
self.active => 1 });

Allowing

my $valueset = 
$schema->resultset('Projects')->active_attributes->attribute_values->search(undef, 
{ prefetch => { project_attribute => project });

Each of which entries you could call

$attribute_value->attribute->project

to get your project as well.

But that just isn't how that works.  :( 

There seems to be a bug in DBIx::Class where you can't do that no matter 
how you tangle - this augmentation would help me in my queries too, 
greatly!  I puzzled at it for awhile, but there seemed to be something 
odd about how the join rels are defined that I couldn't quite grok.  
I'll revisit in awhile and see if I make any progress.  Maybe you'll be 
able to see it...

But I would, until I can define custom compound relations, just collect 
the whole data set in one query.  (forgive my barewords, they shoudl be 
quoted)

my @rows = $schema->resultset('Project')->search
  ( { project.status => 'completed'
    , 'project_attributes.active' => 1
    }
  ,  { prefetch => { project_atributes => { attributes, attribute_values } }
     , join => { project_atributes => [ attributes , attribute_values ] }
  );

Which is terribly denormalized because you duplicate the join pattern in 
the prefetch clause (I think this is another bug.  patch?), but does let 
you do fun things like

print "found project " . $_->name . " attribute " . 
$_->project_attributes->attribute_values->name . "\n" foreach (@rows);


And though you had to mention what direction of joins to take (there can 
be many in a complex schema!) to use for the query, you did NOT have to 
define which columns are utilized for each of the joins - merely define 
that they're joined and what you wanted in your resultset.

I hope we can figure out how to do complex join relations, that would be 
awesome.

Helpfully,

David Ihnen
Programmer
Norchem Laboratories
http://www.norchemlab.com/







More information about the DBIx-Class mailing list