← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 9271: Analytics, impl query planner

 

------------------------------------------------------------
revno: 9271
committer: Lars Helge Øverland <larshelge@xxxxxxxxx>
branch nick: dhis2
timestamp: Tue 2012-12-11 21:46:36 +0100
message:
  Analytics, impl query planner
added:
  dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DataQueryParams.java
  dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/QueryPlanner.java
  dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/ListMap.java
renamed:
  dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/ShardUtils.java => dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/PartitionUtils.java
  dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/ShardUtilsTest.java => dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/PartitionUtilsTest.java
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitService.java
  dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsManager.java
  dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsService.java
  dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultAnalyticsService.java
  dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/JdbcAnalyticsManager.java
  dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableService.java
  dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManager.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/organisationunit/DefaultOrganisationUnitService.java
  dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/PartitionUtils.java
  dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/PartitionUtilsTest.java


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitService.java	2012-12-11 09:10:15 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitService.java	2012-12-11 20:46:36 +0000
@@ -199,6 +199,13 @@
     int getLevelOfOrganisationUnit( int id );
 
     /**
+     * Returns the level of the organisation unit with the given uid.
+     * 
+     * @return the level of the organisation unit with the given uid.
+     */
+    int getLevelOfOrganisationUnit( String uid );
+    
+    /**
      * Returns all OrganisationUnits which are part of the subtree of the
      * OrganisationUnit with the given identifer and have no children.
      *

=== modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsManager.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsManager.java	2012-12-03 21:04:39 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsManager.java	2012-12-11 20:46:36 +0000
@@ -27,7 +27,6 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Future;
 
@@ -35,6 +34,5 @@
 
 public interface AnalyticsManager
 {
-    Future<List<AggregatedDataValue>> getAggregatedDataValueTotals( Collection<AggregatedDataValue> values, Collection<Integer> dataElementIds, 
-        Collection<String> periodIds, Collection<Integer> organisationUnitIds );
+    Future<List<AggregatedDataValue>> getAggregatedDataValueTotals(  DataQueryParams params );
 }

=== modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsService.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsService.java	2012-12-03 21:04:39 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsService.java	2012-12-11 20:46:36 +0000
@@ -27,13 +27,11 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-import java.util.Collection;
 import java.util.List;
 
 import org.hisp.dhis.aggregation.AggregatedDataValue;
 
 public interface AnalyticsService
 {
-    List<AggregatedDataValue> getAggregatedDataValueTotals( Collection<Integer> dataElementIds, 
-        Collection<String> periodIds, Collection<Integer> organisationUnitIds ) throws Exception;
+    List<AggregatedDataValue> getAggregatedDataValueTotals( DataQueryParams params ) throws Exception;
 }

=== added file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DataQueryParams.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DataQueryParams.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DataQueryParams.java	2012-12-11 20:46:36 +0000
@@ -0,0 +1,255 @@
+package org.hisp.dhis.analytics;
+
+/*
+ * Copyright (c) 2004-2012, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the HISP project nor the names of its contributors may
+ *   be used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.hisp.dhis.common.Dxf2Namespace;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
+@JacksonXmlRootElement( localName = "dxf2", namespace = Dxf2Namespace.NAMESPACE )
+public class DataQueryParams
+{
+    public static final String INDICATOR_DIM_ID = "in";
+    public static final String DATAELEMENT_DIM_ID = "de";
+    public static final String PERIOD_DIM_ID = "pe";
+    public static final String ORGUNIT_DIM_ID = "ou";
+    
+    private List<String> indicators = new ArrayList<String>();
+    
+    private List<String> dataElements = new ArrayList<String>();
+    
+    private List<String> periods = new ArrayList<String>();
+    
+    private List<String> organisationUnits = new ArrayList<String>();
+    
+    private Map<String, List<String>> dimensions = new HashMap<String, List<String>>();
+    
+    private boolean categories = false;
+    
+    private transient String tableName;
+
+    // -------------------------------------------------------------------------
+    // Constructors
+    // -------------------------------------------------------------------------
+  
+    public DataQueryParams()
+    {
+    }
+    
+    public DataQueryParams( List<String> indicators, List<String> dataElements, List<String> periods,
+        List<String> organisationUnits, Map<String, List<String>> dimensions, boolean categories )
+    {
+        this.indicators = indicators;
+        this.dataElements = dataElements;
+        this.periods = periods;
+        this.organisationUnits = organisationUnits;
+        this.dimensions = dimensions;
+        this.categories = categories;
+    }
+    
+    public DataQueryParams( DataQueryParams params )
+    {
+        this.indicators = new ArrayList<String>( params.getIndicators() );
+        this.dataElements = new ArrayList<String>( params.getDataElements() );
+        this.periods = new ArrayList<String>( params.getPeriods() );
+        this.organisationUnits = new ArrayList<String>( params.getOrganisationUnits() );
+        this.dimensions = new HashMap<String, List<String>>( params.getDimensions() );
+        this.categories = params.isCategories();
+    }
+
+    // -------------------------------------------------------------------------
+    // Logic
+    // -------------------------------------------------------------------------
+
+    public SortedMap<String, List<String>> getDimensionValuesMap()
+    {
+        SortedMap<String, List<String>> map = new TreeMap<String, List<String>>();
+        
+        // TODO convert indicators to data elements
+        
+        map.put( DATAELEMENT_DIM_ID, new ArrayList<String>( dataElements ) );
+        map.put( ORGUNIT_DIM_ID, new ArrayList<String>( organisationUnits ) );
+        map.put( PERIOD_DIM_ID, new ArrayList<String>( periods ) );
+        
+        for ( String dimension : dimensions.keySet() )
+        {
+            map.put( dimension, dimensions.get( dimension ) );
+        }
+        
+        return map;
+    }
+    
+    public void setDimension( String dimension, List<String> values )
+    {
+        if ( DATAELEMENT_DIM_ID.equals( dimension ) )
+        {
+            setDataElements( values );
+        }
+        else if ( PERIOD_DIM_ID.equals( dimension ) )
+        {
+            setPeriods( values );
+        }
+        else if ( ORGUNIT_DIM_ID.equals( dimension ) )
+        {
+            setOrganisationUnits( values );
+        }
+        else if ( dimensions.containsKey( dimension ) )
+        {
+            dimensions.put( dimension, values );
+        }
+    }
+    
+    public List<String> getDimension( String dimension )
+    {
+        if ( DATAELEMENT_DIM_ID.equals( dimension ) )
+        {
+            return dataElements;
+        }
+        else if ( PERIOD_DIM_ID.equals( dimension ) )
+        {
+            return periods;
+        }
+        else if ( ORGUNIT_DIM_ID.equals( dimension ) )
+        {
+            return organisationUnits;
+        }
+        else if ( dimensions.containsKey( dimension ) )
+        {
+            return dimensions.get( dimension );
+        }
+        
+        throw new IllegalArgumentException( dimension );
+    }
+    
+    public String getLargestDimension()
+    {
+        Map<String, List<String>> map = getDimensionValuesMap();
+        
+        String dimension = map.keySet().iterator().next();
+        int size = map.get( dimension ).size();
+                
+        for ( String dim : map.keySet() )
+        {
+            if ( map.get( dim ).size() > size )
+            {
+                dimension = dim;
+                size = map.get( dim ).size();
+            }
+        }
+        
+        return dimension;
+    }
+        
+    // -------------------------------------------------------------------------
+    // Get and set methods
+    // -------------------------------------------------------------------------
+  
+    @JsonProperty( value = INDICATOR_DIM_ID )
+    public List<String> getIndicators()
+    {
+        return indicators;
+    }
+
+    public void setIndicators( List<String> indicators )
+    {
+        this.indicators = indicators;
+    }
+
+    @JsonProperty( value = DATAELEMENT_DIM_ID )
+    public List<String> getDataElements()
+    {
+        return dataElements;
+    }
+
+    public void setDataElements( List<String> dataElements )
+    {
+        this.dataElements = dataElements;
+    }
+
+    @JsonProperty( value = PERIOD_DIM_ID )
+    public List<String> getPeriods()
+    {
+        return periods;
+    }
+
+    public void setPeriods( List<String> periods )
+    {
+        this.periods = periods;
+    }
+
+    @JsonProperty( value = ORGUNIT_DIM_ID )
+    public List<String> getOrganisationUnits()
+    {
+        return organisationUnits;
+    }
+
+    public void setOrganisationUnits( List<String> organisationUnits )
+    {
+        this.organisationUnits = organisationUnits;
+    }
+
+    @JsonProperty( value = "dimensions" )
+    public Map<String, List<String>> getDimensions()
+    {
+        return dimensions;
+    }
+
+    public void setDimensions( Map<String, List<String>> dimensions )
+    {
+        this.dimensions = dimensions;
+    }
+
+    @JsonProperty( value = "categories" )
+    public boolean isCategories()
+    {
+        return categories;
+    }
+
+    public void setCategories( boolean categories )
+    {
+        this.categories = categories;
+    }
+
+    public String getTableName()
+    {
+        return tableName;
+    }
+
+    public void setTableName( String tableName )
+    {
+        this.tableName = tableName;
+    }
+}

=== modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultAnalyticsService.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultAnalyticsService.java	2012-12-03 21:04:39 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultAnalyticsService.java	2012-12-11 20:46:36 +0000
@@ -28,37 +28,40 @@
  */
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Future;
 
 import org.hisp.dhis.aggregation.AggregatedDataValue;
 import org.hisp.dhis.analytics.AnalyticsManager;
 import org.hisp.dhis.analytics.AnalyticsService;
