]> source.dussan.org Git - iciql.git/commitdiff
Add methods to return group by field counts
authorJames Moger <james.moger@gitblit.com>
Wed, 6 Apr 2016 16:28:32 +0000 (12:28 -0400)
committerJames Moger <james.moger@gitblit.com>
Wed, 6 Apr 2016 16:28:32 +0000 (12:28 -0400)
CHANGELOG.md
src/main/java/com/iciql/Query.java
src/main/java/com/iciql/QueryWhere.java
src/main/java/com/iciql/ValueCount.java [new file with mode: 0644]
src/test/java/com/iciql/test/ModelsTest.java
src/test/java/com/iciql/test/PrimitivesTest.java
src/test/java/com/iciql/test/models/PrimitivesModel.java

index 4c90f092b11ae4a76e6be28a0c469037c64a6f25..5bca337079ee0ab822fdff5a8c8b617532606560 100644 (file)
@@ -2,11 +2,9 @@
 All notable changes to this project will be documented in this file.
 This project adheres to [Semantic Versioning](http://semver.org/).
 
-### [Unreleased][unreleased]
-#### Fixed
-#### Changed
+### [2.1.0][unreleased]
 #### Added
-#### Removed
+- Add methods to select counts of a group by field (*SELECT field, COUNT(*) FROM table [WHERE conditions] GROUP BY field*)
 
 ### [2.0.0] - 2016-04-04
 
index d26e7b7768097a51a722f4dfcc5617799345b23f..20d5520ef39a49e57025f92657caf2d583bc2c3e 100644 (file)
@@ -31,6 +31,7 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -100,6 +101,56 @@ public class Query<T> {
         }
     }
 
+    public <X> List<ValueCount<X>> selectCount(X x) {
+        return selectCount(x, false);
+    }
+
+    public <X> List<ValueCount<X>> selectCountDesc(X x) {
+        return selectCount(x, true);
+    }
+
+    <X> List<ValueCount<X>> selectCount(X x, boolean desc) {
+        List<ValueCount<X>> list = Utils.newArrayList();
+        SelectColumn<T> col = getColumnByReference(x);
+        X alias = x;
+        if (col == null) {
+            alias = getPrimitiveAliasByValue(x);
+            col = getColumnByReference(alias);
+        }
+        if (col == null) {
+            throw new IciqlException("Unmapped column reference!");
+        }
+        groupBy(alias);
+
+        SQLStatement stat = getSelectStatement(false);
+        col.appendSQL(stat);
+        stat.appendSQL(", COUNT(*)");
+        appendFromWhere(stat);
+
+        ResultSet rs = stat.executeQuery();
+        Class<? extends DataTypeAdapter<?>> typeAdapter = col.getFieldDefinition().typeAdapter;
+        Class<?> clazz = x.getClass();
+        try {
+            // SQLite returns pre-closed ResultSets for query results with 0 rows
+            if (!rs.isClosed()) {
+                while (rs.next()) {
+                    X value = (X) db.getDialect().deserialize(rs, 1, clazz, typeAdapter);
+                    long count = rs.getLong(2);
+                    list.add(new ValueCount<X>(value, count));
+                }
+            }
+            Collections.sort(list);
+            if (desc) {
+                Collections.reverse(list);
+            }
+        } catch (Exception e) {
+            throw IciqlException.fromSQL(stat.getSQL(), e);
+        } finally {
+            JdbcUtils.closeSilently(rs, true);
+        }
+        return list;
+    }
+
     public List<T> select() {
         return select(false);
     }
index 16f1397c0939673a7f03139204eb853149a3fadf..de6ecd4f405e8e5a07890af12488dee46e2ab820 100644 (file)
@@ -605,4 +605,12 @@ public class QueryWhere<T> {
         return query.selectCount();\r
     }\r
 \r
+    public <X> List<ValueCount<X>> selectCount(X x) {\r
+        return query.selectCount(x, false);\r
+    }\r
+\r
+    public <X> List<ValueCount<X>> selectCountDesc(X x) {\r
+        return query.selectCount(x, true);\r
+    }\r
+\r
 }\r
diff --git a/src/main/java/com/iciql/ValueCount.java b/src/main/java/com/iciql/ValueCount.java
new file mode 100644 (file)
index 0000000..dfc8570
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2016 James Moger.
+ *
+ * Licensed 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 com.iciql;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class ValueCount<X> implements Serializable, Comparable<ValueCount<X>> {
+
+    private final static long serialVersionUID = 1L;
+
+    private final X value;
+    private final long count;
+
+    ValueCount(X x, long count) {
+        this.value = x;
+        this.count = count;
+    }
+
+    public X getValue() {
+        return value;
+    }
+
+    public long getCount() {
+        return count;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s=%d", value, count);
+    }
+
+    @Override
+    public int compareTo(ValueCount<X> o) {
+        return (count < o.count) ? -1 : ((count == o.count) ? compareValue(o.value) : 1);
+    }
+
+    private int compareValue(X anotherValue) {
+        if (value == null && anotherValue == null) {
+            return 0;
+        } else if (value == null) {
+            return 1;
+        } else if (anotherValue == null) {
+            return -1;
+        }
+
+        if (Number.class.isAssignableFrom(value.getClass())) {
+            long n1 = ((Number) value).longValue();
+            long n2 = ((Number) anotherValue).longValue();
+            return (n1 < n2) ? -1 : ((n1 == n2) ? 0 : 1);
+        } else if (Date.class.isAssignableFrom(value.getClass())) {
+            return ((Date) value).compareTo((Date) anotherValue);
+        }
+
+        return value.toString().compareToIgnoreCase(anotherValue.toString());
+    }
+}
+
index 742ed536c291c4b57c48aa33e9346720fa9f15e3..96a6e4ae5bdfecdbd377555364f6d30f86bb587f 100644 (file)
@@ -19,6 +19,7 @@ package com.iciql.test;
 
 import com.iciql.Db;
 import com.iciql.DbInspector;
