[#1273] Simulate GROUP_CONCAT() aggregate function using Oracle's LISTAGG() function, where available

This commit is contained in:
Lukas Eder 2012-04-06 19:24:46 +00:00
parent 8a5d3ae639
commit ca32a0fa39
6 changed files with 325 additions and 6 deletions

View File

@ -48,6 +48,7 @@ import static org.jooq.impl.Factory.countDistinct;
import static org.jooq.impl.Factory.cumeDist;
import static org.jooq.impl.Factory.denseRank;
import static org.jooq.impl.Factory.firstValue;
import static org.jooq.impl.Factory.groupConcat;
import static org.jooq.impl.Factory.lag;
import static org.jooq.impl.Factory.lead;
import static org.jooq.impl.Factory.listAgg;
@ -746,7 +747,13 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
Result<?> result1 = create().select(
TAuthor_FIRST_NAME(),
TAuthor_LAST_NAME(),
listAgg(TBook_ID(), ", ").withinGroupOrderBy(TBook_ID().desc()).as("books"))
listAgg(TBook_ID(), ", ")
.withinGroupOrderBy(TBook_ID().desc())
.as("books1"),
groupConcat(TBook_ID())
.orderBy(TBook_ID().desc())
.separator(", ")
.as("books2"))
.from(TAuthor())
.join(TBook()).on(TAuthor_ID().equal(TBook_AUTHOR_ID()))
.groupBy(
@ -759,8 +766,10 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
assertEquals(2, result1.size());
assertEquals(AUTHOR_FIRST_NAMES, result1.getValues(TAuthor_FIRST_NAME()));
assertEquals(AUTHOR_LAST_NAMES, result1.getValues(TAuthor_LAST_NAME()));
assertEquals("2, 1", result1.getValue(0, "books"));
assertEquals("4, 3", result1.getValue(1, "books"));
assertEquals("2, 1", result1.getValue(0, "books1"));
assertEquals("2, 1", result1.getValue(0, "books2"));
assertEquals("4, 3", result1.getValue(1, "books1"));
assertEquals("4, 3", result1.getValue(1, "books2"));
switch (getDialect()) {
case CUBRID:

View File

@ -0,0 +1,75 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "jOOQ" 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.
*/
package org.jooq;
import static org.jooq.SQLDialect.CUBRID;
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.HSQLDB;
import static org.jooq.SQLDialect.MYSQL;
import static org.jooq.SQLDialect.ORACLE;
import static org.jooq.SQLDialect.SYBASE;
import java.util.Collection;
import org.jooq.impl.Factory;
/**
* MySQL's <code>GROUP_CONCAT</code> function.
*
* @author Lukas Eder
* @see Factory#listAgg(Field)
*/
public interface GroupConcatOrderByStep extends GroupConcatSeparatorStep {
/**
* Add an <code>ORDER BY</code> clause to the query
*/
@Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
GroupConcatSeparatorStep orderBy(Field<?>... fields);
/**
* Add an <code>ORDER BY</code> clause to the query
*/
@Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
GroupConcatSeparatorStep orderBy(SortField<?>... fields);
/**
* Add an <code>ORDER BY</code> clause to the query
*/
@Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
GroupConcatSeparatorStep orderBy(Collection<SortField<?>> fields);
}

View File

@ -0,0 +1,60 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "jOOQ" 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.
*/
package org.jooq;
import static org.jooq.SQLDialect.CUBRID;
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.HSQLDB;
import static org.jooq.SQLDialect.MYSQL;
import static org.jooq.SQLDialect.ORACLE;
import static org.jooq.SQLDialect.SYBASE;
import org.jooq.impl.Factory;
/**
* MySQL's <code>GROUP_CONCAT</code> function.
*
* @author Lukas Eder
* @see Factory#listAgg(Field)
*/
public interface GroupConcatSeparatorStep extends AggregateFunction<String> {
/**
* Specify the separator on the <code>GROUP_CONCAT</code> function
*/
@Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
AggregateFunction<String> separator(String separator);
}

View File

@ -94,6 +94,7 @@ import org.jooq.ExecuteListener;
import org.jooq.FactoryOperations;
import org.jooq.Field;
import org.jooq.FieldProvider;
import org.jooq.GroupConcatOrderByStep;
import org.jooq.Insert;
import org.jooq.InsertQuery;
import org.jooq.InsertSetStep;
@ -4258,6 +4259,8 @@ public class Factory implements FactoryOperations {
* <li> {@link SQLDialect#MYSQL}: Using <code>GROUP_CONCAT()</code></li>
* <li> {@link SQLDialect#SYBASE}: Using <code>LIST()</code></li>
* </ul>
*
* @see #groupConcat(Field)
*/
@Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
public static OrderedAggregateFunction<String> listAgg(Field<?> field) {
@ -4276,13 +4279,62 @@ public class Factory implements FactoryOperations {
* <li> {@link SQLDialect#MYSQL}: Using <code>GROUP_CONCAT</code></li>
* <li> {@link SQLDialect#SYBASE}: Using <code>LIST()</code></li>
* </ul>
*
* @see #groupConcat(Field, String)
*/
@Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
public static OrderedAggregateFunction<String> listAgg(Field<?> field, String delimiter) {
Field<String> literal = literal("'" + delimiter.replace("'", "''") + "'");
public static OrderedAggregateFunction<String> listAgg(Field<?> field, String separator) {
Field<String> literal = literal("'" + separator.replace("'", "''") + "'");
return new Function<String>(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field), literal);
}
/**
* Get the aggregated concatenation for a field.
* <p>
* This is natively supported by
* <ul>
* <li> {@link SQLDialect#CUBRID}</li>
* <li> {@link SQLDialect#H2}</li>
* <li> {@link SQLDialect#HSQLDB}</li>
* <li> {@link SQLDialect#MYSQL}</li>
* </ul>
* <p>
* It is simulated by the following dialects:
* <ul>
* <li> {@link SQLDialect#ORACLE}: Using <code>LISTAGG()</code></li>
* <li> {@link SQLDialect#SYBASE}: Using <code>LIST()</code></li>
* </ul>
*
* @see #listAgg(Field)
*/
@Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
public static GroupConcatOrderByStep groupConcat(Field<?> field) {
return new GroupConcat(nullSafe(field));
}
/**
* Get the aggregated concatenation for a field.
* <p>
* This is natively supported by
* <ul>
* <li> {@link SQLDialect#CUBRID}</li>
* <li> {@link SQLDialect#H2}</li>
* <li> {@link SQLDialect#HSQLDB}</li>
* <li> {@link SQLDialect#MYSQL}</li>
* </ul>
* <p>
* It is simulated by the following dialects:
* <ul>
* <li> {@link SQLDialect#SYBASE}: Using <code>LIST()</code></li>
* </ul>
*
* @see #listAgg(Field)
*/
@Support({ CUBRID, H2, HSQLDB, MYSQL, SYBASE })
public static GroupConcatOrderByStep groupConcatDistinct(Field<?> field) {
return new GroupConcat(nullSafe(field), true);
}
// -------------------------------------------------------------------------
// XXX Window functions
// -------------------------------------------------------------------------

View File

@ -75,9 +75,11 @@ import org.jooq.WindowRowsStep;
*/
class Function<T> extends AbstractField<T> implements
// Cascading interface implementations for aggregate and window function behaviour
// Cascading interface implementations for aggregate function behaviour
OrderedAggregateFunction<T>,
AggregateFunction<T>,
// and for window function behaviour
WindowIgnoreNullsStep<T>,
WindowPartitionByStep<T>,
WindowRowsStep<T>,

View File

@ -0,0 +1,121 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "jOOQ" 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.
*/
package org.jooq.impl;
import static org.jooq.impl.Factory.literal;
import java.util.Arrays;
import java.util.Collection;
import org.jooq.AggregateFunction;
import org.jooq.Configuration;
import org.jooq.Field;
import org.jooq.GroupConcatOrderByStep;
import org.jooq.GroupConcatSeparatorStep;
import org.jooq.SortField;
import org.jooq.WindowPartitionByStep;
/**
* @author Lukas Eder
*/
class GroupConcat extends AbstractFunction<String> implements GroupConcatOrderByStep {
/**
* Generated UID
*/
private static final long serialVersionUID = -6884415527559632960L;
private final Field<?> field;
private final boolean distinct;
private final SortFieldList orderBy;
private String separator;
GroupConcat(Field<?> field) {
this(field, false);
}
GroupConcat(Field<?> field, boolean distinct) {
super("group_concat", SQLDataType.VARCHAR);
this.field = field;
this.distinct = distinct;
this.orderBy = new SortFieldList();
}
@Override
final Field<String> getFunction0(Configuration configuration) {
Function<String> result;
if (separator == null) {
result = new Function<String>(Term.LIST_AGG, distinct, SQLDataType.VARCHAR, field);
}
else {
Field<String> literal = literal("'" + separator.replace("'", "''") + "'");
result = new Function<String>(Term.LIST_AGG, distinct, SQLDataType.VARCHAR, field, literal);
}
return result.withinGroupOrderBy(orderBy);
}
@Override
public final WindowPartitionByStep<String> over() {
throw new UnsupportedOperationException("OVER() not supported on GROUP_CONCAT aggregate function");
}
@Override
public final AggregateFunction<String> separator(String s) {
this.separator = s;
return this;
}
@Override
public final GroupConcatSeparatorStep orderBy(Field<?>... fields) {
orderBy.addAll(fields);
return this;
}
@Override
public final GroupConcatSeparatorStep orderBy(SortField<?>... fields) {
orderBy.addAll(Arrays.asList(fields));
return this;
}
@Override
public final GroupConcatSeparatorStep orderBy(Collection<SortField<?>> fields) {
orderBy.addAll(fields);
return this;
}
}