[Dbix-class] $rs->count fails with multiple group_by
Noel Burton-Krahn
noel at burton-krahn.com
Tue Dec 16 20:02:25 GMT 2008
If I search with multiple columns in a group_by clause, MySQL dies:
$rs = $schema->resultset('Measurement')->search({}, {
select => [ {sum => 'value'}, qw(date type) ],
as => [ 'value', qw(date type) ],
group_by => [ qw(date type) ],
order_by => [ qw(date type) ],
});
$rs->count();
DBIx::Class::ResultSet::count(): DBI Exception: DBD::mysql::st execute
failed: Operand should contain 1 column(s) [for Statement "SELECT
COUNT( DISTINCT( date, type ) ) FROM measurement me"] at
./t/dbix_select_count_group_by.t line 115
I'll include a full test program below.
DBIx emulates count(*) by constructing a clause like
"count(distinct(col, ...))" which MySQL (and SQLite I believe) don't
like. You could do a count(*) from those databases by doing the
original SQL as a subselect, like so:
select count(*) from (SELECT SUM( value ), date, type FROM measurement
me GROUP BY date, type ORDER BY date, type) x;
That is, generate the SQL you would use to select the rows, and wrap
it in a "select count(*) from ( ... ) x".
I've been digging through the DBIx source to find out where the
original SQL gets generated. Any pointers?
~Noel
---------------------------------------------------------------------------------------------------------
#! /usr/bin/perl -w
=head1 NAME
dbix_select_count_group_by.t - $rs->count fails with multiple group_by
=head1 AUTHOR
Noel Burton-Krahn <noel at burton-krahn.com>
=cut
use strict;
use warnings;
#----------------------------------------------------------------------
package My::DBIx::Class;
use base qw/DBIx::Class/;
__PACKAGE__->load_components(qw/PK::Auto Core/);
use overload '""' => 'dump';
sub define {
my($val, $def) = @_;
return defined($val) ? $val : $def;
}
sub dump {
my($self) = shift;
return join(" ", map { "$_=" . define($self->get_column($_), "NULL")
} $self->columns);
}
#----------------------------------------------------------------------
package MySchema::Measurement;
use base qw/My::DBIx::Class/;
__PACKAGE__->table('measurement');
__PACKAGE__->add_columns(qw(id type date value));
__PACKAGE__->set_primary_key('id');
#----------------------------------------------------------------------
package MySchema;
use base qw/DBIx::Class::Schema/;
__PACKAGE__->load_classes({
'MySchema' => [ qw(Measurement) ],
});
#----------------------------------------------------------------------
package MyTest;
use Test::More tests => 5;
use DateTime;
use Data::Dumper;
# create a mysql database to test with
system(<<'EOS');
mysqladmin -f drop mytest >/dev/null 2>&1
mysqladmin create mytest
mysql mytest <<ESQL
create table measurement (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,type VARCHAR(64) NOT NULL
,date DATETIME NOT NULL
,value DECIMAL(16,4) NOT NULL
) ENGINE=INNODB;
ESQL
#mysql mytest <<ESQL
#show tables;
#show create table person;
#show create table address;
#ESQL
EOS
;
is($?, 0, "create database");
# connect
my $schema = MySchema->connect("dbi:mysql:mytest", 'xxx', 'yyy')
or die("connect: $!");
ok($schema, "connect to db");
my $rs;
my $measurement;
my $count = 100;
my @types = qw(red green blue);
foreach (1..$count) {
my $date = DateTime->now();
$date->add(days => rand(60)-30);
my $value = rand(100);
my $type = $types[rand(@types)];
$measurement = $schema->resultset('Measurement')->create({
type => $type,
date => $date,
value => $value});
}
ok($measurement, "created count=$count measurements");
$rs = $schema->resultset('Measurement')->search({}, {
select => [ {sum => 'value'}, qw(date type) ],
as => [ 'value', qw(date type) ],
group_by => [ qw(date type) ],
order_by => [ qw(date type) ],
});
$schema->storage->debug(1);
while( my $row = $rs->next() ) {
$measurement = $row;
}
ok($rs, "found Measurement: $measurement");
$count = $rs->count();
ok($count, "counted measurements: count=$count");
More information about the DBIx-Class
mailing list