[Dbix-class] Chaining searches with joins on the same table

A Murka amurkat at gmail.com
Fri Jul 10 04:08:56 GMT 2009


Here's my issue:  I have a table of 'images', which has_many
'ratings', each of which belongs_to a 'user'. I want to get:
1. Aggregate sum of all ratings for each object, as well as
2. the logged-in user's rating for each object.

I have extended ResultSet::Image to have 'with_scores' and
'with_scores_for_user' methods, each of which works individually. They
both join on the ratings table, though, and when I chain them as in
$images->with_scores->with_scores_for_user($user), it only produces
ONE join instead of the two that are necessary.

Here's the code for the methods:

package PicDB::ResultSet::Image;

use strict;
use warnings;
use base 'PicDB::ResultSet';

sub with_scores {
       my ($self) = @_;

       return $self->search({},
               {
                       join => ['ratings'],
                       '+select' => [
                               \'SUM(CASE WHEN ratings.score = 1 THEN
1 ELSE 0 END) AS upvotes',
                               \'SUM(CASE WHEN ratings.score = -1 THEN
1 ELSE 0 END) AS downvotes',
                               \'SUM(ratings.score) AS total_score',
                               \'COUNT(*) AS num_votes',
                       ],
                       '+as' => [ 'upvotes', 'downvotes',
'total_score', 'num_votes' ],
                       group_by => ['me.id'],
               }
       );
}
sub with_scores_for_user {
       my ($self, $user) = @_;
       return $self;

       return $self->search({'ratings.user_id' => $user->id},
               {
                       join => ['ratings'],
                       '+select' => ['ratings.score AS user_score'],
                       '+as' => ['user_score'],
               }
       );
}

1;


Here is the output using DBIC_TRACE=1 for
$images->with_scores->with_scores_for_user($user).
The ->with_scores_for_user($user) call adds the <bracketed> sections:

SELECT me.id, me.srcurl, me.caption, me.user_id, me.ip,
me.when_uploaded, me.views,
SUM(CASE WHEN ratings.score = 1 THEN 1 ELSE 0 END) AS upvotes,
SUM(CASE WHEN ratings.score = -1 THEN 1 ELSE 0 END) AS downvotes,
SUM(ratings.score) AS total_score,
COUNT(*) AS num_votes,
< ratings.score AS user_score >
FROM image me LEFT JOIN rating ratings ON ratings.image_id = me.id
< WHERE ( ratings.user_id = ? ) >
GROUP BY me.id ORDER BY when_uploaded DESC LIMIT 12: '7'

As you can see, this query should have joined on 'ratings' -twice-,
and the condition should have been WHERE (ratings_2.user_id = ?). I
feel that DBIC should somehow recognize that chaining the two queries
should create two joins.

Do I just have to create a method that does both joins at once? Is a
better way to implement these aggergates?

Thanks,
-amurka



More information about the DBIx-Class mailing list