LP#1789679: Provide ROLLUP support in report output
authorMike Rylander <mrylander@gmail.com>
Wed, 29 Aug 2018 15:39:30 +0000 (11:39 -0400)
committerGalen Charlton <gmc@equinoxinitiative.org>
Mon, 24 Aug 2020 15:40:27 +0000 (11:40 -0400)
In Postgres 9.5 and newer, it is simple to generate group subtotal and grand
total rows for aggregate queries. The simplest thing would be to spell the
grouping clause as

  GROUP BY ROLLUP (...)

rather than

  GROUP BY ...

when there is a mix of aggregate and non-aggregate columns in a report.  This
branch does just that.

The report definition UI now has a checkbox labeled "Calculate grouping
subtotals" which enables use of the ROLLUP functionality.

Signed-off-by: Mike Rylander <mrylander@gmail.com>
Signed-off-by: Chris Sharp <csharp@georgialibraries.org>
Signed-off-by: Lynn Floyd <lfloyd1@library.in.gov>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Open-ILS/src/perlmods/lib/OpenILS/Reporter/SQLBuilder.pm
Open-ILS/src/reporter/clark-kent.pl
Open-ILS/web/opac/locale/en-US/reports.dtd
Open-ILS/web/reports/oils_rpt_editor.xhtml
Open-ILS/web/reports/oils_rpt_report_editor.js

index 69eab42..22bee1a 100644 (file)
@@ -80,6 +80,7 @@ sub parse_report {
         ->set_where( $report->{where} )
         ->set_having( $report->{having} )
         ->set_order_by( $report->{order_by} )
+        ->set_do_rollup( $report->{do_rollup} )
         ->set_pivot_data( $report->{pivot_data} )
         ->set_pivot_label( $report->{pivot_label} )
         ->set_pivot_default( $report->{pivot_default} );
@@ -99,6 +100,11 @@ sub is_subquery {
     return $self->{_is_subquery};
 }
 
+sub do_rollup {
+    my $self = shift;
+    return $self->builder->{_do_rollup};
+}
+
 sub pivot_data {
     my $self = shift;
     return $self->builder->{_pivot_data};
@@ -114,6 +120,13 @@ sub pivot_default {
     return $self->builder->{_pivot_default};
 }
 
+sub set_do_rollup {
+    my $self = shift;
+    my $p = shift;
+    $self->builder->{_do_rollup} = $p if (defined $p);
+    return $self;
+}
+
 sub set_pivot_default {
     my $self = shift;
     my $p = shift;
@@ -254,7 +267,14 @@ sub toSQL {
 
     my @group_by = $self->group_by_list;
 
-    $sql .= '  GROUP BY ' . join(', ', @group_by) . "\n" if (@group_by);
+    # The GROUP BY clause is used to generate distinct rows even if there are no aggregates in the select list
+    my $rollup_start = 'ROLLUP (';
+    my $rollup_end = ')';
+
+    $rollup_start = $rollup_end = ''
+        if (!$self->do_rollup or scalar(@group_by) == scalar(@{$self->{_select}})); # No ROLLUP if there are no aggregates, or not requested
+
+    $sql .= "  GROUP BY $rollup_start" . join(', ', @group_by) . "$rollup_end\n" if (@group_by);
     $sql .= "  HAVING " . join("\n\tAND ", map { $_->toSQL } @{ $self->{_having} }) . "\n" if (@{ $self->{_having} });
     $sql .= '  ORDER BY ' . join(', ', map { $_->toSQL } @{ $self->{_order_by} }) . "\n" if (@{ $self->{_order_by} });
 
index 72edf18..320ee8c 100755 (executable)
@@ -199,6 +199,7 @@ while (my $r = $sth->fetchrow_hashref) {
        $b->register_params( $report_data );
 
        $r->{resultset} = $b->parse_report( OpenSRF::Utils::JSON->JSON2perl( $r->{report}->{template}->{data} ) );
+       $r->{resultset}->set_do_rollup($report_data->{__do_rollup}) if $report_data->{__do_rollup};
        $r->{resultset}->set_pivot_data($report_data->{__pivot_data}) if $report_data->{__pivot_data};
        $r->{resultset}->set_pivot_label($report_data->{__pivot_label}) if $report_data->{__pivot_label};
        $r->{resultset}->set_pivot_default($report_data->{__pivot_default}) if $report_data->{__pivot_default};
index 4654292..1e7453a 100644 (file)
@@ -80,6 +80,7 @@
 <!ENTITY reports.oils_rpt_editor.store_report_definition "Choose a folder to store this report definition: ">
 <!ENTITY reports.oils_rpt_editor.selected_folder "Selected Folder: ">
 <!ENTITY reports.oils_rpt_editor.output_options "Output Options">
+<!ENTITY reports.oils_rpt_editor.do_rollup "Calculate grouping subtotals">
 <!ENTITY reports.oils_rpt_editor.excel_output "Excel Output">
 <!ENTITY reports.oils_rpt_editor.csv_output "CSV Output">
 <!ENTITY reports.oils_rpt_editor.html_output "HTML Output">
index c18583a..0aec6ca 100644 (file)
@@ -93,6 +93,7 @@
                                        <ul>
                                                <li><input type="checkbox" id="oils_rpt_format_excel" value="t" checked="checked"/><span>&reports.oils_rpt_editor.excel_output;</span></li>
                                                <li><input type="checkbox" id="oils_rpt_format_csv" value="t"/><span>&reports.oils_rpt_editor.csv_output;</span></li>
+                        <li><input type="checkbox" id="oils_rpt_editor_do_rollup" value="1" checked="checked"/><span>&reports.oils_rpt_editor.do_rollup;</span></li>
                                                <li>
                                                        <input type="checkbox" id="oils_rpt_format_html" value="t" checked="checked"/>&reports.oils_rpt_editor.html_output;
                                                        <ul>
index ae69b46..a223c42 100644 (file)
@@ -141,6 +141,7 @@ oils_rpt_editor_pivot_data
                 setSelector(DOM.oils_rpt_editor_pivot_label, rpt_data.__pivot_label);
             if (rpt_data.__pivot_data)
                 setSelector(DOM.oils_rpt_editor_pivot_data, rpt_data.__pivot_data);
+            DOM.oils_rpt_editor_do_rollup.checked = rpt_data.__do_rollup == '1';
         }
 
         if (run = this.last_run) {
@@ -353,7 +354,7 @@ oilsRptReportEditor.prototype.save = function(options) {
         data.__pivot_label = getSelectorVal(DOM.oils_rpt_editor_pivot_label);
         data.__pivot_data = getSelectorVal(DOM.oils_rpt_editor_pivot_data);
     }
-
+    data.__do_rollup = DOM.oils_rpt_editor_do_rollup.checked ? '1' : '0';
 
        data = js2JSON(data);
        _debug("complete report data = "+data);