Skip to content

Commit

Permalink
implement limit function on axis (topcount, bottompercent, ...)
Browse files Browse the repository at this point in the history
  • Loading branch information
pstoellberger committed Jan 22, 2013
1 parent 1df1436 commit d07acc9
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 3 deletions.
58 changes: 58 additions & 0 deletions src/org/olap4j/query/LimitFunction.java
@@ -0,0 +1,58 @@
/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
package org.olap4j.query;

/**
* Defines in what order to perform sort operations.
*
* @author Paul Stoellberger
* @since 1.0.1
*/
public enum LimitFunction {
/**
* Returns a specified number of items from the
* top of a set, optionally ordering the set first.
*/
TopCount,
/**
* Sorts a set and returns the top N elements
* whose cumulative total is at least a specified percentage.
*/
TopPercent,
/**
* Sorts a set and returns the top N elements
* whose cumulative total is at least a specified value.
*/
TopSum,
/**
* Returns a specified number of items from the
* bottom of a set, optionally ordering the set first.
*/
BottomCount,
/**
* Sorts a set and returns the bottom N elements
* whose cumulative total is at least a specified percentage.
*/
BottomPercent,
/**
* Sorts a set and returns the bottom N elements
* whose cumulative total is at least a specified value.
*/
BottomSum
}
// End LimitFunction.java
38 changes: 36 additions & 2 deletions src/org/olap4j/query/Olap4jNodeConverter.java
Expand Up @@ -299,6 +299,40 @@ private static AxisNode toOlap4j(QueryAxis axis) {
}
}

// We might need to limit the axis set
ParseTreeNode limitedNode = null;
if (axis.getLimitFunction() != null) {
ParseTreeNode n =
LiteralNode.createNumeric(
null,
axis.getLimitFunctionN(),
false);
if (axis.getLimitFunctionSortLiteral() != null) {
LiteralNode evaluatorNode = null;
evaluatorNode =
LiteralNode.createSymbol(
null,
axis.getLimitFunctionSortLiteral());
limitedNode =
new CallNode(
null,
axis.getLimitFunction().toString(),
Syntax.Function,
callNode,
n,
evaluatorNode);
} else {
limitedNode =
new CallNode(
null,
axis.getLimitFunction().toString(),
Syntax.Function,
callNode,
n);
}
} else {
limitedNode = callNode;
}
// We might need to sort the whole axis.
ParseTreeNode sortedNode = null;
if (axis.getSortOrder() != null) {
Expand All @@ -311,12 +345,12 @@ private static AxisNode toOlap4j(QueryAxis axis) {
null,
"Order",
Syntax.Function,
callNode,
limitedNode,
evaluatorNode,
LiteralNode.createSymbol(
null, axis.getSortOrder().name()));
} else {
sortedNode = callNode;
sortedNode = limitedNode;
}

return new AxisNode(
Expand Down
71 changes: 70 additions & 1 deletion src/org/olap4j/query/QueryAxis.java
Expand Up @@ -23,6 +23,7 @@
import org.olap4j.metadata.Measure;
import org.olap4j.metadata.Member;

import java.math.BigDecimal;
import java.util.*;

/**
Expand All @@ -43,7 +44,9 @@ public class QueryAxis extends QueryNodeImpl {
private boolean nonEmpty;
private SortOrder sortOrder = null;
private String sortEvaluationLiteral = null;

private LimitFunction limitFunction = null;
private BigDecimal limitFunctionN = null;
private String limitFunctionSortLiteral = null;
/**
* Creates a QueryAxis.
*
Expand Down Expand Up @@ -351,6 +354,72 @@ public SortOrder getSortOrder() {
public String getSortIdentifierNodeName() {
return sortEvaluationLiteral;
}

/**
* Returns a specified number of items from the
* top of the axis set
* @param n
*/
public void topCount(BigDecimal n) {
this.limitFunction = LimitFunction.TopCount;
this.limitFunctionN = n;
}

/**
* Returns a specified number of items from the
* bottom of the axis set
* @param n
*/
public void bottomCount(BigDecimal n) {
this.limitFunction = LimitFunction.BottomCount;
this.limitFunctionN = n;
}

/**
* Limit the axis set to a specified number of items depending
* on the sortLiteral and {@link LimitFunction}
* @param n - number of items/cumulative sum/percentage
*/
public void limit(LimitFunction function, BigDecimal n, String limitSortLiteral) {
this.limitFunction = function;
this.limitFunctionN = n;
this.limitFunctionSortLiteral = limitSortLiteral;
}

/**
* Clears the limit parameters of that axis
*/
public void clearLimitFunction() {
this.limitFunction = null;
this.limitFunctionN = null;
this.limitFunctionSortLiteral = null;
}

/**
* @return The {@link LimitFunction}
*/
public LimitFunction getLimitFunction() {
return limitFunction;
}

/**
* @return the number of items/cumulative sum/percentage
* being used by the {@link LimitFunction)}
*/
public BigDecimal getLimitFunctionN() {
return limitFunctionN;
}

/**
* Returns the current sort literal being used by the
* limit functionMight return null of none
* is currently specified.
* @return sort literal of the limit function
*/ public String getLimitFunctionSortLiteral() {
return limitFunctionSortLiteral;
}


}

