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 |