In theory the CTE itself is the same like a table jOOQ would generate columns for, so I as well really created one field per column per CTE.
The only alternative coming into my mind is outsourcing the individual SELECT-statements into a view, for which jOOQ would create classes etc. automatically again. But doing this for each and every CTE for each and every report in question doesn't feel that right as well. Every change in any CTE would involve a change in the database schema, where it seems far easier to change Java-code instead.
The good thing on creating fields and stuff manually for all the CTEs
is that types are available everywhere needed and one doesn't need to
handle that within the query itself. Like in "cte.field(...)" one can
simply reference an already available field definition and jOOQ takes
that name, type etc. At other places, only the name of that field
could be used as needed.
So in my opinion things break down to if anything from the code
generator is used or not. If so, one wouldn't want to deal with plain
strings anymore at any place and express everything using names,
fields etc. from jOOQ instead I guess.
Even without using results from the code generator, using jOOQ instead
of plain SQL make things easier readable using methods and
Java-indentation than embedding complex strings in Java. Therefore I
thought of supporting SQL in pre-formatted files and those might be
forwarded to the jOOQ-parser then. But inlining anything else than
trivial SQL in Java doesn't feel right anymore.
How to handle the necessary CustomRecord? Does it need to implement
RecordX-interfaces with concrete types as well? Looks the following
currently:
> @SuppressWarnings("serial")
> static class TimePeriodRec extends CustomRecord<TimePeriodRec>
> {
> protected TimePeriodRec(Table<TimePeriodRec> table)
> {
> super(table);
> }
> }
Does it need to be like the following instead?
> @SuppressWarnings("serial")
> static class TimePeriodRec extends CustomRecord<TimePeriodRec> implements Record2<OffsetDateTime, OffsetDateTime>
That would be what the generator does:
> public class CltCmdRecord extends UpdatableRecordImpl<CltCmdRecord> implements Record8<[...]> {
Or is it about simply adding getters like the generator adds?
> /**
> * Getter for <code>clt_cmd.id</code>.
> */
> public Integer getId() {
> return (Integer) get(0);
> }
The second question is, how do I get an actual CTE using CustomTable?
I thought that I might be able to use the table instance where I use
the CTE, but that doesn't seem to be the case. Instead, I still need
to work with CTE-names and fields manually:
> return DSL.name(TIME_PERIOD.getName())
> .fields(TIME_PERIOD.START_AT.getName(),
> TIME_PERIOD.END_AT .getName())
> .as(DSL.selectFrom(DSL.values(DSL.row(startAt, endAt))));
vs. before:
> return TIME_PERIOD.NAME
> .fields(TIME_PERIOD.COL_START_AT.getName(),
> TIME_PERIOD.COL_END_AT .getName())
> .as(DSL.selectFrom(DSL.values(DSL.row(startAt, endAt))));
I thought I could save especially the ".fields(...)"-thing after
reading your following statement:
> The main benefit is that your tables will know their fields, and you don't
> have to register them again.
But didn't get it to work and looking at the docs, CTEs can only be
created by names etc. I don't see how to come from a table to a CTE.
So, by using CustomTable it seems I only follow the approach of the
code generator, but it actually doesn't make creating CTEs themself
easier?
> No, but it makes working with them easier, as they now behave like ordinary
> tables / views.
I still don't get it: There's no place where I can interchange CTEs
and my custom tables, only reuse individual components.
CommonTableExpression<Record8<Integer, String, String, Long, OffsetDateTime, MeterMfctCode, String, MeterType>> recsInTimePeriod = this.cteRecsInTimePeriod();
recsInTimePeriod.field(CteDefs.RecsInTimePeriod.COL_METER_MFCT_CODE),
private CommonTableExpression<Record7<MeterMfctCode, String, MeterType, String, String, Integer, OffsetDateTime>>
cteReAndCltPerMeterLid()
{
CommonTableExpression<Record8<Integer, String, String, Long, OffsetDateTime, MeterMfctCode, String, MeterType>> recsInTimePeriod = this.cteRecsInTimePeriod();
// Still need to do some trickery when declaring the CTE
return CteDefs.ReAndCltPerMeterLid.CTE
.fields
(
CteDefs.RE_AND_CLT_PER_METER_LID.COL_METER_MFCT_CODE.getName(),
CteDefs.RE_AND_CLT_PER_METER_LID.COL_METER_READING_SERIAL.getName(),
CteDefs.RE_AND_CLT_PER_METER_LID.COL_METER_TYPE.getName(),
CteDefs.RE_AND_CLT_PER_METER_LID.COL_REAL_ESTATE_NR.getName(),
CteDefs.RE_AND_CLT_PER_METER_LID.COL_CLT_MAC.getName(),
CteDefs.RE_AND_CLT_PER_METER_LID.COL_METER_LID_CNT.getName(),
CteDefs.RE_AND_CLT_PER_METER_LID.COL_METER_LID_LAST_CAPTURED_AT.getName()
)
.as
(
// But when using the CTE, it looks like any ordinary SQL
DSL.select
(
RECS_IN_TIME_PERIOD.COL_METER_MFCT_CODE,
RECS_IN_TIME_PERIOD.COL_METER_READING_SERIAL,
RECS_IN_TIME_PERIOD.COL_METER_TYPE,
RECS_IN_TIME_PERIOD.COL_REAL_ESTATE_NR,
RECS_IN_TIME_PERIOD.COL_CLT_MAC,
DSL.count().as(RE_AND_CLT_PER_METER_LID.COL_METER_LID_CNT),
DSL
.max(RECS_IN_TIME_PERIOD.COL_CLT_REC_CAPTURED_AT))
.as(RE_AND_CLT_PER_METER_LID.COL_METER_LID_LAST_CAPTURED_AT)
)
.from(RECS_IN_TIME_PERIOD)
.groupBy
(
RECS_IN_TIME_PERIOD.COL_METER_MFCT_CODE),
RECS_IN_TIME_PERIOD.COL_METER_READING_SERIAL),
RECS_IN_TIME_PERIOD.COL_METER_TYPE),
RECS_IN_TIME_PERIOD.COL_REAL_ESTATE_NR),
RECS_IN_TIME_PERIOD.COL_CLT_MAC)
)
);
}
Or am I understanding your suggestion wrongly and I need to really
implement CTE-SQL on my custom tables somehow? Like is done in the
docs for custom fields, but I can't override "accept".
https://www.jooq.org/doc/latest/manual/sql-building/queryparts/custom-queryparts/
Guten Tag Thorsten Schöning,
am Freitag, 31. Januar 2020 um 19:06 schrieben Sie:
> I tried following the approach jOOQ is doing by simply creating
> classes with fields for each CTE and their columns.[...]
One can get exactly what jOOQ does by using the code generator with a
schema for a XMLDatabase:
https://www.jooq.org/doc/latest/manual/code-generation/codegen-xml/
That schema would need to describe the name of the CTE and its columns
and jOOQ would simply generate code for that like it does with tables
from a database. In other messages of this thread, Lukas Eder
suggested to implement CustomTable instead of using a class with names
and fields only like I did, so using the generator directly is pretty
close.
Managing all those different tables isn't that easy currently: Things
are tables for the generator, so classes are put into packages like
"[...].ctes.tables[.records]". While one might prefer something like
"[...].ctes[.records]" directly only. That way one might mix things
into other jOOQ-packages, like in the following example:
> [...].db.jooq
> [...].db.jooq.ctes
> [...].db.jooq.ctes.tables
> [...].db.jooq.ctes.tables.records
> [...].db.jooq.enums
> [...].db.jooq.tables
> [...].db.jooq.tables.record
I'm attaching my generated files and their config for references,
maybe someone finds that approach interesting.
Guten Tag Lukas Eder,
am Freitag, 7. Februar 2020 um 15:37 schrieben Sie:
> Does this make sense?
Absolutely, thanks! :-) You only have my old code, already changed it
to what you suggested right now. BUT: Not with using a custom table,
but my former approach with individual, unrelated fields only.
Would be great if you could have an additional look and tell me if
my current approach should work or doesn't make any sense. Didn't
execute anything currently.
I had the feeling that my current implementation should work as well,
because jOOQ simply renders given field names and as long those are
available by some CTE, things will simply work. Or do I really NEED
custom tables like in your former suggestion?
Because what I have now
is what I want, especially after reading your suggestion, and if I
need custom tables to get that, I will change my definitions to custom
tables.