[Dbix-class] prefetch across multiple tables
John Stoffel
john at stoffel.org
Thu Dec 18 15:34:15 GMT 2014
>>>>> "Lasse" == Lasse Makholm <lasse at unity3d.com> writes:
Lasse> On Wed, Dec 17, 2014 at 7:39 PM, John Stoffel <john at stoffel.org> wrote:
Lasse> Hi all,
Lasse> With a bit of nudging, I had a head-smack moment and upgraded to the
Lasse> latest version of DBIx::Class on CPAN, and it now looks like things
Lasse> are working better for me, but I'm still getting some interesting
Lasse> behavior.
Lasse> Before I got errors, now I can do:
Lasse> > ../bin/dbic-test.pl Emlen
Lasse> DBIx::Class::ResultSet::_construct_results(): Unable to properly collapse has_many results
Lasse> in iterator mode due to order criteria - performed an eager cursor slurp underneath. Consider
Lasse> using ->all() instead at ../bin/dbic-test.pl line 20
Lasse> Do you have proper non-nullable primary keys set up in all the
Lasse> relevant result classes?
Maybe... here's my three tables using mysql describe:
mysql> describe names;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| name_id | int(7) | NO | PRI | NULL | auto_increment |
| full_name | varchar(50) | NO | | NULL | |
| last_name | text | YES | | NULL | |
| first_name | text | YES | | NULL | |
| comments | varchar(100) | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
mysql> describe account;
+----------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+---------------+------+-----+---------+----------------+
| account_id | int(7) | NO | PRI | NULL | auto_increment |
| account_number | varchar(10) | NO | | NULL | |
| boxfolder_id | int(7) | NO | | NULL | |
| name_id | int(7) | YES | | NULL | |
| url | varchar(1028) | YES | | NULL | |
| comments | varchar(100) | YES | | NULL | |
+----------------+---------------+------+-----+---------+----------------+
mysql> describe boxfolder;
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| boxfolder_id | int(7) | NO | PRI | NULL | auto_increment |
| volume | varchar(20) | NO | | NULL | |
| box | varchar(10) | NO | | NULL | |
| folder | varchar(20) | YES | | NULL | |
| range | text | YES | | NULL | |
| comments | varchar(100) | YES | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
I suspect I *might* have a problem, since the Default column shows
NULL for each PRI key defined there. But a bit of googling seems to
show that I'll be ok here.
Lasse> Full Name: Emlen & Howell (8213)
Lasse> account_id=5920 boxfolder_id=234 URL: b16f10
Lasse> account_id=5921 boxfolder_id=249 URL: b17f10
Lasse> account_id=5922 boxfolder_id=281 URL: b19f12
Lasse> Full Name: Emlen, Caleb (8214)
Lasse> account_id=5923 boxfolder_id=14 URL: b01f14
Lasse> account_id=5924 boxfolder_id=50 URL: b04f05
Lasse> So I think I need to change my loop from:
Lasse> while (my $r = $rs->next) {
Lasse> print "Full Name: ", $r->full_name, " (", $r->name_id, ")\n";
Lasse> foreach my $a ($r->account()) {
Lasse> print " account_id=", $a->account_id();
Lasse> print " boxfolder_id=",$a->boxfolder_id()," ";
Lasse> my $t = $a->url();
Lasse> $t =~ m/value2=(\w+)\&/;
Lasse> print " URL: $1";
Lasse> $vol = $a->boxfolder->volume;
Lasse> $folder = $a->boxfolder->volume;
Lasse> $range = $a->boxfolder->range;
Lasse> print " V=$vol " if defined $vol;
Lasse> print " F=$folder " if defined $folder;
Lasse> print " R=$range " if defined $range;
Lasse> print "\n";
Lasse> }
Lasse> }
Lasse> To something more like this:
Lasse> my @r = $rs->all
Lasse> foreach my $r (@r) {
Lasse> Be aware that this will cost you in terms of memory because
Lasse> you'll inflate all row objects once. Especially if you
Lasse> prefetch a lot... Depending on your specific result sets, this
Lasse> may not matter to you... But it's definitely something to be
Lasse> aware of...
Yeah, I think I ran into this. For the web app side, I really want to
page the results anyway, which is the next step I'm working on, once I
confirm that I'm getting back good search results.
Lasse> We've seen lots of worker processes gobble up 50 - 100 MB of
Lasse> extra memory because of this, so we've been converting most
Lasse> uses of foreach my $row ($rs->all) { ... } to while (my $row =
Lasse> $rs-> next) { ... }. YMMV.
Well.. here's my code that I'm currently using in my Dancer app, which
is based on the Template Toolkit (another thing I'm still learning and
coming upto speed on!).
Now I *know* my query is stupid here. That's because there's no
actual data in the names table beyond the full_name column and the
primary key. LOL It's all a conversion from a spreadsheet into a DB.
I think I can get way with:
full_name REGEXP '[[:<:]]David[[:>:]]'
since it does find more matches, which is a good thing.
Anyway, my Dancer app looks a little like this:
sub _perform_search {
my ($query) = @_;
my $schema = schema 'Carey';
# search in Carey
my @r = $schema->resultset('Name')->search({ -or => [
full_name => { like => "$query %" },
full_name => { like => "$query,%" },
full_name => { like => "% $query" },
]
},
{
group_by => { -asc => "full_name" },
prefetch => { 'accounts' => 'boxfolder' },
rows => $limit,
});
return @r;
}
And the template:
<% FOREACH result IN results %>
<li><% result.full_name.replace("((?i)$query)",'<b>$1</b>') %>
<ul>
<% FOREACH a IN result.accounts %>
<% b = a.boxfolder %>
<li><a href="<% a.url %>" target="_blank"> Volume <% a.boxfolder.volume %>, Box <% b.box %>\
, Folder <%
b.folder %>: <% b.range %></a><br> Account_id = <% a.account_id %>,
Boxfolder_id = <% b.boxfolder_id %>, URL = <% a.url %>
<% END %>
</ul>
<% END %>
Thanks,
John
More information about the DBIx-Class
mailing list