| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package DBIx::PivotQuery; |
|
2
|
4
|
|
|
4
|
|
63219
|
use strict; |
|
|
4
|
|
|
|
|
21
|
|
|
|
4
|
|
|
|
|
108
|
|
|
3
|
4
|
|
|
4
|
|
1497
|
use Filter::signatures; |
|
|
4
|
|
|
|
|
75203
|
|
|
|
4
|
|
|
|
|
125
|
|
|
4
|
4
|
|
|
4
|
|
23
|
use feature 'signatures'; |
|
|
4
|
|
|
|
|
10
|
|
|
|
4
|
|
|
|
|
751
|
|
|
5
|
4
|
|
|
4
|
|
710
|
no warnings 'experimental::signatures'; |
|
|
4
|
|
|
|
|
4
|
|
|
|
4
|
|
|
|
|
103
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
4
|
|
|
4
|
|
11
|
use Exporter 'import'; |
|
|
4
|
|
|
|
|
6
|
|
|
|
4
|
|
|
|
|
88
|
|
|
8
|
4
|
|
|
4
|
|
689
|
use Carp 'croak'; |
|
|
4
|
|
|
|
|
2
|
|
|
|
4
|
|
|
|
|
143
|
|
|
9
|
4
|
|
|
4
|
|
12
|
use vars '$VERSION'; |
|
|
4
|
|
|
|
|
3
|
|
|
|
4
|
|
|
|
|
146
|
|
|
10
|
|
|
|
|
|
|
$VERSION = '0.01'; |
|
11
|
|
|
|
|
|
|
|
|
12
|
4
|
|
|
4
|
|
12
|
use vars qw(@EXPORT_OK); |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
4791
|
|
|
13
|
|
|
|
|
|
|
@EXPORT_OK = qw(pivot_by pivot_list pivot_sql); |
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 NAME |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
DBIx::PivotQuery - create pivot tables from queries |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
use DBIx::PivotQuery 'pivot_by'; |
|
22
|
|
|
|
|
|
|
my $rows = pivot_by( |
|
23
|
|
|
|
|
|
|
dbh => $dbh, |
|
24
|
|
|
|
|
|
|
columns => ['month'], |
|
25
|
|
|
|
|
|
|
rows => ['region'], |
|
26
|
|
|
|
|
|
|
aggregate => ['sum(amount) as amount'], |
|
27
|
|
|
|
|
|
|
sql => <<'SQL'); |
|
28
|
|
|
|
|
|
|
select |
|
29
|
|
|
|
|
|
|
month(date) as report_month |
|
30
|
|
|
|
|
|
|
, region |
|
31
|
|
|
|
|
|
|
, amount |
|
32
|
|
|
|
|
|
|
from mytable |
|
33
|
|
|
|
|
|
|
SQL |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
The above code returns a data structure roughly like |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# [ |
|
38
|
|
|
|
|
|
|
# ['region','1','2',...,'11','12'], |
|
39
|
|
|
|
|
|
|
# ['East', 0, 0 ,..., 10, 20 ], |
|
40
|
|
|
|
|
|
|
# ['North', 0, 1 ,..., 10, 20 ], |
|
41
|
|
|
|
|
|
|
# ['South', 0, 3 ,..., 10, 5 ], |
|
42
|
|
|
|
|
|
|
# ['West', 0, 6 ,..., 8, 20 ], |
|
43
|
|
|
|
|
|
|
# ] |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
=head1 FUNCTIONS |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
# This should maybe return a duck-type statement handle so that people |
|
48
|
|
|
|
|
|
|
# can fetch row-by-row to their hearts content |
|
49
|
|
|
|
|
|
|
# row-by-row still means we need to know all values for the column key :-/ |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=head2 C<< pivot_by >> |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
my $l = pivot_by( |
|
54
|
|
|
|
|
|
|
dbh => $test_dbh, |
|
55
|
|
|
|
|
|
|
rows => ['region'], |
|
56
|
|
|
|
|
|
|
columns => ['date'], |
|
57
|
|
|
|
|
|
|
aggregate => ['sum(amount) as amount'], |
|
58
|
|
|
|
|
|
|
placeholder_values => [], |
|
59
|
|
|
|
|
|
|
subtotals => 1, |
|
60
|
|
|
|
|
|
|
sql => <<'SQL', |
|
61
|
|
|
|
|
|
|
select |
|
62
|
|
|
|
|
|
|
region |
|
63
|
|
|
|
|
|
|
, "date" |
|
64
|
|
|
|
|
|
|
, amount |
|
65
|
|
|
|
|
|
|
, customer |
|
66
|
|
|
|
|
|
|
from mytable |
|
67
|
|
|
|
|
|
|
SQL |
|
68
|
|
|
|
|
|
|
); |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Transforms the SQL given and returns an AoA pivot table according to |
|
71
|
|
|
|
|
|
|
C, C and C. |
|
72
|
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
The last word (\w+) of each element of C will be used as the |
|
74
|
|
|
|
|
|
|
aggregate column name unless C is given. |
|
75
|
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
Supplying C for a column name in C will create an empty cell |
|
77
|
|
|
|
|
|
|
in that place. This is convenient when creating subtotals. |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=head3 Options |
|
80
|
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=over 4 |
|
82
|
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=item B |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
headers => 1, |
|
86
|
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
Whether to include the headers as the first row |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=back |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
Subtotals are calculated by repeatedly running the query. For optimization, you |
|
92
|
|
|
|
|
|
|
could first select the relevant (aggregated) |
|
93
|
|
|
|
|
|
|
rows into a temporary table and then create the subtotals from that temporary |
|
94
|
|
|
|
|
|
|
table if query performance is an issue: |
|
95
|
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
select foo, sum(bar) as bar, baz |
|
97
|
|
|
|
|
|
|
into #tmp_query |
|
98
|
|
|
|
|
|
|
from mytable |
|
99
|
|
|
|
|
|
|
where year = ? |
|
100
|
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
select foo, bar, baz from #tmp_query |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=cut |
|
104
|
|
|
|
|
|
|
|
|
105
|
5
|
50
|
|
5
|
1
|
10165
|
sub pivot_by( %options ) { |
|
|
5
|
|
|
|
|
25
|
|
|
|
5
|
|
|
|
|
5
|
|
|
106
|
|
|
|
|
|
|
croak "Need an SQL string in option 'sql'" |
|
107
|
5
|
50
|
|
|
|
11
|
unless $options{sql}; |
|
108
|
|
|
|
|
|
|
croak "Need a database handle in option 'dbh'" |
|
109
|
5
|
50
|
|
|
|
14
|
unless $options{dbh}; |
|
110
|
5
|
|
50
|
|
|
9
|
$options{ placeholder_values } ||= []; |
|
111
|
5
|
|
50
|
|
|
10
|
$options{ rows } ||= []; |
|
112
|
|
|
|
|
|
|
|
|
113
|
5
|
100
|
66
|
|
|
16
|
if( $options{ subtotals } and ! ref $options{ subtotals }) { |
|
114
|
1
|
|
|
|
|
1
|
$options{ subtotals } = [@{ $options{rows}}]; |
|
|
1
|
|
|
|
|
2
|
|
|
115
|
|
|
|
|
|
|
}; |
|
116
|
|
|
|
|
|
|
|
|
117
|
5
|
|
|
|
|
8
|
my $subtotals = delete $options{ subtotals }; |
|
118
|
|
|
|
|
|
|
|
|
119
|
5
|
|
|
|
|
17
|
my $result = simple_pivot_by( %options ); |
|
120
|
|
|
|
|
|
|
|
|
121
|
5
|
100
|
|
|
|
15
|
if( $subtotals ) { |
|
122
|
1
|
|
|
|
|
4
|
for my $i ( reverse 0..$#$subtotals ) { |
|
123
|
2
|
|
|
|
|
2
|
$subtotals->[$i] = undef; |
|
124
|
2
|
|
|
|
|
7
|
my $s = simple_pivot_by( |
|
125
|
|
|
|
|
|
|
%options, |
|
126
|
|
|
|
|
|
|
rows => $subtotals, |
|
127
|
|
|
|
|
|
|
headers => 0 |
|
128
|
|
|
|
|
|
|
); |
|
129
|
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
# Now splice our subtotals into the list |
|
131
|
|
|
|
|
|
|
# Wherever the subtotals key changes, insert the subtotal |
|
132
|
2
|
50
|
|
|
|
6
|
my $p = $options{ headers } ? 1 : 0; |
|
133
|
2
|
|
|
|
|
3
|
my $last; |
|
134
|
2
|
|
66
|
|
|
8
|
while( @$s and $p < @$result ) { |
|
135
|
14
|
|
|
|
|
11
|
my $curr = join "\0", @{ $result->[$p] }[0..$i-1]; |
|
|
14
|
|
|
|
|
13
|
|
|
136
|
14
|
|
100
|
|
|
25
|
$last ||= $curr; |
|
137
|
14
|
100
|
|
|
|
20
|
if( $last ne $curr ) { |
|
138
|
3
|
|
|
|
|
3
|
splice @$result, $p, 0, shift @$s; |
|
139
|
3
|
|
|
|
|
3
|
$p++; |
|
140
|
3
|
|
|
|
|
4
|
$last = join "\0", @{ $result->[$p] }[0..$i-1]; |
|
|
3
|
|
|
|
|
3
|
|
|
141
|
|
|
|
|
|
|
}; |
|
142
|
14
|
|
|
|
|
34
|
$p++; |
|
143
|
|
|
|
|
|
|
}; |
|
144
|
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
# Whatever remains will just be appended |
|
146
|
2
|
|
|
|
|
4
|
push @$result, @$s; |
|
147
|
|
|
|
|
|
|
}; |
|
148
|
|
|
|
|
|
|
}; |
|
149
|
|
|
|
|
|
|
|
|
150
|
5
|
|
|
|
|
12
|
$result; |
|
151
|
|
|
|
|
|
|
} |
|
152
|
|
|
|
|
|
|
|
|
153
|
7
|
50
|
|
7
|
0
|
15
|
sub simple_pivot_by( %options ) { |
|
|
7
|
|
|
|
|
18
|
|
|
|
7
|
|
|
|
|
5
|
|
|
154
|
7
|
|
|
|
|
18
|
my $sql = pivot_sql( %options ); |
|
155
|
7
|
|
|
|
|
36
|
my $sth = $options{ dbh }->prepare( $sql ); |
|
156
|
7
|
|
|
|
|
497
|
$sth->execute( @{$options{ placeholder_values }} ); |
|
|
7
|
|
|
|
|
188
|
|
|
157
|
7
|
|
|
|
|
66
|
my $rows = $sth->fetchall_arrayref({}); |
|
158
|
7
|
|
|
|
|
767
|
my @aggregate_columns; |
|
159
|
7
|
50
|
|
|
|
15
|
if( exists $options{ aggregate_columns }) { |
|
160
|
0
|
|
|
|
|
0
|
@aggregate_columns = @{ $options{ aggregate_columns }}; |
|
|
0
|
|
|
|
|
0
|
|
|
161
|
|
|
|
|
|
|
} else { |
|
162
|
7
|
50
|
|
|
|
8
|
@aggregate_columns = map {/(\w+)\w*$/ ? $1 : $_ } @{ $options{ aggregate }}; |
|
|
7
|
|
|
|
|
68
|
|
|
|
7
|
|
|
|
|
13
|
|
|
163
|
|
|
|
|
|
|
}; |
|
164
|
7
|
|
|
|
|
26
|
pivot_list( %options, aggregate => \@aggregate_columns, list => $rows ); |
|
165
|
|
|
|
|
|
|
} |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
# Takes an AoA and derives the total order from it if possible |
|
168
|
|
|
|
|
|
|
# Returns the total order of the keys. Not every key is expected to be available |
|
169
|
|
|
|
|
|
|
# in every row |
|
170
|
0
|
0
|
|
0
|
0
|
0
|
sub partial_order( $comparator, $keygen, @list ) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
171
|
0
|
|
|
|
|
0
|
my %sort; |
|
172
|
|
|
|
|
|
|
my %keys; |
|
173
|
|
|
|
|
|
|
|
|
174
|
0
|
|
|
|
|
0
|
for my $row (@list) { |
|
175
|
0
|
|
|
|
|
0
|
my $last_key; |
|
176
|
0
|
|
|
|
|
0
|
for my $col (@$row) { |
|
177
|
|
|
|
|
|
|
# This approach doesn't have the transitive property |
|
178
|
|
|
|
|
|
|
# We need to place items in arrays resp. on a float lattice |
|
179
|
|
|
|
|
|
|
# $sort{ $item } = (max( $sort_after($item ) - min( $sort_before($item)) / 2 |
|
180
|
0
|
|
|
|
|
0
|
my $key = $keygen->( $col ); |
|
181
|
0
|
|
|
|
|
0
|
$keys{ $key } = 1; |
|
182
|
0
|
0
|
|
|
|
0
|
if( defined $last_key ) { |
|
183
|
0
|
|
|
|
|
0
|
for my $cmp (["$last_key\0$key",-1], |
|
184
|
|
|
|
|
|
|
["$key\0$last_key",1], |
|
185
|
|
|
|
|
|
|
) { |
|
186
|
0
|
|
|
|
|
0
|
my ($k,$v) = @$cmp; |
|
187
|
0
|
|
|
|
|
0
|
$sort{$k} = $v; |
|
188
|
|
|
|
|
|
|
} |
|
189
|
|
|
|
|
|
|
} else { |
|
190
|
0
|
|
|
|
|
0
|
$last_key = $key; |
|
191
|
|
|
|
|
|
|
}; |
|
192
|
|
|
|
|
|
|
} |
|
193
|
|
|
|
|
|
|
} |
|
194
|
|
|
|
|
|
|
|
|
195
|
0
|
|
|
|
|
0
|
sort { $sort{ $a } <=> $sort{$b} } keys %keys; |
|
|
0
|
|
|
|
|
0
|
|
|
196
|
|
|
|
|
|
|
} |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
# Pivots an AoH (no AoA support yet!?) |
|
199
|
|
|
|
|
|
|
# The list must already be sorted by @rows, @columns |
|
200
|
|
|
|
|
|
|
# At least one line must contain all column values (!) |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
=head2 C<< pivot_list >> |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
my $l = pivot_list( |
|
205
|
|
|
|
|
|
|
list => @AoH, |
|
206
|
|
|
|
|
|
|
columns => ['date'], |
|
207
|
|
|
|
|
|
|
rows => ['region'], |
|
208
|
|
|
|
|
|
|
aggregate => ['amount'], |
|
209
|
|
|
|
|
|
|
); |
|
210
|
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
The rows of C<@$l> are then plain arrays not hashes. |
|
212
|
|
|
|
|
|
|
The first row of C<@$l> will contain the column titles. |
|
213
|
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
The column titles are built from joining the pivot column values by C<$;> . |
|
215
|
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
=over 4 |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
=item B |
|
219
|
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
headers => 1, |
|
221
|
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
Whether to include the headers as the first row |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
=back |
|
225
|
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
=cut |
|
227
|
|
|
|
|
|
|
|
|
228
|
10
|
50
|
|
10
|
1
|
1802
|
sub pivot_list( %options ) { |
|
|
10
|
|
|
|
|
31
|
|
|
|
10
|
|
|
|
|
10
|
|
|
229
|
10
|
|
|
|
|
10
|
my @rows; |
|
230
|
|
|
|
|
|
|
my %colnum; |
|
231
|
0
|
|
|
|
|
0
|
my %rownum; |
|
232
|
|
|
|
|
|
|
|
|
233
|
10
|
100
|
|
|
|
21
|
if( ! exists $options{ headers }) { |
|
234
|
6
|
|
|
|
|
22
|
$options{ headers } = 1; |
|
235
|
|
|
|
|
|
|
}; |
|
236
|
|
|
|
|
|
|
|
|
237
|
10
|
100
|
|
|
|
8
|
my @key_cols = @{ $options{ columns } || [] }; |
|
|
10
|
|
|
|
|
35
|
|
|
238
|
10
|
100
|
|
|
|
12
|
my @key_rows = @{ $options{ rows } || [] }; |
|
|
10
|
|
|
|
|
27
|
|
|
239
|
10
|
50
|
|
|
|
8
|
my @aggregates = @{ $options{ aggregate } || [] }; |
|
|
10
|
|
|
|
|
26
|
|
|
240
|
10
|
|
|
|
|
7
|
my @colhead; |
|
241
|
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
# Now we need to determine the numbers for all the columns |
|
243
|
10
|
50
|
|
|
|
17
|
if( $options{ sort_columns } ) { |
|
244
|
|
|
|
|
|
|
# If we have a user-supplied sorting function, use that: |
|
245
|
0
|
|
|
0
|
|
0
|
@colnum{ sort( sub { $options{ sort_columns }->($a,$b) }, keys %colnum )} |
|
|
0
|
|
|
|
|
0
|
|
|
246
|
|
|
|
|
|
|
= (@key_rows)..((@key_rows)+(keys %colnum)-1); |
|
247
|
0
|
|
|
|
|
0
|
for( keys %colnum ) { |
|
248
|
0
|
|
|
|
|
0
|
$colhead[ $colnum{ $_ }] = $_; |
|
249
|
|
|
|
|
|
|
}; |
|
250
|
|
|
|
|
|
|
} else { |
|
251
|
|
|
|
|
|
|
# We assume that the first row contains all columns in order. |
|
252
|
|
|
|
|
|
|
# Following lines may skip values or have additional columns which |
|
253
|
|
|
|
|
|
|
# will be appended. This could be smarter by introducing a partial |
|
254
|
|
|
|
|
|
|
# order in the hope that everything will work out in the end. |
|
255
|
10
|
|
|
|
|
12
|
my $col = @key_rows; |
|
256
|
10
|
|
|
|
|
8
|
for my $cell (@{ $options{ list }}) { |
|
|
10
|
|
|
|
|
19
|
|
|
257
|
147
|
|
|
|
|
84
|
my $colkey = join $;, @{ $cell }{ @key_cols }; |
|
|
147
|
|
|
|
|
129
|
|
|
258
|
147
|
100
|
|
|
|
185
|
if( ! exists $colnum{ $colkey }) { |
|
259
|
48
|
|
66
|
|
|
142
|
$colnum{ $colkey } ||= $col++; |
|
260
|
48
|
|
|
|
|
53
|
push @colhead, $colkey; |
|
261
|
|
|
|
|
|
|
}; |
|
262
|
|
|
|
|
|
|
}; |
|
263
|
|
|
|
|
|
|
} |
|
264
|
|
|
|
|
|
|
|
|
265
|
10
|
|
|
|
|
11
|
my @effective_key_rows = grep { defined $_ } @key_rows; # remove placeholders |
|
|
15
|
|
|
|
|
26
|
|
|
266
|
|
|
|
|
|
|
|
|
267
|
10
|
50
|
|
|
|
18
|
if( ! @colhead) { |
|
268
|
0
|
|
|
|
|
0
|
@colhead = $aggregates[0]; |
|
269
|
|
|
|
|
|
|
}; |
|
270
|
|
|
|
|
|
|
|
|
271
|
10
|
|
|
|
|
11
|
my $last_row; |
|
272
|
|
|
|
|
|
|
my @row; |
|
273
|
10
|
|
|
|
|
10
|
for my $cell (@{ $options{ list }}) { |
|
|
10
|
|
|
|
|
9
|
|
|
274
|
147
|
|
|
|
|
96
|
my $colkey = join $;, @{ $cell }{ @key_cols }; |
|
|
147
|
|
|
|
|
133
|
|
|
275
|
147
|
|
|
|
|
79
|
my $rowkey = join $;, @{ $cell }{ @effective_key_rows }; |
|
|
147
|
|
|
|
|
134
|
|
|
276
|
|
|
|
|
|
|
|
|
277
|
147
|
100
|
100
|
|
|
373
|
if( defined $last_row and $rowkey ne $last_row ) { |
|
278
|
37
|
|
|
|
|
53
|
push @rows, [splice @row, 0]; |
|
279
|
|
|
|
|
|
|
}; |
|
280
|
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
# We should have %row instead, but how to name the |
|
282
|
|
|
|
|
|
|
# columns and rows that are values now?! |
|
283
|
|
|
|
|
|
|
# prefix "pivot_" ? |
|
284
|
|
|
|
|
|
|
# Allow the user to supply names? |
|
285
|
|
|
|
|
|
|
# Expect the user to rename the keys? |
|
286
|
147
|
100
|
|
|
|
183
|
if( ! @row ) { |
|
287
|
47
|
100
|
|
|
|
36
|
@row = map { defined $_ ? $cell->{$_} : undef } @key_rows; |
|
|
80
|
|
|
|
|
137
|
|
|
288
|
|
|
|
|
|
|
}; |
|
289
|
|
|
|
|
|
|
|
|
290
|
147
|
|
|
|
|
277
|
my %cellv = %$cell; |
|
291
|
147
|
|
|
|
|
108
|
@cellv{ @aggregates } = @{$cell}{@aggregates}; |
|
|
147
|
|
|
|
|
126
|
|
|
292
|
|
|
|
|
|
|
#$row[ $colnum{ $colkey }] = \%cellv; |
|
293
|
147
|
|
|
|
|
148
|
$row[ $colnum{ $colkey }] = $cell->{ $aggregates[0] }; |
|
294
|
147
|
|
|
|
|
156
|
$last_row = $rowkey; |
|
295
|
|
|
|
|
|
|
}; |
|
296
|
10
|
50
|
|
|
|
18
|
if(@row) { |
|
297
|
10
|
|
|
|
|
11
|
push @rows, \@row; |
|
298
|
|
|
|
|
|
|
}; |
|
299
|
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
unshift @rows, [ @key_rows, @colhead ] |
|
301
|
10
|
100
|
|
|
|
30
|
if $options{ headers }; |
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
\@rows |
|
304
|
10
|
|
|
|
|
128
|
} |
|
305
|
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
=head2 C<< pivot_sql >> |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
pivot_sql( |
|
309
|
|
|
|
|
|
|
columns => ['date'], |
|
310
|
|
|
|
|
|
|
rows => ['region'], |
|
311
|
|
|
|
|
|
|
aggregate => ['sum(amount) as amount'], |
|
312
|
|
|
|
|
|
|
sql => <<'SQL' ); |
|
313
|
|
|
|
|
|
|
select |
|
314
|
|
|
|
|
|
|
"date" |
|
315
|
|
|
|
|
|
|
, region |
|
316
|
|
|
|
|
|
|
, amount |
|
317
|
|
|
|
|
|
|
from mytable |
|
318
|
|
|
|
|
|
|
SQL |
|
319
|
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
Creates SQL around a subselect that aggregates the given |
|
321
|
|
|
|
|
|
|
columns. |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
The SQL created by the call above would be |
|
324
|
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
select "region" |
|
326
|
|
|
|
|
|
|
, "date" |
|
327
|
|
|
|
|
|
|
, sum(amount) as amount |
|
328
|
|
|
|
|
|
|
from ( |
|
329
|
|
|
|
|
|
|
select |
|
330
|
|
|
|
|
|
|
"date" |
|
331
|
|
|
|
|
|
|
, region |
|
332
|
|
|
|
|
|
|
, amount |
|
333
|
|
|
|
|
|
|
from mytable |
|
334
|
|
|
|
|
|
|
) foo |
|
335
|
|
|
|
|
|
|
group by "region, "date" |
|
336
|
|
|
|
|
|
|
order by "region", "date" |
|
337
|
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
Note that the values in the C and C options will be automatically |
|
339
|
|
|
|
|
|
|
enclosed in double quotes. |
|
340
|
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
This function is convenient if you want to ccreate ad-hoc pivot queries instead |
|
342
|
|
|
|
|
|
|
of setting up the appropriate views in the database. |
|
343
|
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
If you want to produce subtotals, this function can be called |
|
345
|
|
|
|
|
|
|
with the elements removed successively from C<$options{rows}> or |
|
346
|
|
|
|
|
|
|
C<$options{columns}> for computing row or column totals. |
|
347
|
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
=cut |
|
349
|
|
|
|
|
|
|
|
|
350
|
8
|
50
|
|
8
|
1
|
25
|
sub pivot_sql( %options ) { |
|
|
8
|
|
|
|
|
18
|
|
|
|
8
|
|
|
|
|
8
|
|
|
351
|
8
|
50
|
|
|
|
9
|
my @columns = (grep { defined $_ } @{ $options{ rows } || [] }, @{ $options{ columns } || []}); |
|
|
21
|
50
|
|
|
|
34
|
|
|
|
8
|
|
|
|
|
20
|
|
|
|
8
|
|
|
|
|
20
|
|
|
352
|
8
|
|
|
|
|
15
|
my $qcolumns = join "\n , ", @columns, @{ $options{ aggregate }}; |
|
|
8
|
|
|
|
|
20
|
|
|
353
|
8
|
|
|
|
|
13
|
my $keycolumns = join "\n , ", @columns; |
|
354
|
8
|
|
|
|
|
7
|
my $clauses = ''; |
|
355
|
8
|
50
|
|
|
|
15
|
if($keycolumns) { |
|
356
|
8
|
|
|
|
|
18
|
$clauses = join "\n", |
|
357
|
|
|
|
|
|
|
"group by $keycolumns", |
|
358
|
|
|
|
|
|
|
"order by $keycolumns", |
|
359
|
|
|
|
|
|
|
}; |
|
360
|
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
return <
|
|
362
|
|
|
|
|
|
|
select |
|
363
|
|
|
|
|
|
|
$qcolumns |
|
364
|
|
|
|
|
|
|
from ( |
|
365
|
|
|
|
|
|
|
$options{sql} |
|
366
|
|
|
|
|
|
|
) foo |
|
367
|
|
|
|
|
|
|
$clauses |
|
368
|
|
|
|
|
|
|
SQL |
|
369
|
8
|
|
|
|
|
35
|
} |
|
370
|
|
|
|
|
|
|
1; |
|
371
|
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
=head1 Unsupported features |
|
373
|
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
Currently only one aggregate value is allowed. |
|
375
|
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
Row aggregates ("totals") are not supported yet. Row aggregates will |
|
377
|
|
|
|
|
|
|
mean heavy rewriting of the SQL to wrap the aggregate function over the column |
|
378
|
|
|
|
|
|
|
names of the query. |
|
379
|
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
L |
|
383
|
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
=head1 REPOSITORY |
|
385
|
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
The public repository of this module is |
|
387
|
|
|
|
|
|
|
L. |
|
388
|
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
=head1 SUPPORT |
|
390
|
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
The public support forum of this module is |
|
392
|
|
|
|
|
|
|
L. |
|
393
|
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
=head1 BUG TRACKER |
|
395
|
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
Please report bugs in this module via the RT CPAN bug queue at |
|
397
|
|
|
|
|
|
|
L |
|
398
|
|
|
|
|
|
|
or via mail to L. |
|
399
|
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
=head1 AUTHOR |
|
401
|
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
Max Maischein C |
|
403
|
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
=head1 COPYRIGHT (c) |
|
405
|
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
Copyright 2017 by Max Maischein C. |
|
407
|
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
=head1 LICENSE |
|
409
|
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
This module is released under the same terms as Perl itself. |
|
411
|
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
=cut |