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

Peter Rabbitson rabbit+dbic at rabbit.us
Wed Aug 5 07:43:28 GMT 2009


A Murka wrote:
> 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.
> 

So any chance I can get a test for this?



More information about the DBIx-Class mailing list