+import com.iciql.ValueCount;
 import com.iciql.ValidationRemark;
 import com.iciql.test.models.Product;
 import com.iciql.test.models.ProductAnnotationOnly;
@@ -174,4 +175,47 @@ public class ModelsTest {
         assertEquals(10, ids.size());
         assertEquals("[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]", ids.toString());
     }
+
+    @Test
+    public void testGroupByCount() {
+        Product products = new Product();
+
+        List<ValueCount<String>> categories = db.from(products)
+                .selectCount(products.category);
+        assertEquals(5, categories.size());
+        assertEquals("[Meat/Poultry=1, Produce=1, Seafood=1, Beverages=2, Condiments=5]", categories.toString());
+    }
+
+    @Test
+    public void testGroupByCountDesc() {
+        Product products = new Product();
+
+        List<ValueCount<String>> categories = db.from(products)
+                .selectCountDesc(products.category);
+        assertEquals(5, categories.size());
+        assertEquals("[Condiments=5, Beverages=2, Seafood=1, Produce=1, Meat/Poultry=1]", categories.toString());
+    }
+
+    @Test
+    public void testFilteredGroupByCount() {
+        Product products = new Product();
+
+        List<ValueCount<String>> categories = db.from(products)
+                .where(products.category).isNot("Seafood")
+                .selectCount(products.category);
+        assertEquals(4, categories.size());
+        assertEquals("[Meat/Poultry=1, Produce=1, Beverages=2, Condiments=5]", categories.toString());
+    }
+
+    @Test
+    public void testFilteredGroupByCountDesc() {
+        Product products = new Product();
+
+        List<ValueCount<String>> categories = db.from(products)
+                .where(products.category).isNot("Seafood")
+                .selectCountDesc(products.category);
+        assertEquals(4, categories.size());
+        assertEquals("[Condiments=5, Beverages=2, Produce=1, Meat/Poultry=1]", categories.toString());
+    }
+
 }
index 49595a209b8dfb35b38a289221b7763777c7319e..db36be06148c9eaf9b993e4438a7ffb90e95fa5e 100644 (file)
@@ -18,6 +18,7 @@ package com.iciql.test;
 \r
 import com.iciql.Db;\r
 import com.iciql.IciqlException;\r
+import com.iciql.ValueCount;\r
 import com.iciql.test.models.MultipleBoolsModel;\r
 import com.iciql.test.models.PrimitivesModel;\r
 import org.junit.Test;\r
@@ -114,4 +115,22 @@ public class PrimitivesTest {
         assertEquals(models.size(), list.size());\r
         assertEquals("[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]", list.toString());\r
     }\r
+\r
+    @Test\r
+    public void testPrimitiveGroupByCount() {\r
+        Db db = IciqlSuite.openNewDb();\r
+\r
+        // insert random models in reverse order\r
+        List<PrimitivesModel> models = PrimitivesModel.getList();\r
+        Collections.reverse(models);\r
+        // insert them in reverse order\r
+        db.insertAll(models);\r
+\r
+        PrimitivesModel primitives = new PrimitivesModel();\r
+        List<ValueCount<Integer>> types = db.from(primitives)\r
+                .selectCount(primitives.typeCode);\r
+        assertEquals(2, types.size());\r
+        assertEquals("[0=5, 1=5]", types.toString());\r
+    }\r
+\r
 }\r
index 078b5652d0278d5f37a62fe165f993dbf6e26c0b..aebf9f63bed56800baf4dcf9757ee63401063f31 100644 (file)
@@ -50,6 +50,9 @@ public class PrimitivesModel {
     @IQColumn\r
     public float myFloat;\r
 \r
+    @IQColumn\r
+    public int typeCode;\r
+\r
     public PrimitivesModel() {\r
         Random rand = new Random();\r
         myLong = rand.nextLong();\r
@@ -78,6 +81,7 @@ public class PrimitivesModel {
         for (int i = 1; i <= 10; i++) {\r
             PrimitivesModel p = new PrimitivesModel();\r
             p.myLong = i;\r
+            p.typeCode = i % 2;\r
             list.add(p);\r
         }\r
         return list;\r