From a25bbeace730f6c181063e75bc672b54ea00eaa4 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Sun, 15 Jan 2012 09:35:11 +0000 Subject: [PATCH] [#1045] Simulate Oracle's PIVOT clause for other dialects using subqueries - added first draft for simulation --- jOOQ/src/main/java/org/jooq/impl/Pivot.java | 210 ++++++++++++++++++-- 1 file changed, 192 insertions(+), 18 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/impl/Pivot.java b/jOOQ/src/main/java/org/jooq/impl/Pivot.java index 46c016e017..06c598d3c2 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Pivot.java +++ b/jOOQ/src/main/java/org/jooq/impl/Pivot.java @@ -35,19 +35,25 @@ */ package org.jooq.impl; +import static org.jooq.impl.Factory.trueCondition; import static org.jooq.impl.Factory.vals; import java.util.Collection; +import java.util.Collections; import java.util.List; import org.jooq.Attachable; import org.jooq.BindContext; +import org.jooq.Condition; +import org.jooq.Configuration; +import org.jooq.Context; import org.jooq.Field; import org.jooq.PivotForStep; import org.jooq.PivotInStep; -import org.jooq.QueryPartInternal; +import org.jooq.QueryPart; import org.jooq.Record; import org.jooq.RenderContext; +import org.jooq.Select; import org.jooq.Table; import org.jooq.exception.DataAccessException; @@ -93,33 +99,201 @@ implements @Override public final void toSQL(RenderContext context) { + context.sql(pivot(context)); + } - // Bind variables are not allowed inside of PIVOT clause - boolean inline = context.inline(); - boolean declare = context.declareFields(); + private Table pivot(Configuration configuration) { + switch (configuration.getDialect()) { - context.sql(table) - .sql(" pivot(") - .inline(true) - .declareFields(true) - .sql(aggregateFunctions) - .sql(" for ") - .literal(on.getName()) - .sql(" in (") - .sql(in) - .declareFields(declare) - .inline(inline) - .sql("))"); + // Oracle has native support for the PIVOT clause + case ORACLE: { + return new OraclePivotTable(); + } + + // Some other dialects can simulate it. This implementation is + // EXPERIMENTAL and not officially supported + default: { + return new DefaultPivotTable(); + } + } + } + + /** + * A simulation of Oracle's PIVOT table + */ + private class DefaultPivotTable extends DialectPivotTable { + + /** + * Generated UID + */ + private static final long serialVersionUID = -5930286639571867314L; + + @Override + public void toSQL(RenderContext context) { + context.declareTables(true) + .sql(select(context)) + .declareTables(false); + } + + @Override + public void bind(BindContext context) throws DataAccessException { + context.declareTables(true) + .bind(select(context)) + .declareTables(false); + } + + private Table select(Context context) { + FieldList groupingFields = new FieldList(); + FieldList aliasedGroupingFields = new FieldList(); + FieldList aggregatedFields = new FieldList(); + + Table pivot = table.as("pivot_outer"); + + // Clearly, the API should be improved to make this more object- + // oriented... + + // This loop finds all fields that are used in aggregate + // functions. They're excluded from the GROUP BY clause + for (Field field : aggregateFunctions) { + if (field instanceof AggregateFunctionImpl) { + for (QueryPart argument : ((AggregateFunctionImpl) field).getArguments()) { + if (argument instanceof Field) { + aggregatedFields.add((Field) argument); + } + } + } + } + + // This loop finds all fields qualify for GROUP BY clauses + for (Field field : table.getFields()) { + if (!aggregatedFields.contains(field)) { + if (!on.equals(field)) { + aliasedGroupingFields.add(pivot.getField(field)); + groupingFields.add(field); + } + } + } + + // The product {aggregateFunctions} x {in} + FieldList aggregationSelects = new FieldList(); + for (Field inField : in) { + for (Field aggregateFunction : aggregateFunctions) { + Condition join = trueCondition(); + + for (Field field : groupingFields) { + join = join.and(join(pivot, field)); + } + + Select aggregateSelect = create(context) + .select(aggregateFunction) + .from(table) + .where(on.equal((Field) inField)) + .and(join); + + aggregationSelects.add(aggregateSelect.asField(inField.getName() + "_" + aggregateFunction.getName())); + } + } + + // This is the complete select + Table select = + create(context).select(aliasedGroupingFields) + .select(aggregationSelects) + .from(pivot) + .where(pivot.getField(on).in(in.toArray(new Field[0]))) + .groupBy(aliasedGroupingFields) + .asTable(); + return select; + } + } + + /** + * The Oracle-specific PIVOT implementation + */ + private class OraclePivotTable extends DialectPivotTable { + + /** + * Generated UID + */ + private static final long serialVersionUID = -3451610306872726958L; + + @Override + public void toSQL(RenderContext context) { + + // Bind variables are not allowed inside of PIVOT clause + boolean inline = context.inline(); + boolean declare = context.declareFields(); + + context.sql(table) + .sql(" pivot(") + .inline(true) + .declareFields(true) + .sql(aggregateFunctions) + .sql(" for ") + .literal(on.getName()) + .sql(" in (") + .sql(in) + .declareFields(declare) + .inline(inline) + .sql("))"); + } + + @Override + public void bind(BindContext context) throws DataAccessException { + context.bind(table); + } + } + + /** + * A base class for dialect-specific implementations of the pivot table + */ + private abstract class DialectPivotTable extends AbstractTable { + + /** + * Generated UID + */ + private static final long serialVersionUID = 2662639259338694177L; + + DialectPivotTable() { + super("pivot"); + } + + @Override + public Class getRecordType() { + return RecordImpl.class; + } + + @Override + public Table as(String as) { + return new TableAlias(this, as); + } + + @Override + protected FieldList getFieldList() { + return Pivot.this.getFieldList(); + } + + @Override + protected List getAttachables0() { + return Collections.emptyList(); + } + } + + + /** + * Extracted method for type-safety + */ + private Condition join(Table pivot, Field field) { + return field.equal(pivot.getField(field)); } @Override public final boolean declaresTables() { - return table.internalAPI(QueryPartInternal.class).declaresTables(); + return true; } @Override public final void bind(BindContext context) throws DataAccessException { - context.bind(table); + context.bind(pivot(context)); } @Override