[Dbix-class] joined table aliases

Byron Young Byron.Young at riverbed.com
Fri Jan 30 21:50:27 GMT 2009


Hi,

I've run into a problem with constructing queries with joins in DBIC.  I have a page in my Catalyst app where you can view the contents of a DB table, and I'm building a 'Filter Results' box in the page so you can search the table.  My code takes the values of the Filter form and generates a query depending on what's in there.

But I've run into an problem in the case that two of the filled-out Filter fields cause the same join to happen more than once.  When that happens, the joined table is given an alias of table_2, table_3, etc, so then my WHERE condition needs to know what the alias is in order to build the right search.

As far as I can tell, there's no way to get that joined table alias that's going to be used before I call search(), or to have search() automatically fix my WHERE condition.  Am I missing something?  I can make it work in my app by tracking the number of times a relationship has been joined and then generating the alias myself, and fixing my WHERE conditions.  Does this seem like an ok solution?  I'm a little wary of it because (1) I'm re-implementing some things in my app that are already implemented in DBIC - namely, the join => [] attribute parsing and the join table aliasing, and (2) (this may be unlikely because the aliasing scheme is documented) the table alias numbering behavior might change in the future, and I would have to update my app to keep up with DBIC changes. 


In case I'm not explaining myself well, here's some of my code and the resulting SQL:


The %joins entries describe how to build the query when the 'Filter Results' form is submitted.  It just grabs the entries for the form fields that were actually entered.  The two I show below both happen to end up with a join and a search on the same table, 'hostname'.

    my %joins = (tested_host => { where     => 'hostname.name',
                                  join      => { 'map_results_tested_hosts'
                                                 => { 'tested_host' 
                                                      => 'hostname' } },
                                  '+select' => [ 'hostname.name' ],
                                },
                 tested_product => { where     => 'hostname.name',
                                     join      => { 'map_results_tested_products'
                                                    => { 'tested_product' 
                                                         => 'hostname' } },
                                     '+select' => [ 'hostname.name' ],
                                    },);


    while (my ($filter, $join_attrs) = each(%joins)) {
        if (exists $filters->{$filter}) {
            my $filter_attr = {};
            %where = ();

            # set condition for where clause
            my $cond_attr = $join_attrs->{where};

            # $cond_attr can be an array ref to signify an operator other 
            # than '=' should be used when searching for the condition. the 
            # first item is the condition name and the second is the operator
            if (ref $cond_attr && ref $cond_attr eq 'ARRAY') {
                $where{$cond_attr->[0]} = { $cond_attr->[1] => $filters->{$filter} }
            } else {
                $where{$cond_attr} = $filters->{$filter};
            }

            # set join attributes
            foreach my $key (qw/join +select +as/) {
                my $val = $join_attrs->{$key};
                $filter_attr->{$key} = $val;
            }

            $rs = $rs->search(\%where, $filter_attr);
        }
    }


If just one of the tested_host or tested_product filters are used, the query is fine, but it both are used I get a collision in the WHERE condition.  The second join gives 'hostname' an alias 'hostname_2', but my WHERE condition for both queries is still 'hostname.name'.  Here's the query:


SELECT me.id, me.result_type, me.start_time, me.end_time, me.results_dir, me.test_case, me.job, me.modified_ts, me.result_category, me.bug_ids, me.comments, me.is_debug, hostname.name 
  FROM results me 
    LEFT JOIN map_results_tested_products map_results_tested_products ON ( map_results_tested_products.result = me.id )  
    JOIN tested_products tested_product ON ( tested_product.id = map_results_tested_products.tested_product )  
    JOIN hostnames hostname ON ( hostname.id = tested_product.hostname ) 
    LEFT JOIN map_results_tested_hosts map_results_tested_hosts ON ( map_results_tested_hosts.result = me.id )  
    JOIN tested_hosts tested_host ON ( tested_host.id = map_results_tested_hosts.tested_host )  
    JOIN hostnames hostname_2 ON ( hostname_2.id = tested_host.hostname ) 
  WHERE ( ( ( hostname.name = ? ) AND ( hostname.name = ? ) ) ) 
  ORDER BY id ASC LIMIT 25: 'gen-cs167', 'gen-cs167'


As you can see, hostname_2.name is not included as a column and it's not searched on.  Is there a good way to get around this?

Thanks,
Byron



More information about the DBIx-Class mailing list