[#1276] Simulate Oracle's LISTAGG() in DB2 using XMLAGG(), SUBSTR() and CONCAT()

This commit is contained in:
Lukas Eder 2012-04-06 19:57:49 +00:00
parent ca32a0fa39
commit 003344ef32
6 changed files with 73 additions and 10 deletions

View File

@ -734,7 +734,6 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
public void testListAgg() throws Exception {
switch (getDialect()) {
case ASE:
case DB2:
case DERBY:
case INGRES:
case POSTGRES:
@ -773,6 +772,7 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
switch (getDialect()) {
case CUBRID:
case DB2:
case H2:
case HSQLDB:
case MYSQL:

View File

@ -36,6 +36,7 @@
package org.jooq;
import static org.jooq.SQLDialect.CUBRID;
import static org.jooq.SQLDialect.DB2;
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.HSQLDB;
import static org.jooq.SQLDialect.MYSQL;
@ -58,18 +59,18 @@ public interface GroupConcatOrderByStep extends GroupConcatSeparatorStep {
/**
* Add an <code>ORDER BY</code> clause to the query
*/
@Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
@Support({ CUBRID, DB2, 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 })
@Support({ CUBRID, DB2, 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 })
@Support({ CUBRID, DB2, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
GroupConcatSeparatorStep orderBy(Collection<SortField<?>> fields);
}

View File

@ -36,6 +36,7 @@
package org.jooq;
import static org.jooq.SQLDialect.CUBRID;
import static org.jooq.SQLDialect.DB2;
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.HSQLDB;
import static org.jooq.SQLDialect.MYSQL;
@ -55,6 +56,6 @@ public interface GroupConcatSeparatorStep extends AggregateFunction<String> {
/**
* Specify the separator on the <code>GROUP_CONCAT</code> function
*/
@Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
@Support({ CUBRID, DB2, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
AggregateFunction<String> separator(String separator);
}

View File

@ -4254,6 +4254,7 @@ public class Factory implements FactoryOperations {
* by the following dialects:
* <ul>
* <li> {@link SQLDialect#CUBRID}: Using <code>GROUP_CONCAT()</code></li>
* <li> {@link SQLDialect#DB2}: Using <code>XMLAGG()</code></li>
* <li> {@link SQLDialect#H2}: Using <code>GROUP_CONCAT()</code></li>
* <li> {@link SQLDialect#HSQLDB}: Using <code>GROUP_CONCAT()</code></li>
* <li> {@link SQLDialect#MYSQL}: Using <code>GROUP_CONCAT()</code></li>
@ -4262,7 +4263,7 @@ public class Factory implements FactoryOperations {
*
* @see #groupConcat(Field)
*/
@Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
@Support({ CUBRID, DB2, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
public static OrderedAggregateFunction<String> listAgg(Field<?> field) {
return new Function<String>(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field));
}
@ -4274,6 +4275,7 @@ public class Factory implements FactoryOperations {
* by the following dialects:
* <ul>
* <li> {@link SQLDialect#CUBRID}: Using <code>GROUP_CONCAT</code></li>
* <li> {@link SQLDialect#DB2}: Using <code>XMLAGG()</code></li>
* <li> {@link SQLDialect#H2}: Using <code>GROUP_CONCAT</code></li>
* <li> {@link SQLDialect#HSQLDB}: Using <code>GROUP_CONCAT</code></li>
* <li> {@link SQLDialect#MYSQL}: Using <code>GROUP_CONCAT</code></li>
@ -4282,7 +4284,7 @@ public class Factory implements FactoryOperations {
*
* @see #groupConcat(Field, String)
*/
@Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
@Support({ CUBRID, DB2, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
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);
@ -4301,13 +4303,14 @@ public class Factory implements FactoryOperations {
* <p>
* It is simulated by the following dialects:
* <ul>
* <li> {@link SQLDialect#DB2}: Using <code>XMLAGG()</code></li>
* <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 })
@Support({ CUBRID, DB2, H2, HSQLDB, MYSQL, ORACLE, SYBASE })
public static GroupConcatOrderByStep groupConcat(Field<?> field) {
return new GroupConcat(nullSafe(field));
}

View File

@ -38,6 +38,7 @@ package org.jooq.impl;
import static java.util.Arrays.asList;
import static org.jooq.SQLDialect.CUBRID;
import static org.jooq.SQLDialect.DB2;
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.HSQLDB;
import static org.jooq.SQLDialect.MYSQL;
@ -66,6 +67,7 @@ import org.jooq.WindowOverStep;
import org.jooq.WindowPartitionByStep;
import org.jooq.WindowRowsAndStep;
import org.jooq.WindowRowsStep;
import org.jooq.util.db2.DB2DataType;
/**
* A field that handles built-in functions, aggregate functions, and window
@ -167,14 +169,15 @@ class Function<T> extends AbstractField<T> implements
@Override
public final void toSQL(RenderContext context) {
context.sql(getFNName(context.getDialect()));
if (term == LIST_AGG && asList(CUBRID, H2, HSQLDB, MYSQL).contains(context.getDialect())) {
toSQLGroupConcat(context);
}
else if (term == LIST_AGG && asList(SYBASE).contains(context.getDialect())) {
toSQLList(context);
}
else if (term == LIST_AGG && asList(DB2).contains(context.getDialect())) {
toSQLXMLAGG(context);
}
else {
toSQLArguments(context);
toSQLWithinGroupClause(context);
@ -182,10 +185,58 @@ class Function<T> extends AbstractField<T> implements
}
}
/**
* [#1276] <code>LIST_AGG</code> simulation for DB2
*/
private void toSQLXMLAGG(RenderContext context) {
// This is a complete view of what the below SQL will render
// substr(xmlserialize(xmlagg(xmltext(concat(', ', title)) order by id) as varchar(1024)), 3)
if (arguments.size() > 1) {
context.keyword("substr(");
}
context.keyword("xmlserialize(xmlagg(xmltext(");
if (arguments.size() > 1) {
context.keyword("concat(")
.sql(arguments.get(1))
.sql(", ");
}
context.sql(arguments.get(0));
if (arguments.size() > 1) {
context.sql(")"); // CONCAT
}
context.sql(")"); // XMLTEXT
if (!withinGroupOrderBy.isEmpty()) {
context.keyword(" order by ")
.sql(withinGroupOrderBy);
}
context.sql(")"); // XMLAGG
context.keyword(" as ");
context.sql(DB2DataType.VARCHAR.getCastTypeName());
context.sql(")"); // XMLSERIALIZE
if (arguments.size() > 1) {
context.sql(", ");
// The separator is of this form: [', '].
// The example has length 4
context.sql(arguments.get(1).toString().length() - 1);
context.sql(")"); // SUBSTR
}
}
/**
* [#1275] <code>LIST_AGG</code> simulation for CUBRID, HSQLDB, MySQL
*/
private void toSQLList(RenderContext context) {
context.sql(getFNName(context.getDialect()));
context.sql("(");
if (distinct) {
@ -206,6 +257,7 @@ class Function<T> extends AbstractField<T> implements
* [#1273] <code>LIST_AGG</code> simulation for MySQL and CUBRID
*/
private final void toSQLGroupConcat(RenderContext context) {
context.sql(getFNName(context.getDialect()));
context.sql("(");
if (distinct) {
@ -308,6 +360,7 @@ class Function<T> extends AbstractField<T> implements
* Render function arguments and argument modifiers
*/
private final void toSQLArguments(RenderContext context) {
context.sql(getFNName(context.getDialect()));
context.sql("(");
if (distinct) {

View File

@ -110,6 +110,11 @@ enum Term {
case MYSQL:
return "group_concat";
// DB2 needs to do some rather complex XML manipulation to
// achieve the same results. XMLAGG() itself cannot do it
case DB2:
return "xmlagg";
case ORACLE:
return "listagg";