[Dbix-class] Joins using sub-selects
Andrew Beverley
andy at andybev.com
Tue Oct 9 23:38:06 GMT 2018
On Mon, 8 Oct 2018 15:26:41 +0000 David Cantrell wrote:
> I'm having trouble figuring out how to express one of my joins in
> DBIx::Class.
>
> The two tables involved and some relevant sample data are:
>
> > select * from serviceplan_price;
> +----+----------------+-------+-------+---------------------+
> | id | serviceplan_id | type | value | effective_date_time |
> +----+----------------+-------+-------+---------------------+
> | 78 | 63 | new | 0.14 | 1973-01-01 00:00:00 |
> | 79 | 64 | new | 0.73 | 1982-01-01 00:00:00 |
> | 80 | 64 | new | 3.18 | 2012-01-01 00:00:00 |
> | 81 | 63 | new | 2.99 | 2019-01-01 00:00:00 |
> | 82 | 63 | renew | ...
>
> Note that the effective date can be in the future. This is how we
> represent historical prices, and planned price rises. And that there are
> two types, 'new' (the price you pay when you first buy something) and
> 'renew' (the price for subsequent renewals). The 'new' price might
> include the cost of setting up hardware, for example, which isn't needed
> on renewal.
>
> > select * from serviceplan;
> +----+--------------------------------+
> | id | name | irrelevant details ... |
> +----+--------------------------------+
> | 63 | foo | blahblah |
> | 64 | bar | blahblah |
> +----+--------------------------------+
>
> And I want to define a relationship so that, along with a serviceplan, I
> can fetch its *current* new price or renewal price. For an added wrinkle
> we want to be able to mock the current date/time in our tests, so we
> can't just use NOW(), but I don't think that's the problem. In plain old
> SQL it would look like this for fetching them with their current new price:
>
> SELECT me.id, me.name, ...
> current_new_price.id, ...
> FROM serviceplan me
> JOIN serviceplan_price current_new_price ON (
> current_new_price.serviceplan_id = me.id AND
> current_new_price.type = 'new' AND
> current_new_price.effective_date_time = (
> SELECT MAX(effective_date_time)
> FROM serviceplan_price
> WHERE effective_date_time <= '$mocked'
> AND type ='new'
> AND serviceplan_id = me.id
> )
> )
>
> In terms of a DBIx::Class relationship on my serviceplan result class
> I've got this (repeated for the current_renew_price):
>
> __PACKAGE__->belongs_to(
> current_new_price => 'MyApp::Result::ServiceplanPrice',
> sub ($args) {
> my $foreign = $args->{foreign_alias};
> my $me = $args->{self_alias};
> my $mocked_now = MyApp::Mocks->now(),
> return {
> "$foreign.serviceplan_id" => { -ident => "$me.id" },
> "$foreign.type" => 'new',
> "$foreign.effective_date_time" => { -ident => qq{
> ( SELECT MAX(effective_date_time)
> FROM serviceplan_price
> WHERE effective_date_time <= '$mocked_now'
> AND type = 'new'
> AND serviceplan_id = $me.id
> )
> } }
> }
> }
> );
>
> I *think* that I have no choice but to write the relationship condition
> as an anonymous sub,
You can actually do this with pure DBIC, using a correlated query with
Frew's module:
https://blog.afoolishmanifesto.com/posts/introducing-dbix-class-helper-resultset-correlaterelationship/
The blog post is pretty good and should explain it, but shout if you
can't work it out.
Andy
More information about the DBIx-Class
mailing list