// End QueryAxis.java
Expand Down
97 changes: 97 additions & 0 deletions testsrc/org/olap4j/OlapTest.java
Expand Up @@ -27,6 +27,7 @@

import junit.framework.TestCase;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;

Expand Down Expand Up @@ -1428,6 +1429,102 @@ public void testMultipleHierarchyConsistency() throws Exception {
resultsString);
query.validate();
}
public void testLimitFunction() throws Exception {
Cube cube = getFoodmartCube("Sales");
if (cube == null) {
fail("Could not find Sales cube");
}
// Setup a base query.
Query query = new Query("my query", cube);
QueryDimension productDimension = query.getDimension("Product");
NamedList<Level> productLevels =
productDimension.getDimension()
.getDefaultHierarchy().getLevels();

Level productLevel = productLevels.get("Product Category");
productDimension.include(productLevel);

QueryDimension measuresDimension = query.getDimension("Measures");
measuresDimension.include(nameList("Measures", "Sales Count"));

query.getAxis(Axis.ROWS).addDimension(productDimension);
query.getAxis(Axis.COLUMNS).addDimension(measuresDimension);

query.getAxis(Axis.ROWS).topCount(new BigDecimal(6));

query.validate();

// Validate the generated MDX
String mdxString = query.getSelect().toString();
TestContext.assertEqualsVerbose(
"SELECT\n"
+ "{[Measures].[Sales Count]} ON COLUMNS,\n"
+ "TopCount({[Product].[Product].[Product Category].Members}, 6) ON ROWS\n"
+ "FROM [Sales]",
mdxString);

// Validate the returned results
CellSet results = query.execute();
String resultsString = TestContext.toString(results);
TestContext.assertEqualsVerbose(
"Axis #0:\n"
+ "{}\n"
+ "Axis #1:\n"
+ "{[Measures].[Sales Count]}\n"
+ "Axis #2:\n"
+ "{[Product].[Product].[Drink].[Alcoholic Beverages].[Beer and Wine]}\n"
+ "{[Product].[Product].[Drink].[Beverages].[Carbonated Beverages]}\n"
+ "{[Product].[Product].[Drink].[Beverages].[Drinks]}\n"
+ "{[Product].[Product].[Drink].[Beverages].[Hot Beverages]}\n"
+ "{[Product].[Product].[Drink].[Beverages].[Pure Juice Beverages]}\n"
+ "{[Product].[Product].[Drink].[Dairy].[Dairy]}\n"
+ "Row #0: 2,219\n"
+ "Row #1: 1,107\n"
+ "Row #2: 798\n"
+ "Row #3: 1,391\n"
+ "Row #4: 1,096\n"
+ "Row #5: 1,367\n",
resultsString);

query.getAxis(Axis.ROWS).limit(
LimitFunction.TopCount,
new BigDecimal(6),
"[Measures].[Sales Count]");

query.validate();

// Validate the generated MDX
mdxString = query.getSelect().toString();
TestContext.assertEqualsVerbose(
"SELECT\n"
+ "{[Measures].[Sales Count]} ON COLUMNS,\n"
+ "TopCount({[Product].[Product].[Product Category].Members}, 6, [Measures].[Sales Count]) ON ROWS\n"
+ "FROM [Sales]",
mdxString);

// Validate the returned results
results = query.execute();
resultsString = TestContext.toString(results);
TestContext.assertEqualsVerbose(
"Axis #0:\n"
+ "{}\n"
+ "Axis #1:\n"
+ "{[Measures].[Sales Count]}\n"
+ "Axis #2:\n"
+ "{[Product].[Product].[Food].[Snack Foods].[Snack Foods]}\n"
+ "{[Product].[Product].[Food].[Produce].[Vegetables]}\n"
+ "{[Product].[Product].[Food].[Dairy].[Dairy]}\n"
+ "{[Product].[Product].[Food].[Baking Goods].[Jams and Jellies]}\n"
+ "{[Product].[Product].[Food].[Produce].[Fruit]}\n"
+ "{[Product].[Product].[Food].[Deli].[Meat]}\n"
+ "Row #0: 9,957\n"
+ "Row #1: 6,751\n"
+ "Row #2: 4,189\n"
+ "Row #3: 3,868\n"
+ "Row #4: 3,836\n"
+ "Row #5: 3,064\n",
resultsString);
}
public void testHierarchyConsistency() throws Exception {
Cube cube = getFoodmartCube("Sales");
if (cube == null) {
Expand Down

0 comments on commit d07acc9

Please sign in to comment.