-import org.hisp.dhis.system.util.PaginatedList;
+import org.hisp.dhis.analytics.DataQueryParams;
 import org.hisp.dhis.system.util.Timer;
 import org.springframework.beans.factory.annotation.Autowired;
 
 public class DefaultAnalyticsService
     implements AnalyticsService
 {
+    //TODO select from correct shard
+    //TODO period aggregation for multiple period types
+    //TODO hierarchy aggregation for org units at multiple levels
+    //TODO indicator aggregation
+    
     @Autowired
     private AnalyticsManager analyticsManager;
     
-    public List<AggregatedDataValue> getAggregatedDataValueTotals( Collection<Integer> dataElementIds, 
-        Collection<String> periodIds, Collection<Integer> organisationUnitIds ) throws Exception
+    public List<AggregatedDataValue> getAggregatedDataValueTotals( DataQueryParams params ) throws Exception
     {
         Timer t = new Timer().start();
         
-        List<List<Integer>> dePages = new PaginatedList<Integer>( dataElementIds ).setNumberOfPages( 4 ).getPages();
+        List<DataQueryParams> queries = QueryPlanner.planQuery( params, 6 );
         
         List<Future<List<AggregatedDataValue>>> futures = new ArrayList<Future<List<AggregatedDataValue>>>();
         
         List<AggregatedDataValue> values = new ArrayList<AggregatedDataValue>();
         
-        for ( List<Integer> dePage : dePages )
+        for ( DataQueryParams query : queries )
         {
-            futures.add( analyticsManager.getAggregatedDataValueTotals( values, dePage, periodIds, organisationUnitIds ) );
+            futures.add( analyticsManager.getAggregatedDataValueTotals( query ) );
         }
         
         for ( Future<List<AggregatedDataValue>> future : futures )

=== modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/JdbcAnalyticsManager.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/JdbcAnalyticsManager.java	2012-12-03 21:04:39 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/JdbcAnalyticsManager.java	2012-12-11 20:46:36 +0000
@@ -27,10 +27,8 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-import static org.hisp.dhis.system.util.TextUtils.getCommaDelimitedString;
 import static org.hisp.dhis.system.util.TextUtils.getQuotedCommaDelimitedString;
 
-import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Future;
 
@@ -38,6 +36,7 @@
 import org.apache.commons.logging.LogFactory;
 import org.hisp.dhis.aggregation.AggregatedDataValue;
 import org.hisp.dhis.analytics.AnalyticsManager;
+import org.hisp.dhis.analytics.DataQueryParams;
 import org.hisp.dhis.expression.ExpressionService;
 import org.hisp.dhis.organisationunit.OrganisationUnitService;
 import org.hisp.dhis.period.PeriodService;
@@ -76,23 +75,18 @@
     // Implementation
     // -------------------------------------------------------------------------
 
-    //TODO period aggregation for multiple period types
-    //TODO hierarchy aggregation for org units at multiple levels
-    //TODO indicator aggregation
-    
     @Async
-    public Future<List<AggregatedDataValue>> getAggregatedDataValueTotals( Collection<AggregatedDataValue> values, Collection<Integer> dataElementIds, 
-        Collection<String> periodIds, Collection<Integer> organisationUnitIds )
+    public Future<List<AggregatedDataValue>> getAggregatedDataValueTotals( DataQueryParams params )
     {
-        int level = organisationUnitService.getLevelOfOrganisationUnit( organisationUnitIds.iterator().next() );        
-        String periodType = PeriodType.getPeriodTypeFromIsoString( periodIds.iterator().next() ).getName().toLowerCase();
+        int level = organisationUnitService.getLevelOfOrganisationUnit( params.getOrganisationUnits().iterator().next() );        
+        String periodType = PeriodType.getPeriodTypeFromIsoString( params.getPeriods().iterator().next() ).getName().toLowerCase();
         
         final String sql = 
             "SELECT dataelementid, 0 as categoryoptioncomboid, periodid, idlevel" + level + " as organisationunitid, SUM(value) as value " +
-            "FROM analytics " +
-            "WHERE dataelementid IN ( " + getCommaDelimitedString( dataElementIds ) + " ) " +
-            "AND " + periodType + " IN ( " + getQuotedCommaDelimitedString( periodIds ) + " ) " +
-            "AND idlevel" + level + " IN ( " + getCommaDelimitedString( organisationUnitIds ) + " ) " +
+            "FROM " + params.getTableName() + " " +
+            "WHERE dataelementid IN ( " + getQuotedCommaDelimitedString( params.getDataElements() ) + " ) " +
+            "AND " + periodType + " IN ( " + getQuotedCommaDelimitedString( params.getPeriods() ) + " ) " +
+            "AND idlevel" + level + " IN ( " + getQuotedCommaDelimitedString( params.getOrganisationUnits() ) + " ) " +
             "GROUP BY dataelementid, periodid, idlevel" + level;
                 
         log.info( sql );

=== added file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/QueryPlanner.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/QueryPlanner.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/QueryPlanner.java	2012-12-11 20:46:36 +0000
@@ -0,0 +1,106 @@
+package org.hisp.dhis.analytics.data;
+
+/*
+ * Copyright (c) 2004-2012, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the HISP project nor the names of its contributors may
+ *   be used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedMap;
+
+import org.hisp.dhis.analytics.DataQueryParams;
+import org.hisp.dhis.analytics.table.PartitionUtils;
+import org.hisp.dhis.system.util.ListMap;
+import org.hisp.dhis.system.util.PaginatedList;
+
+public class QueryPlanner
+{
+    /**
+     * Creates a list of DataParams.
+     * 
+     * @param params the data query params.
+     * @param optimalQueries the number of optimal queries for the planner to return.
+     * @return
+     */
+    public static List<DataQueryParams> planQuery( DataQueryParams params, int optimalQueries )
+    {
+        List<DataQueryParams> queries = new ArrayList<DataQueryParams>();
+
+        ListMap<String, String> tablePeriodMap = PartitionUtils.getTablePeriodMap( params.getPeriods() );
+        
+        boolean periodSatisfies = tablePeriodMap.size() >= optimalQueries;
+        
+        if ( periodSatisfies )
+        {
+            for ( String tableName : tablePeriodMap.keySet() )
+            {
+                DataQueryParams query = new DataQueryParams( params );
+                query.setPeriods( tablePeriodMap.get( tableName ) );
+                query.setTableName( tableName );
+                queries.add( query );
+            }
+        }
+        else
+        {
+            int pages = optimalQueries / tablePeriodMap.size();
+            
+            String dimension = getPartitionDimension( params, pages );
+            
+            List<String> dimensionValues = params.getDimension( dimension );
+            
+            List<List<String>> valuePages = new PaginatedList<String>( dimensionValues ).setNumberOfPages( pages ).getPages();
+            
+            for ( String tableName : tablePeriodMap.keySet() )
+            {
+                for ( List<String> values : valuePages )
+                {
+                    DataQueryParams query = new DataQueryParams( params );
+                    query.setPeriods( tablePeriodMap.get( tableName ) );
+                    query.setDimension( dimension, values );
+                    query.setTableName( tableName );
+                    queries.add( query );
+                }
+            }
+        }
+        
+        return queries;
+    }
+    
+    private static String getPartitionDimension( DataQueryParams params, int optimalQueries )
+    {
+        SortedMap<String, List<String>> map = params.getDimensionValuesMap();
+        
+        for ( String dimension : map.keySet() )
+        {
+            if ( map.get( dimension ).size() >= optimalQueries )
+            {
+                return dimension;
+            }
+        }
+        
+        return params.getLargestDimension();
+    }
+}

=== modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableService.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableService.java	2012-12-11 01:38:43 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableService.java	2012-12-11 20:46:36 +0000
@@ -67,7 +67,7 @@
         
         final Date earliest = tableManager.getEarliestData();
         final Date latest = tableManager.getLatestData();
-        final List<String> tables = ShardUtils.getTempTableNames( earliest, latest );        
+        final List<String> tables = PartitionUtils.getTempTableNames( earliest, latest );        
         clock.logTime( "Checked data timespan" );
         
         //dropTables( tables ); //remove
@@ -116,7 +116,7 @@
             
             for ( String table : tablePage )
             {
-                Period period = ShardUtils.getPeriod( table );
+                Period period = PartitionUtils.getPeriod( table );
                 
                 futures.add( tableManager.populateTableAsync( table, period.getStartDate(), period.getEndDate() ) );
             }

=== modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManager.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManager.java	2012-12-11 01:38:43 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManager.java	2012-12-11 20:46:36 +0000
@@ -54,9 +54,13 @@
  * each organisation unit group set and organisation unit level. Also, columns
  * for dataelementid, periodid, organisationunitid, categoryoptioncomboid, value.
  * 
+ * The analytics table is horizontally partitioned. The partition key is the start 
+ * date of the  period of the data record. The table is partitioned according to 
+ * time span with one partition per calendar quarter.
+ * 
  * The data records in this table are not aggregated. Typically, queries will
  * aggregate in organisation unit hierarchy dimension, in the period/time dimension,
- * and the category dimensions, as well as org unit group set dimensions.
+ * and the category dimensions, as well as organisation unit group set dimensions.
  * 
  * @author Lars Helge Overland
  */
@@ -82,7 +86,6 @@
     // Implementation
     // -------------------------------------------------------------------------
   
-    //TODO shard on data quarter based on start date
     //TODO average aggregation operator data, pre-aggregate in time dimension, not in org unit dimension
     
     public void createTable( String tableName )

=== renamed file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/ShardUtils.java' => 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/PartitionUtils.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/ShardUtils.java	2012-12-11 01:38:43 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/PartitionUtils.java	2012-12-11 20:46:36 +0000
@@ -31,14 +31,16 @@
 import static org.hisp.dhis.analytics.AnalyticsTableManager.TABLE_NAME_TEMP;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 
 import org.hisp.dhis.period.Period;
 import org.hisp.dhis.period.PeriodType;
 import org.hisp.dhis.period.QuarterlyPeriodType;
+import org.hisp.dhis.system.util.ListMap;
 
-public class ShardUtils
+public class PartitionUtils
 {
     private static final QuarterlyPeriodType QUARTERLY = new QuarterlyPeriodType();
     
@@ -93,4 +95,16 @@
         
         return PeriodType.getPeriodFromIsoString( isoPeriod );
     }
+    
+    public static ListMap<String, String> getTablePeriodMap( Collection<String> isoPeriods )
+    {
+        ListMap<String, String> map = new ListMap<String, String>();
+        
+        for ( String period : isoPeriods )
+        {
+            map.putValue( getTable( period ), period );
+        }
+        
+        return map;
+    }
 }

=== renamed file 'dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/ShardUtilsTest.java' => 'dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/PartitionUtilsTest.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/ShardUtilsTest.java	2012-12-11 01:38:43 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/PartitionUtilsTest.java	2012-12-11 20:46:36 +0000
@@ -40,7 +40,7 @@
 import org.hisp.dhis.period.QuarterlyPeriodType;
 import org.junit.Test;
 
-public class ShardUtilsTest
+public class PartitionUtilsTest
 {
     @Test
     public void testGetTableNames()
@@ -49,7 +49,7 @@
         Date earliest = cal.set( 2000, 5, 4 ).time();
         Date latest = cal.set( 2001, 2, 10 ).time();
         
-        List<String> tables = ShardUtils.getTempTableNames( earliest, latest );
+        List<String> tables = PartitionUtils.getTempTableNames( earliest, latest );
         
         assertEquals( 4, tables.size() );
         assertTrue( tables.contains( TABLE_NAME_TEMP + "_2000Q2" ) );
@@ -61,11 +61,11 @@
     @Test
     public void testGetTable()
     {
-        assertEquals( TABLE_NAME + "_2000Q4", ShardUtils.getTable( "200011" ) );
-        assertEquals( TABLE_NAME + "_2000Q1", ShardUtils.getTable( "2000W02" ) );
-        assertEquals( TABLE_NAME + "_2000Q2", ShardUtils.getTable( "2000Q2" ) );
-        assertEquals( TABLE_NAME + "_2000Q3", ShardUtils.getTable( "2000S2" ) );
-        assertEquals( TABLE_NAME + "_2000Q1", ShardUtils.getTable( "2000" ) );
+        assertEquals( TABLE_NAME + "_2000Q4", PartitionUtils.getTable( "200011" ) );
+        assertEquals( TABLE_NAME + "_2000Q1", PartitionUtils.getTable( "2000W02" ) );
+        assertEquals( TABLE_NAME + "_2000Q2", PartitionUtils.getTable( "2000Q2" ) );
+        assertEquals( TABLE_NAME + "_2000Q3", PartitionUtils.getTable( "2000S2" ) );
+        assertEquals( TABLE_NAME + "_2000Q1", PartitionUtils.getTable( "2000" ) );
     }
     
     @Test
@@ -76,7 +76,7 @@
         Period q2 = new QuarterlyPeriodType().createPeriod( cal.set( 2000, 4, 1 ).time() );
         Period q4 = new QuarterlyPeriodType().createPeriod( cal.set( 2000, 10, 1 ).time() );
         
-        assertEquals( q2, ShardUtils.getPeriod( TABLE_NAME_TEMP + "_2000Q2" ) );
-        assertEquals( q4, ShardUtils.getPeriod( TABLE_NAME_TEMP + "_2000Q4" ) );
+        assertEquals( q2, PartitionUtils.getPeriod( TABLE_NAME_TEMP + "_2000Q2" ) );
+        assertEquals( q4, PartitionUtils.getPeriod( TABLE_NAME_TEMP + "_2000Q4" ) );
     }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/organisationunit/DefaultOrganisationUnitService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/organisationunit/DefaultOrganisationUnitService.java	2012-12-11 09:10:15 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/organisationunit/DefaultOrganisationUnitService.java	2012-12-11 20:46:36 +0000
@@ -230,6 +230,11 @@
         return getOrganisationUnit( id ).getOrganisationUnitLevel();
     }
 
+    public int getLevelOfOrganisationUnit( String uid )
+    {
+        return getOrganisationUnit( uid ).getOrganisationUnitLevel();
+    }
+
     public Collection<OrganisationUnit> getLeafOrganisationUnits( int id )
     {
         Collection<OrganisationUnit> units = getOrganisationUnitWithChildren( id );

=== added file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/ListMap.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/ListMap.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/ListMap.java	2012-12-11 20:46:36 +0000
@@ -0,0 +1,18 @@
+package org.hisp.dhis.system.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class ListMap<T, V>
+    extends HashMap<T, List<V>>
+{
+    public List<String> putValue( T key, V value )
+    {
+        List<V> list = this.get( key );
+        list = list == null ? new ArrayList<V>() : list;        
+        list.add( value );
+        this.put( key, list );        
+        return null;
+    }
+}