New versions: Dev (3.15) | Latest (3.14) | 3.13 | 3.12 | 3.11 | 3.10 | 3.9 | 3.8 | Old versions: 3.7 | 3.6 | 3.5 | 3.4 | 3.3 | 2.6
Overview
This manual is divided into six main sections:
-
Getting started with jOOQ
This section will get you started with jOOQ quickly. It contains simple explanations about what jOOQ is, what jOOQ isn't and how to set it up for the first time
-
SQL building
This section explains all about the jOOQ syntax used for building queries through the query DSL and the query model API. It explains the central factories, the supported SQL statements and various other syntax elements
-
Code generation
This section explains how to configure and use the built-in source code generator
-
SQL execution
This section will get you through the specifics of what can be done with jOOQ at runtime, in order to execute queries, perform CRUD operations, import and export data, and hook into the jOOQ execution lifecycle for debugging
-
Tools
This section is dedicated to tools that ship with jOOQ, such as the jOOQ's JDBC mocking feature
-
Reference
This section is a reference for elements in this manual
Table of contents
- 1.
- Preface
- 2.
- Copyright, License, and Trademarks
- 3.
- Getting started with jOOQ
- 3.1.
- How to read this manual
- 3.2.
- The sample database used in this manual
- 3.3.
- Different use cases for jOOQ
- 3.3.1.
- jOOQ as a SQL builder without code generation
- 3.3.2.
- jOOQ as a SQL builder with code generation
- 3.3.3.
- jOOQ as a SQL executor
- 3.3.4.
- jOOQ for CRUD
- 3.3.5.
- jOOQ for PROs
- 3.4.
- Tutorials
- 3.4.1.
- jOOQ in 7 easy steps
- 3.4.1.1.
- Step 1: Preparation
- 3.4.1.2.
- Step 2: Your database
- 3.4.1.3.
- Step 3: Code generation
- 3.4.1.4.
- Step 4: Connect to your database
- 3.4.1.5.
- Step 5: Querying
- 3.4.1.6.
- Step 6: Iterating
- 3.4.1.7.
- Step 7: Explore!
- 3.4.2.
- Using jOOQ in modern IDEs
- 3.4.3.
- Using jOOQ with Spring and Apache DBCP
- 3.4.4.
- Using jOOQ with Flyway
- 3.5.
- jOOQ and Java 8
- 3.6.
- jOOQ and JavaFX
- 3.7.
- jOOQ and Nashorn
- 3.8.
- jOOQ and Scala
- 3.9.
- jOOQ and Groovy
- 3.10.
- jOOQ and NoSQL
- 3.11.
- jOOQ and JPA
- 3.12.
- Build your own
- 3.13.
- jOOQ and backwards-compatibility
- 4.
- SQL building
- 4.1.
- The query DSL type
- 4.1.1.
- DSL subclasses
- 4.2.
- The DSLContext API
- 4.2.1.
- SQL Dialect
- 4.2.2.
- SQL Dialect Family
- 4.2.3.
- Connection vs. DataSource
- 4.2.4.
- Custom data
- 4.2.5.
- Custom ExecuteListeners
- 4.2.6.
- Custom Settings
- 4.2.6.1.
- Object qualification
- 4.2.6.2.
- Runtime schema and table mapping
- 4.2.6.3.
- Identifier style
- 4.2.6.4.
- Keyword style
- 4.2.6.5.
- Parameter types
- 4.2.6.6.
- Statement Type
- 4.2.6.7.
- Execute Logging
- 4.2.6.8.
- Optimistic Locking
- 4.2.6.9.
- Auto-attach Records
- 4.2.6.10.
- Updatable Primary Keys
- 4.2.6.11.
- Reflection caching
- 4.2.6.12.
- Fetch Warnings
- 4.2.6.13.
- Backslash Escaping
- 4.2.6.14.
- Scalar subqueries for stored functions
- 4.2.7.
- Thread safety
- 4.3.
- SQL Statements (DML)
- 4.3.1.
- jOOQ's DSL and model API
- 4.3.2.
- The WITH clause
- 4.3.3.
- The SELECT statement
- 4.3.3.1.
- SELECT clause
- 4.3.3.2.
- FROM clause
- 4.3.3.3.
- JOIN operator
- 4.3.3.4.
- WHERE clause
- 4.3.3.5.
- CONNECT BY clause
- 4.3.3.6.
- GROUP BY clause
- 4.3.3.7.
- HAVING clause
- 4.3.3.8.
- WINDOW clause
- 4.3.3.9.
- ORDER BY clause
- 4.3.3.10.
- LIMIT .. OFFSET clause
- 4.3.3.11.
- SEEK clause
- 4.3.3.12.
- FOR UPDATE clause
- 4.3.3.13.
- UNION, INTERSECTION and EXCEPT
- 4.3.3.14.
- Oracle-style hints
- 4.3.3.15.
- Lexical and logical SELECT clause order
- 4.3.4.
- The INSERT statement
- 4.3.5.
- The UPDATE statement
- 4.3.6.
- The DELETE statement
- 4.3.7.
- The MERGE statement
- 4.4.
- SQL Statements (DDL)
- 4.4.1.
- The ALTER statement
- 4.4.1.1.
- ALTER SEQUENCE
- 4.4.1.2.
- ALTER TABLE
- 4.4.2.
- The CREATE statement
- 4.4.2.1.
- CREATE INDEX
- 4.4.2.2.
- CREATE SEQUENCE
- 4.4.2.3.
- CREATE TABLE
- 4.4.2.4.
- CREATE VIEW
- 4.4.3.
- The DROP statement
- 4.4.3.1.
- DROP INDEX
- 4.4.3.2.
- DROP SEQUENCE
- 4.4.3.3.
- DROP TABLE
- 4.4.3.4.
- DROP VIEW
- 4.4.4.
- The TRUNCATE statement
- 4.5.
- Table expressions
- 4.5.1.
- Generated Tables
- 4.5.2.
- Aliased Tables
- 4.5.3.
- Joined tables
- 4.5.4.
- The VALUES() table constructor
- 4.5.5.
- Nested SELECTs
- 4.5.6.
- The Oracle 11g PIVOT clause
- 4.5.7.
- jOOQ's relational division syntax
- 4.5.8.
- Array and cursor unnesting
- 4.5.9.
- Table-valued functions
- 4.5.10.
- The DUAL table
- 4.6.
- Column expressions
- 4.6.1.
- Table columns
- 4.6.2.
- Aliased columns
- 4.6.3.
- Cast expressions
- 4.6.4.
- Datatype coercions
- 4.6.5.
- Arithmetic expressions
- 4.6.6.
- String concatenation
- 4.6.7.
- Case sensitivity with strings
- 4.6.8.
- General functions
- 4.6.8.1.
- COALESCE
- 4.6.8.2.
- DECODE
- 4.6.8.3.
- NULLIF
- 4.6.8.4.
- NVL
- 4.6.8.5.
- NVL2
- 4.6.9.
- Numeric functions
- 4.6.9.1.
- ABS
- 4.6.9.2.
- ACOS
- 4.6.9.3.
- ACOS
- 4.6.9.4.
- ATAN
- 4.6.9.5.
- ATAN2
- 4.6.9.6.
- CEIL
- 4.6.9.7.
- COS
- 4.6.9.8.
- COSH
- 4.6.9.9.
- COT
- 4.6.9.10.
- COTH
- 4.6.9.11.
- DEG
- 4.6.9.12.
- EXP
- 4.6.9.13.
- FLOOR
- 4.6.9.14.
- GREATEST
- 4.6.9.15.
- LEAST
- 4.6.9.16.
- LN
- 4.6.9.17.
- LOG
- 4.6.9.18.
- NEG
- 4.6.9.19.
- POWER
- 4.6.9.20.
- RAD
- 4.6.9.21.
- RAND
- 4.6.9.22.
- ROUND
- 4.6.9.23.
- SIGN
- 4.6.9.24.
- SIN
- 4.6.9.25.
- SINH
- 4.6.9.26.
- SQRT
- 4.6.9.27.
- TAN
- 4.6.9.28.
- TANH
- 4.6.9.29.
- TRUNC
- 4.6.10.
- Bitwise functions
- 4.6.10.1.
- BIT_COUNT
- 4.6.10.2.
- BIT_AND
- 4.6.10.3.
- BIT_NAND
- 4.6.10.4.
- BIT_NOR
- 4.6.10.5.
- BIT_NOT
- 4.6.10.6.
- BIT_OR
- 4.6.10.7.
- SHL
- 4.6.10.8.
- SHR
- 4.6.10.9.
- BIT_XNOR
- 4.6.10.10.
- BIT_XOR
- 4.6.11.
- String functions
- 4.6.11.1.
- ASCII
- 4.6.11.2.
- CONCAT
- 4.6.11.3.
- LEFT
- 4.6.11.4.
- LENGTH
- 4.6.11.5.
- LOWER
- 4.6.11.6.
- LPAD
- 4.6.11.7.
- LTRIM
- 4.6.11.8.
- MD5
- 4.6.11.9.
- POSITION
- 4.6.11.10.
- REPEAT
- 4.6.11.11.
- REPLACE
- 4.6.11.12.
- REVERSE
- 4.6.11.13.
- RIGHT
- 4.6.11.14.
- RPAD
- 4.6.11.15.
- RTRIM
- 4.6.11.16.
- SPACE
- 4.6.11.17.
- SUBSTRING
- 4.6.11.18.
- TRANSLATE
- 4.6.11.19.
- TRIM
- 4.6.11.20.
- UPPER
- 4.6.12.
- Datetime functions
- 4.6.12.1.
- CURRENT_DATE
- 4.6.12.2.
- CURRENT_TIME
- 4.6.12.3.
- CURRENT_TIMESTAMP
- 4.6.12.4.
- DATE
- 4.6.12.5.
- DATEADD
- 4.6.12.6.
- DATEDIFF
- 4.6.12.7.
- DAY
- 4.6.12.8.
- EXTRACT
- 4.6.12.9.
- HOUR
- 4.6.12.10.
- MINUTE
- 4.6.12.11.
- MONTH
- 4.6.12.12.
- SECOND
- 4.6.12.13.
- TIME
- 4.6.12.14.
- TIMESTAMP
- 4.6.12.15.
- TIMESTAMPADD
- 4.6.12.16.
- TRUNC
- 4.6.12.17.
- YEAR
- 4.6.13.
- System functions
- 4.6.13.1.
- CURRENT_USER
- 4.6.14.
- Aggregate functions
- 4.6.14.1.
- Grouping
- 4.6.14.2.
- Distinctness
- 4.6.14.3.
- Filtering
- 4.6.14.4.
- Ordering WITHIN GROUP
- 4.6.14.5.
- Keeping
- 4.6.14.6.
- AVG
- 4.6.14.7.
- COUNT
- 4.6.14.8.
- CUME_DIST
- 4.6.14.9.
- DENSE_RANK
- 4.6.14.10.
- GROUP_CONCAT
- 4.6.14.11.
- LISTAGG
- 4.6.14.12.
- MAX
- 4.6.14.13.
- MEDIAN
- 4.6.14.14.
- MIN
- 4.6.14.15.
- PERCENT_RANK
- 4.6.14.16.
- PRODUCT
- 4.6.14.17.
- RANK
- 4.6.14.18.
- SUM
- 4.6.15.
- Window functions
- 4.6.16.
- Grouping functions
- 4.6.17.
- User-defined functions
- 4.6.18.
- User-defined aggregate functions
- 4.6.19.
- The CASE expression
- 4.6.20.
- Sequences and serials
- 4.6.21.
- Tuples or row value expressions
- 4.7.
- Conditional expressions
- 4.7.1.
- Condition building
- 4.7.2.
- BOOLEAN columns
- 4.7.3.
- AND, OR, NOT boolean operators
- 4.7.4.
- Comparison predicate
- 4.7.5.
- Boolean operator precedence
- 4.7.6.
- Comparison predicate (degree > 1)
- 4.7.7.
- Quantified comparison predicate
- 4.7.8.
- NULL predicate
- 4.7.9.
- NULL predicate (degree > 1)
- 4.7.10.
- DISTINCT predicate
- 4.7.11.
- BETWEEN predicate
- 4.7.12.
- BETWEEN predicate (degree > 1)
- 4.7.13.
- LIKE predicate
- 4.7.14.
- IN predicate
- 4.7.15.
- IN predicate (degree > 1)
- 4.7.16.
- EXISTS predicate
- 4.7.17.
- OVERLAPS predicate
- 4.8.
- Synthetic SQL clauses
- 4.9.
- Dynamic SQL
- 4.10.
- Plain SQL
- 4.11.
- Plain SQL Templating Language
- 4.12.
- Names and identifiers
- 4.13.
- Bind values and parameters
- 4.13.1.
- Indexed parameters
- 4.13.2.
- Named parameters
- 4.13.3.
- Inlined parameters
- 4.13.4.
- SQL injection and plain SQL QueryParts
- 4.14.
- QueryParts
- 4.14.1.
- SQL rendering
- 4.14.2.
- Pretty printing SQL
- 4.14.3.
- Variable binding
- 4.14.4.
- Extend jOOQ with custom types
- 4.14.5.
- Plain SQL QueryParts
- 4.14.6.
- Serializability
- 4.14.7.
- Custom SQL transformation
- 4.14.7.1.
- Logging abbreviated bind values
- 4.15.
- Zero-based vs one-based APIs
- 4.16.
- SQL building in Scala
- 5.
- SQL execution
- 5.1.
- Comparison between jOOQ and JDBC
- 5.2.
- Query vs. ResultQuery
- 5.3.
- Fetching
- 5.3.1.
- Record vs. TableRecord
- 5.3.2.
- Record1 to Record22
- 5.3.3.
- Arrays, Maps and Lists
- 5.3.4.
- RecordHandler
- 5.3.5.
- RecordMapper
- 5.3.6.
- POJOs
- 5.3.7.
- POJOs with RecordMappers
- 5.3.8.
- Lazy fetching
- 5.3.9.
- Many fetching
- 5.3.10.
- Later fetching
- 5.3.11.
- ResultSet fetching
- 5.3.12.
- Auto data type conversion
- 5.3.13.
- Custom data type conversion
- 5.3.14.
- Interning data
- 5.4.
- Static statements vs. Prepared Statements
- 5.5.
- Reusing a Query's PreparedStatement
- 5.6.
- JDBC flags
- 5.7.
- Using JDBC batch operations
- 5.8.
- Sequence execution
- 5.9.
- Stored procedures and functions
- 5.9.1.
- Oracle Packages
- 5.9.2.
- Oracle member procedures
- 5.10.
- Exporting to XML, CSV, JSON, HTML, Text
- 5.10.1.
- Exporting XML
- 5.10.2.
- Exporting CSV
- 5.10.3.
- Exporting JSON
- 5.10.4.
- Exporting HTML
- 5.10.5.
- Exporting Text
- 5.11.
- Importing data
- 5.11.1.
- The Loader API
- 5.11.2.
- Import options
- 5.11.2.1.
- Throttling
- 5.11.2.2.
- Duplicate handling
- 5.11.2.3.
- Error handling
- 5.11.3.
- Import data sources
- 5.11.3.1.
- Importing CSV
- 5.11.3.2.
- Importing JSON
- 5.11.3.3.
- Importing XML
- 5.11.4.
- Import result and error handling
- 5.12.
- CRUD with UpdatableRecords
- 5.12.1.
- Simple CRUD
- 5.12.2.
- Records' internal flags
- 5.12.3.
- IDENTITY values
- 5.12.4.
- Navigation methods
- 5.12.5.
- Non-updatable records
- 5.12.6.
- Optimistic locking
- 5.12.7.
- Batch execution
- 5.12.8.
- CRUD SPI: RecordListener
- 5.13.
- DAOs
- 5.14.
- Transaction management
- 5.15.
- Exception handling
- 5.16.
- ExecuteListeners
- 5.17.
- Database meta data
- 5.18.
- Logging
- 5.19.
- Performance considerations
- 6.
- Code generation
- 6.1.
- Configuration and setup of the generator
- 6.2.
- Running the code generator with Maven
- 6.3.
- Running the code generator with Ant
- 6.4.
- Running the code generator with Gradle
- 6.5.
- Advanced generator configuration
- 6.6.
- Programmatic generator configuration
- 6.7.
- Custom generator strategies
- 6.8.
- Matcher strategies
- 6.9.
- Custom code sections
- 6.10.
- Generated global artefacts
- 6.11.
- Generated tables
- 6.12.
- Generated records
- 6.13.
- Generated POJOs
- 6.14.
- Generated Interfaces
- 6.15.
- Generated DAOs
- 6.16.
- Generated sequences
- 6.17.
- Generated procedures
- 6.18.
- Generated UDTs
- 6.19.
- Data type rewrites
- 6.20.
- Custom data types and type conversion
- 6.21.
- Custom data type binding
- 6.22.
- Mapping generated schemata and tables
- 6.23.
- Code generation for large schemas
- 6.24.
- Code generation and version control
- 7.
- Tools
- 7.1.
- JDBC mocking for unit testing
- 7.2.
- SQL 2 jOOQ Parser
- 7.3.
- jOOQ Console
- 8.
- Reference
- 8.1.
- Supported RDBMS
- 8.2.
- Data types
- 8.2.1.
- BLOBs and CLOBs
- 8.2.2.
- BOOLEAN data type
- 8.2.3.
- Unsigned integer types
- 8.2.4.
- INTERVAL data types
- 8.2.5.
- XML data types
- 8.2.6.
- Geospacial data types
- 8.2.7.
- CURSOR data types
- 8.2.8.
- ARRAY and TABLE data types
- 8.2.9.
- Oracle DATE data type
- 8.3.
- SQL to DSL mapping rules
- 8.4.
- Quality Assurance
- 8.5.
- Migrating to jOOQ 3.0
- 8.6.
- Credits
1. Preface
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
jOOQ's reason for being - compared to JPA
Java and SQL have come a long way. SQL is an "old", yet established and well-understood technology. Java is a legacy too, although its platform JVM allows for many new and contemporary languages built on top of it. Yet, after all these years, libraries dealing with the interface between SQL and Java have come and gone, leaving JPA to be a standard that is accepted only with doubts, short of any surviving options.
So far, there had been only few database abstraction frameworks or libraries, that truly respected SQL as a first class citizen among languages. Most frameworks, including the industry standards JPA, EJB, Hibernate, JDO, Criteria Query, and many others try to hide SQL itself, minimising its scope to things called JPQL, HQL, JDOQL and various other inferior query languages
jOOQ has come to fill this gap.
jOOQ's reason for being - compared to LINQ
Other platforms incorporate ideas such as LINQ (with LINQ-to-SQL), or Scala's SLICK, or also Java's QueryDSL to better integrate querying as a concept into their respective language. By querying, they understand querying of arbitrary targets, such as SQL, XML, Collections and other heterogeneous data stores. jOOQ claims that this is going the wrong way too.
In more advanced querying use-cases (more than simple CRUD and the occasional JOIN), people will want to profit from the expressivity of SQL. Due to the relational nature of SQL, this is quite different from what object-oriented and partially functional languages such as C#, Scala, or Java can offer.
It is very hard to formally express and validate joins and the ad-hoc table expression types they create. It gets even harder when you want support for more advanced table expressions, such as pivot tables, unnested cursors, or just arbitrary projections from derived tables. With a very strong object-oriented typing model, these features will probably stay out of scope.
In essence, the decision of creating an API that looks like SQL or one that looks like C#, Scala, Java is a definite decision in favour of one or the other platform. While it will be easier to evolve SLICK in similar ways as LINQ (or QueryDSL in the Java world), SQL feature scope that clearly communicates its underlying intent will be very hard to add, later on (e.g. how would you model Oracle's partitioned outer join syntax? How would you model ANSI/ISO SQL:1999 grouping sets? How can you support scalar subquery caching? etc...).
jOOQ has come to fill this gap.
jOOQ's reason for being - compared to SQL / JDBC
So why not just use SQL?
SQL can be written as plain text and passed through the JDBC API. Over the years, people have become wary of this approach for many reasons:
- No typesafety
- No syntax safety
- No bind value index safety
- Verbose SQL String concatenation
- Boring bind value indexing techniques
- Verbose resource and exception handling in JDBC
- A very "stateful", not very object-oriented JDBC API, which is hard to use
For these many reasons, other frameworks have tried to abstract JDBC away in the past in one way or another. Unfortunately, many have completely abstracted SQL away as well
jOOQ has come to fill this gap.
jOOQ is different
SQL was never meant to be abstracted. To be confined in the narrow boundaries of heavy mappers, hiding the beauty and simplicity of relational data. SQL was never meant to be object-oriented. SQL was never meant to be anything other than... SQL!
2. Copyright, License, and Trademarks
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
This section lists the various licenses that apply to different versions of jOOQ. Prior to version 3.2, jOOQ was shipped for free under the terms of the Apache Software License 2.0. With jOOQ 3.2, jOOQ became dual-licensed: Apache Software License 2.0 (for use with Open Source databases) and commercial (for use with commercial databases).
This manual itself (as well as the www.jooq.org public website) is licensed to you under the terms of the CC BY-SA 4.0 license.
Please contact legal@datageekery.com, should you have any questions regarding licensing.
License for jOOQ 3.2 and later
This work is dual-licensed - under the Apache Software License 2.0 (the "ASL") - under the jOOQ License and Maintenance Agreement (the "jOOQ License") ============================================================================= You may choose which license applies to you: - If you're using this work with Open Source databases, you may choose either ASL or jOOQ License. - If you're using this work with at least one commercial database, you must choose jOOQ License For more information, please visit https://www.jooq.org/licenses Apache Software License 2.0: ----------------------------------------------------------------------------- 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. jOOQ License and Maintenance Agreement: ----------------------------------------------------------------------------- Data Geekery grants the Customer the non-exclusive, timely limited and non-transferable license to install and use the Software under the terms of the jOOQ License and Maintenance Agreement. This library is distributed with a LIMITED WARRANTY. See the jOOQ License and Maintenance Agreement for more details: https://www.jooq.org/licensing
Historic license for jOOQ 1.x, 2.x, 3.0, 3.1
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.
Trademarks owned by Data Geekery™ GmbH
- jOOλ™ is a trademark by Data Geekery™ GmbH
- jOOQ™ is a trademark by Data Geekery™ GmbH
- jOOR™ is a trademark by Data Geekery™ GmbH
- jOOU™ is a trademark by Data Geekery™ GmbH
- jOOX™ is a trademark by Data Geekery™ GmbH
Trademarks owned by Data Geekery™ GmbH partners
- GSP and General SQL Parser are trademarks by Gudu Software Limited
- SQL 2 jOOQ is a trademark by Data Geekery™ GmbH and Gudu Software Limited
- Flyway is a trademark by Snow Mountain Labs UG (haftungsbeschränkt)
Trademarks owned by database vendors with no affiliation to Data Geekery™ GmbH
- Access® is a registered trademark of Microsoft® Inc.
- Adaptive Server® Enterprise is a registered trademark of Sybase®, Inc.
- CUBRID™ is a trademark of NHN® Corp.
- DB2® is a registered trademark of IBM® Corp.
- Derby is a trademark of the Apache™ Software Foundation
- H2 is a trademark of the H2 Group
- HSQLDB is a trademark of The hsql Development Group
- Ingres is a trademark of Actian™ Corp.
- MariaDB is a trademark of Monty Program Ab
- MySQL® is a registered trademark of Oracle® Corp.
- Firebird® is a registered trademark of Firebird Foundation Inc.
- Oracle® database is a registered trademark of Oracle® Corp.
- PostgreSQL® is a registered trademark of The PostgreSQL Global Development Group
- Postgres Plus® is a registered trademark of EnterpriseDB® software
- SQL Anywhere® is a registered trademark of Sybase®, Inc.
- SQL Server® is a registered trademark of Microsoft® Inc.
- SQLite is a trademark of Hipp, Wyrick & Company, Inc.
Other trademarks by vendors with no affiliation to Data Geekery™ GmbH
- Java® is a registered trademark by Oracle® Corp. and/or its affiliates
- Scala is a trademark of EPFL
Other trademark remarks
Other names may be trademarks of their respective owners.
Throughout the manual, the above trademarks are referenced without a formal ® (R) or ™ (TM) symbol. It is believed that referencing third-party trademarks in this manual or on the jOOQ website constitutes "fair use". Please contact us if you think that your trademark(s) are not properly attributed.
Contributions
The following are authors and contributors of jOOQ or parts of jOOQ in alphabetical order:
- Aaron Digulla
- Andreas Franzén
- Anuraag Agrawal
- Arnaud Roger
- Art O Cathain
- Artur Dryomov
- Ben Manes
- Brent Douglas
- Brett Meyer
- Christian Stein
- Christopher Deckers
- Ed Schaller
- Eric Peters
- Ernest Mishkin
- Espen Stromsnes
- Eugeny Karpov
- Fabrice Le Roy
- Gonzalo Ortiz Jaureguizar
- Gregory Hlavac
- Henrik Sjöstrand
- Ivan Dugic
- Javier Durante
- Johannes Bühler
- Joseph B Phillips
- Joseph Pachod
- Knut Wannheden
- Laurent Pireyn
- Luc Marchaud
- Lukas Eder
- Matti Tahvonen
- Michael Doberenz
- Michael Simons
- Michał Kołodziejski
- Miguel Gonzalez Sanchez
- Mustafa Yücel
- Nathaniel Fischer
- Oliver Flege
- Peter Ertl
- Richard Bradley
- Robin Stocker
- Samy Deghou
- Sander Plas
- Sean Wellington
- Sergey Epik
- Sergey Zhuravlev
- Stanislas Nanchen
- Stephan Schroevers
- Sugiharto Lim
- Sven Jacobs
- Szymon Jachim
- Terence Zhang
- Timothy Wilson
- Timur Shaidullin
- Thomas Darimont
- Tsukasa Kitachi
- Victor Bronstein
- Victor Z. Peng
- Vladimir Kulev
- Vladimir Vinogradov
- Vojtech Polivka
- Wang Gaoyuan
- Zoltan Tamasi
See the following website for details about contributing to jOOQ:
https://www.jooq.org/legal/contributions
3. Getting started with jOOQ
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
These chapters contain a quick overview of how to get started with this manual and with jOOQ. While the subsequent chapters contain a lot of reference information, this chapter here just wraps up the essentials.
3.1. How to read this manual
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
This section helps you correctly interpret this manual in the context of jOOQ.
Code blocks
The following are code blocks:
-- A SQL code block SELECT 1 FROM DUAL
// A Java code block for (int i = 0; i < 10; i++);
<!-- An XML code block --> <hello what="world"></hello>
# A config file code block org.jooq.property=value
These are useful to provide examples in code. Often, with jOOQ, it is even more useful to compare SQL code with its corresponding Java/jOOQ code. When this is done, the blocks are aligned side-by-side, with SQL usually being on the left, and an equivalent jOOQ DSL query in Java usually being on the right:
-- In SQL: SELECT 1 FROM DUAL
// Using jOOQ: create.selectOne().fetch()
Code block contents
The contents of code blocks follow conventions, too. If nothing else is mentioned next to any given code block, then the following can be assumed:
-- SQL assumptions ------------------ -- If nothing else is specified, assume that the Oracle syntax is used SELECT 1 FROM DUAL
// Java assumptions // ---------------- // Whenever you see "standalone functions", assume they were static imported from org.jooq.impl.DSL // "DSL" is the entry point of the static query DSL exists(); max(); min(); val(); inline(); // correspond to DSL.exists(); DSL.max(); DSL.min(); etc... // Whenever you see BOOK/Book, AUTHOR/Author and similar entities, assume they were (static) imported from the generated schema BOOK.TITLE, AUTHOR.LAST_NAME // correspond to com.example.generated.Tables.BOOK.TITLE, com.example.generated.Tables.BOOK.TITLE FK_BOOK_AUTHOR // corresponds to com.example.generated.Keys.FK_BOOK_AUTHOR // Whenever you see "create" being used in Java code, assume that this is an instance of org.jooq.DSLContext. // The reason why it is called "create" is the fact, that a jOOQ QueryPart is being created from the DSL object. // "create" is thus the entry point of the non-static query DSL DSLContext create = DSL.using(connection, SQLDialect.ORACLE);
Your naming may differ, of course. For instance, you could name the "create" instance "db", instead.
Execution
When you're coding PL/SQL, T-SQL or some other procedural SQL language, SQL statements are always executed immediately at the semi-colon. This is not the case in jOOQ, because as an internal DSL, jOOQ can never be sure that your statement is complete until you call fetch()
or execute()
. The manual tries to apply fetch()
and execute()
as thoroughly as possible. If not, it is implied:
SELECT 1 FROM DUAL UPDATE t SET v = 1
create.selectOne().fetch(); create.update(T).set(T.V, 1).execute();
Degree (arity)
jOOQ records (and many other API elements) have a degree N between 1 and 22. The variable degree of an API element is denoted as [N], e.g. Row[N] or Record[N]. The term "degree" is preferred over arity, as "degree" is the term used in the SQL standard, whereas "arity" is used more often in mathematics and relational theory.
Settings
jOOQ allows to override runtime behaviour using org.jooq.conf.Settings. If nothing is specified, the default runtime settings are assumed.
Sample database
jOOQ query examples run against the sample database. See the manual's section about the sample database used in this manual to learn more about the sample database.
3.2. The sample database used in this manual
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
For the examples in this manual, the same database will always be referred to. It essentially consists of these entities created using the Oracle dialect
CREATE TABLE language ( id NUMBER(7) NOT NULL PRIMARY KEY, cd CHAR(2) NOT NULL, description VARCHAR2(50) ); CREATE TABLE author ( id NUMBER(7) NOT NULL PRIMARY KEY, first_name VARCHAR2(50), last_name VARCHAR2(50) NOT NULL, date_of_birth DATE, year_of_birth NUMBER(7), distinguished NUMBER(1) ); CREATE TABLE book ( id NUMBER(7) NOT NULL PRIMARY KEY, author_id NUMBER(7) NOT NULL, title VARCHAR2(400) NOT NULL, published_in NUMBER(7) NOT NULL, language_id NUMBER(7) NOT NULL, CONSTRAINT fk_book_author FOREIGN KEY (author_id) REFERENCES author(id), CONSTRAINT fk_book_language FOREIGN KEY (language_id) REFERENCES language(id) ); CREATE TABLE book_store ( name VARCHAR2(400) NOT NULL UNIQUE ); CREATE TABLE book_to_book_store ( name VARCHAR2(400) NOT NULL, book_id INTEGER NOT NULL, stock INTEGER, PRIMARY KEY(name, book_id), CONSTRAINT fk_b2bs_book_store FOREIGN KEY (name) REFERENCES book_store (name) ON DELETE CASCADE, CONSTRAINT fk_b2bs_book FOREIGN KEY (book_id) REFERENCES book (id) ON DELETE CASCADE );
More entities, types (e.g. UDT's, ARRAY types, ENUM types, etc), stored procedures and packages are introduced for specific examples
In addition to the above, you may assume the following sample data:
INSERT INTO language (id, cd, description) VALUES (1, 'en', 'English'); INSERT INTO language (id, cd, description) VALUES (2, 'de', 'Deutsch'); INSERT INTO language (id, cd, description) VALUES (3, 'fr', 'Français'); INSERT INTO language (id, cd, description) VALUES (4, 'pt', 'Português'); INSERT INTO author (id, first_name, last_name, date_of_birth , year_of_birth) VALUES (1 , 'George' , 'Orwell' , DATE '1903-06-26', 1903 ); INSERT INTO author (id, first_name, last_name, date_of_birth , year_of_birth) VALUES (2 , 'Paulo' , 'Coelho' , DATE '1947-08-24', 1947 ); INSERT INTO book (id, author_id, title , published_in, language_id) VALUES (1 , 1 , '1984' , 1948 , 1 ); INSERT INTO book (id, author_id, title , published_in, language_id) VALUES (2 , 1 , 'Animal Farm' , 1945 , 1 ); INSERT INTO book (id, author_id, title , published_in, language_id) VALUES (3 , 2 , 'O Alquimista', 1988 , 4 ); INSERT INTO book (id, author_id, title , published_in, language_id) VALUES (4 , 2 , 'Brida' , 1990 , 2 ); INSERT INTO book_store VALUES ('Orell Füssli'); INSERT INTO book_store VALUES ('Ex Libris'); INSERT INTO book_store VALUES ('Buchhandlung im Volkshaus'); INSERT INTO book_to_book_store VALUES ('Orell Füssli' , 1, 10); INSERT INTO book_to_book_store VALUES ('Orell Füssli' , 2, 10); INSERT INTO book_to_book_store VALUES ('Orell Füssli' , 3, 10); INSERT INTO book_to_book_store VALUES ('Ex Libris' , 1, 1 ); INSERT INTO book_to_book_store VALUES ('Ex Libris' , 3, 2 ); INSERT INTO book_to_book_store VALUES ('Buchhandlung im Volkshaus', 3, 1 );
3.3. Different use cases for jOOQ
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
jOOQ has originally been created as a library for complete abstraction of JDBC and all database interaction. Various best practices that are frequently encountered in pre-existing software products are applied to this library. This includes:
- Typesafe database object referencing through generated schema, table, column, record, procedure, type, dao, pojo artefacts (see the chapter about code generation)
- Typesafe SQL construction / SQL building through a complete querying DSL API modelling SQL as a domain specific language in Java (see the chapter about the query DSL API)
- Convenient query execution through an improved API for result fetching (see the chapters about the various types of data fetching)
- SQL dialect abstraction and SQL clause emulation to improve cross-database compatibility and to enable missing features in simpler databases (see the chapter about SQL dialects)
- SQL logging and debugging using jOOQ as an integral part of your development process (see the chapters about logging)
Effectively, jOOQ was originally designed to replace any other database abstraction framework short of the ones handling connection pooling (and more sophisticated transaction management)
Use jOOQ the way you prefer
... but open source is community-driven. And the community has shown various ways of using jOOQ that diverge from its original intent. Some use cases encountered are:
- Using Hibernate for 70% of the queries (i.e. CRUD) and jOOQ for the remaining 30% where SQL is really needed
- Using jOOQ for SQL building and JDBC for SQL execution
- Using jOOQ for SQL building and Spring Data for SQL execution
- Using jOOQ without the source code generator to build the basis of a framework for dynamic SQL execution.
The following sections explain about various use cases for using jOOQ in your application.
3.3.1. jOOQ as a SQL builder without code generation
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
We strongly recommend to use jOOQ with its code generator to get the most out of jOOQ!
However, if you have a dynamic schema, you don't have to use the code generator. This is the most simple of all use cases, allowing for construction of valid SQL for any database. In this use case, you will not use jOOQ's code generator and maybe not even jOOQ's query execution facilities. Instead, you'll use jOOQ's query DSL API to wrap strings, literals and other user-defined objects into an object-oriented, type-safe AST modelling your SQL statements. An example is given here:
// Fetch a SQL string from a jOOQ Query in order to manually execute it with another tool. // For simplicity reasons, we're using the API to construct case-insensitive object references, here. Query query = create.select(field("BOOK.TITLE"), field("AUTHOR.FIRST_NAME"), field("AUTHOR.LAST_NAME")) .from(table("BOOK")) .join(table("AUTHOR")) .on(field("BOOK.AUTHOR_ID").eq(field("AUTHOR.ID"))) .where(field("BOOK.PUBLISHED_IN").eq(1948)); String sql = query.getSQL(); List<Object> bindValues = query.getBindValues();
The SQL string built with the jOOQ query DSL can then be executed using JDBC directly, using Spring's JdbcTemplate, using Apache DbUtils and many other tools (note that since jOOQ uses PreparedStatement
by default, this will generate a bind variable for "1948". Read more about bind variables here).
You can also avoid getting the SQL string and bind values separately:
String sql = query.getSQL(ParamType.INLINED);
If you wish to use jOOQ only as a SQL builder, the following sections of the manual will be of interest to you:
- SQL building: This section contains a lot of information about creating SQL statements using the jOOQ API
- Plain SQL: This section contains information useful in particular to those that want to supply table expressions, column expressions, etc. as plain SQL to jOOQ, rather than through generated artefacts
- Bind values: This section explains how bind values are managed and/or inlined in jOOQ.
3.3.2. jOOQ as a SQL builder with code generation
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In addition to using jOOQ as a standalone SQL builder, you can also use jOOQ's code generation features in order to compile your SQL statements using a Java compiler against an actual database schema. This adds a lot of power and expressiveness to just simply constructing SQL using the query DSL and custom strings and literals, as you can be sure that all database artefacts actually exist in the database, and that their type is correct. We strongly recommend using this approach. An example is given here:
// Fetch a SQL string from a jOOQ Query in order to manually execute it with another tool. Query query = create.select(BOOK.TITLE, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .from(BOOK) .join(AUTHOR) .on(BOOK.AUTHOR_ID.eq(AUTHOR.ID)) .where(BOOK.PUBLISHED_IN.eq(1948)); String sql = query.getSQL(); List<Object> bindValues = query.getBindValues();
The SQL string built with the jOOQ query DSL can then be executed using JDBC directly, using Spring's JdbcTemplate, using Apache DbUtils and many other tools (note that since jOOQ uses PreparedStatement
by default, this will generate a bind variable for "1948". Read more about bind variables here).
You can also avoid getting the SQL string and bind values separately:
String sql = query.getSQL(ParamType.INLINED);
If you wish to use jOOQ only as a SQL builder with code generation, the following sections of the manual will be of interest to you:
- SQL building: This section contains a lot of information about creating SQL statements using the jOOQ API
- Code generation: This section contains the necessary information to run jOOQ's code generator against your developer database
- Bind values: This section explains how bind values are managed and/or inlined in jOOQ.
3.3.3. jOOQ as a SQL executor
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Instead of any tool mentioned in the previous chapters, you can also use jOOQ directly to execute your jOOQ-generated SQL statements. This will add a lot of convenience on top of the previously discussed API for typesafe SQL construction, when you can re-use the information from generated classes to fetch records and custom data types. An example is given here:
// Typesafely execute the SQL statement directly with jOOQ Result<Record3<String, String, String>> result = create.select(BOOK.TITLE, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .from(BOOK) .join(AUTHOR) .on(BOOK.AUTHOR_ID.eq(AUTHOR.ID)) .where(BOOK.PUBLISHED_IN.eq(1948)) .fetch();
By having jOOQ execute your SQL, the jOOQ query DSL becomes truly embedded SQL.
jOOQ doesn't stop here, though! You can execute any SQL with jOOQ. In other words, you can use any other SQL building tool and run the SQL statements with jOOQ. An example is given here:
// Use your favourite tool to construct SQL strings: String sql = "SELECT title, first_name, last_name FROM book JOIN author ON book.author_id = author.id " + "WHERE book.published_in = 1984"; // Fetch results using jOOQ Result<Record> result = create.fetch(sql); // Or execute that SQL with JDBC, fetching the ResultSet with jOOQ: ResultSet rs = connection.createStatement().executeQuery(sql); Result<Record> result = create.fetch(rs);
If you wish to use jOOQ as a SQL executor with (or without) code generation, the following sections of the manual will be of interest to you:
- SQL building: This section contains a lot of information about creating SQL statements using the jOOQ API
- Code generation: This section contains the necessary information to run jOOQ's code generator against your developer database
- SQL execution: This section contains a lot of information about executing SQL statements using the jOOQ API
- Fetching: This section contains some useful information about the various ways of fetching data with jOOQ
3.3.4. jOOQ for CRUD
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Apart from jOOQ's fluent API for query construction, jOOQ can also help you execute everyday CRUD operations. An example is given here:
// Fetch an author AuthorRecord author = create.fetchOne(AUTHOR, AUTHOR.ID.eq(1)); // Create a new author, if it doesn't exist yet if (author == null) { author = create.newRecord(AUTHOR); author.setId(1); author.setFirstName("Dan"); author.setLastName("Brown"); } // Mark the author as a "distinguished" author and store it author.setDistinguished(1); // Executes an update on existing authors, or insert on new ones author.store();
If you wish to use all of jOOQ's features, the following sections of the manual will be of interest to you (including all sub-sections):
- SQL building: This section contains a lot of information about creating SQL statements using the jOOQ API
- Code generation: This section contains the necessary information to run jOOQ's code generator against your developer database
- SQL execution: This section contains a lot of information about executing SQL statements using the jOOQ API
3.3.5. jOOQ for PROs
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
jOOQ isn't just a library that helps you build and execute SQL against your generated, compilable schema. jOOQ ships with a lot of tools. Here are some of the most important tools shipped with jOOQ:
- jOOQ's Execute Listeners: jOOQ allows you to hook your custom execute listeners into jOOQ's SQL statement execution lifecycle in order to centrally coordinate any arbitrary operation performed on SQL being executed. Use this for logging, identity generation, SQL tracing, performance measurements, etc.
- Logging: jOOQ has a standard DEBUG logger built-in, for logging and tracing all your executed SQL statements and fetched result sets
- Stored Procedures: jOOQ supports stored procedures and functions of your favourite database. All routines and user-defined types are generated and can be included in jOOQ's SQL building API as function references.
- Batch execution: Batch execution is important when executing a big load of SQL statements. jOOQ simplifies these operations compared to JDBC
- Exporting and Importing: jOOQ ships with an API to easily export/import data in various formats
If you're a power user of your favourite, feature-rich database, jOOQ will help you access all of your database's vendor-specific features, such as OLAP features, stored procedures, user-defined types, vendor-specific SQL, functions, etc. Examples are given throughout this manual.
3.4. Tutorials
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Don't have time to read the full manual? Here are a couple of tutorials that will get you into the most essential parts of jOOQ as quick as possible.
3.4.1. jOOQ in 7 easy steps
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
This manual section is intended for new users, to help them get a running application with jOOQ, quickly.
3.4.1.1. Step 1: Preparation
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
If you haven't already downloaded it, download jOOQ:
https://www.jooq.org/download
Alternatively, you can create a Maven dependency to download jOOQ artefacts:
<dependency> <groupId>org.jooq</groupId> <artifactId>jooq</artifactId> <version>3.5.4</version> </dependency> <dependency> <groupId>org.jooq</groupId> <artifactId>jooq-meta</artifactId> <version>3.5.4</version> </dependency> <dependency> <groupId>org.jooq</groupId> <artifactId>jooq-codegen</artifactId> <version>3.5.4</version> </dependency>
Note that only the jOOQ Open Source Edition is available from Maven Central. If you're using the jOOQ Professional Edition or the jOOQ Enterprise Edition, you will have to manually install jOOQ in your local Nexus, or in your local Maven cache. For more information, please refer to the licensing pages.
Please refer to the manual's section about Code generation configuration to learn how to use jOOQ's code generator with Maven.
For this example, we'll be using MySQL. If you haven't already downloaded MySQL Connector/J, download it here:
http://dev.mysql.com/downloads/connector/j/
If you don't have a MySQL instance up and running yet, get it from https://www.mysql.com or https://hub.docker.com/_/mysql now!
3.4.1.2. Step 2: Your database
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
We're going to create a database called "library" and a corresponding "author" table. Connect to MySQL via your command line client and type the following:
CREATE DATABASE `library`; USE `library`; CREATE TABLE `author` ( `id` int NOT NULL, `first_name` varchar(255) DEFAULT NULL, `last_name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) );
3.4.1.3. Step 3: Code generation
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In this step, we're going to use jOOQ's command line tools to generate classes that map to the Author table we just created. More detailed information about how to set up the jOOQ code generator can be found here:
jOOQ manual pages about setting up the code generator
The easiest way to generate a schema is to copy the jOOQ jar files (there should be 3) and the MySQL Connector jar file to a temporary directory. Then, create a library.xml that looks like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <configuration xmlns="http://www.jooq.org/xsd/jooq-codegen-3.5.0.xsd"> <!-- Configure the database connection here --> <jdbc> <driver>com.mysql.cj.jdbc.Driver</driver> <url>jdbc:mysql://localhost:3306/library</url> <user>root</user> <password></password> </jdbc> <generator> <!-- The default code generator. You can override this one, to generate your own code style. Defaults to org.jooq.util.JavaGenerator Note the classes have been moved to org.jooq.codegen or org.jooq.meta in jOOQ 3.11 --> <name>org.jooq.util.JavaGenerator</name> <database> <!-- The database type. The format here is: org.jooq.util.[database].[database]Database Note the classes have been moved to org.jooq.codegen or org.jooq.meta in jOOQ 3.11 --> <name>org.jooq.util.mysql.MySQLDatabase</name> <!-- The database schema (or in the absence of schema support, in your RDBMS this can be the owner, user, database name) to be generated --> <inputSchema>library</inputSchema> <!-- All elements that are generated from your schema (A Java regular expression. Use the pipe to separate several expressions) Watch out for case-sensitivity. Depending on your database, this might be important! --> <includes>.*</includes> <!-- All elements that are excluded from your schema (A Java regular expression. Use the pipe to separate several expressions). Excludes match before includes, i.e. excludes have a higher priority --> <excludes></excludes> </database> <target> <!-- The destination package of your generated classes (within the destination directory) --> <packageName>test.generated</packageName> <!-- The destination directory of your generated classes. Using Maven directory layout here --> <directory>C:/workspace/MySQLTest/src/main/java</directory> </target> </generator> </configuration>
Replace the username with whatever user has the appropriate privileges to query the database meta data. You'll also want to look at the other values and replace as necessary. Here are the two interesting properties:
generator.target.package
- set this to the parent package you want to create for the generated classes. The setting of test.generated
will cause the test.generated.Author
and test.generated.AuthorRecord
to be created
generator.target.directory
- the directory to output to.
Once you have the JAR files and library.xml in your temp directory, type this on a Windows machine:
java -classpath jooq-3.5.4.jar;jooq-meta-3.5.4.jar;jooq-codegen-3.5.4.jar;mysql-connector-java-5.1.18-bin.jar;. org.jooq.util.GenerationTool library.xml
... or type this on a UNIX / Linux / Mac system (colons instead of semi-colons):
java -classpath jooq-3.5.4.jar:jooq-meta-3.5.4.jar:jooq-codegen-3.5.4.jar:mysql-connector-java-5.1.18-bin.jar:. org.jooq.util.GenerationTool library.xml
Note: The GenerationTool
class has been moved to org.jooq.codegen
in jOOQ 3.11
Note: jOOQ will try loading the library.xml from your classpath. This is also why there is a trailing period (.
) on the classpath. If the file cannot be found on the classpath, jOOQ will look on the file system from the current working directory.
Replace the filenames with your actual filenames. In this example, jOOQ 3.5.4 is being used. If everything has worked, you should see this in your console output:
Nov 1, 2011 7:25:06 PM org.jooq.impl.JooqLogger info INFO: Initialising properties : /library.xml Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Database parameters Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: ---------------------------------------------------------- Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: dialect : MYSQL Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: schema : library Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: target dir : C:/workspace/MySQLTest/src Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: target package : test.generated Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: ---------------------------------------------------------- Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Emptying : C:/workspace/MySQLTest/src/test/generated Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Generating classes in : C:/workspace/MySQLTest/src/test/generated Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Generating schema : Library.java Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Schema generated : Total: 122.18ms Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Sequences fetched : 0 (0 included, 0 excluded) Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Tables fetched : 5 (5 included, 0 excluded) Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Generating tables : C:/workspace/MySQLTest/src/test/generated/tables Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: ARRAYs fetched : 0 (0 included, 0 excluded) Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Enums fetched : 0 (0 included, 0 excluded) Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: UDTs fetched : 0 (0 included, 0 excluded) Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Generating table : Author.java Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Tables generated : Total: 680.464ms, +558.284ms Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Generating Keys : C:/workspace/MySQLTest/src/test/generated/tables Nov 1, 2011 7:25:08 PM org.jooq.impl.JooqLogger info INFO: Keys generated : Total: 718.621ms, +38.157ms Nov 1, 2011 7:25:08 PM org.jooq.impl.JooqLogger info INFO: Generating records : C:/workspace/MySQLTest/src/test/generated/tables/records Nov 1, 2011 7:25:08 PM org.jooq.impl.JooqLogger info INFO: Generating record : AuthorRecord.java Nov 1, 2011 7:25:08 PM org.jooq.impl.JooqLogger info INFO: Table records generated : Total: 782.545ms, +63.924ms Nov 1, 2011 7:25:08 PM org.jooq.impl.JooqLogger info INFO: Routines fetched : 0 (0 included, 0 excluded) Nov 1, 2011 7:25:08 PM org.jooq.impl.JooqLogger info INFO: Packages fetched : 0 (0 included, 0 excluded) Nov 1, 2011 7:25:08 PM org.jooq.impl.JooqLogger info INFO: GENERATION FINISHED! : Total: 791.688ms, +9.143ms
3.4.1.4. Step 4: Connect to your database
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Let's just write a vanilla main class in the project containing the generated classes:
// For convenience, always static import your generated tables and jOOQ functions to decrease verbosity: import static test.generated.Tables.*; import static org.jooq.impl.DSL.*; import java.sql.*; public class Main { public static void main(String[] args) { String userName = "root"; String password = ""; String url = "jdbc:mysql://localhost:3306/library"; // Connection is the only JDBC resource that we need // PreparedStatement and ResultSet are handled by jOOQ, internally try (Connection conn = DriverManager.getConnection(url, userName, password)) { // ... } // For the sake of this tutorial, let's keep exception handling simple catch (Exception e) { e.printStackTrace(); } } }
This is pretty standard code for establishing a MySQL connection.
3.4.1.5. Step 5: Querying
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Let's add a simple query constructed with jOOQ's query DSL:
DSLContext create = DSL.using(conn, SQLDialect.MYSQL); Result<Record> result = create.select().from(AUTHOR).fetch();
First get an instance of DSLContext
so we can write a simple SELECT
query. We pass an instance of the MySQL connection to DSL
. Note that the DSLContext doesn't close the connection. We'll have to do that ourselves.
We then use jOOQ's query DSL to return an instance of Result. We'll be using this result in the next step.
3.4.1.6. Step 6: Iterating
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
After the line where we retrieve the results, let's iterate over the results and print out the data:
for (Record r : result) { Integer id = r.getValue(AUTHOR.ID); String firstName = r.getValue(AUTHOR.FIRST_NAME); String lastName = r.getValue(AUTHOR.LAST_NAME); System.out.println("ID: " + id + " first name: " + firstName + " last name: " + lastName); }
The full program should now look like this:
package test; // For convenience, always static import your generated tables and // jOOQ functions to decrease verbosity: import static test.generated.Tables.*; import static org.jooq.impl.DSL.*; import java.sql.*; import org.jooq.*; import org.jooq.impl.*; public class Main { /** * @param args */ public static void main(String[] args) { String userName = "root"; String password = ""; String url = "jdbc:mysql://localhost:3306/library"; // Connection is the only JDBC resource that we need // PreparedStatement and ResultSet are handled by jOOQ, internally try (Connection conn = DriverManager.getConnection(url, userName, password)) { DSLContext create = DSL.using(conn, SQLDialect.MYSQL); Result<Record> result = create.select().from(AUTHOR).fetch(); for (Record r : result) { Integer id = r.getValue(AUTHOR.ID); String firstName = r.getValue(AUTHOR.FIRST_NAME); String lastName = r.getValue(AUTHOR.LAST_NAME); System.out.println("ID: " + id + " first name: " + firstName + " last name: " + lastName); } } // For the sake of this tutorial, let's keep exception handling simple catch (Exception e) { e.printStackTrace(); } } }
3.4.1.7. Step 7: Explore!
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
jOOQ has grown to be a comprehensive SQL library. For more information, please consider the documentation:
https://www.jooq.org/learn
... explore the Javadoc:
https://www.jooq.org/javadoc/latest/
... or join the news group:
https://groups.google.com/forum/#!forum/jooq-user
This tutorial is the courtesy of Ikai Lan. See the original source here:
http://ikaisays.com/2011/11/01/getting-started-with-jooq-a-tutorial/
3.4.2. Using jOOQ in modern IDEs
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Feel free to contribute a tutorial!
3.4.3. Using jOOQ with Spring and Apache DBCP
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
jOOQ and Spring are easy to integrate. In this example, we shall integrate:
- Apache DBCP (but you may as well use some other connection pool, like BoneCP, C3P0, HikariCP, and various others).
- Spring TX as the transaction management library.
- jOOQ as the SQL building and execution library.
Before you copy the manual examples, consider also these further resources:
- The complete example can also be downloaded from GitHub.
- Another example using Spring and Guice for transaction management can be downloaded from GitHub.
- Another, excellent tutorial by Petri Kainulainen can be found here.
Add the required Maven dependencies
For this example, we'll create the following Maven dependencies
<!-- Use this or the latest Spring RELEASE version --> <properties> <org.springframework.version>3.2.3.RELEASE</org.springframework.version> </properties> <dependencies> <!-- Database access --> <dependency> <groupId>org.jooq</groupId> <artifactId>jooq</artifactId> <version>3.5.4</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-dbcp2</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.3.168</version> </dependency> <!-- Logging --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.16</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.5</version> </dependency> <!-- Spring (transitive dependencies are not listed explicitly) --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${org.springframework.version}</version> </dependency> <!-- Testing --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <type>jar</type> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${org.springframework.version}</version> <scope>test</scope> </dependency> </dependencies>
Note that only the jOOQ Open Source Edition is available from Maven Central. If you're using the jOOQ Professional Edition or the jOOQ Enterprise Edition, you will have to manually install jOOQ in your local Nexus, or in your local Maven cache. For more information, please refer to the licensing pages.
Create a minimal Spring configuration file
The above dependencies are configured together using a Spring Beans configuration:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd"> <!-- This is needed if you want to use the @Transactional annotation --> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" > <!-- These properties are replaced by Maven "resources" --> <property name="url" value="${db.url}" /> <property name="driverClassName" value="${db.driver}" /> <property name="username" value="${db.username}" /> <property name="password" value="${db.password}" /> </bean> <!-- Configure Spring's transaction manager to use a DataSource --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- Configure jOOQ's ConnectionProvider to use Spring's TransactionAwareDataSourceProxy, which can dynamically discover the transaction context --> <bean id="transactionAwareDataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"> <constructor-arg ref="dataSource" /> </bean> <bean class="org.jooq.impl.DataSourceConnectionProvider" name="connectionProvider"> <constructor-arg ref="transactionAwareDataSource" /> </bean> <!-- Configure the DSL object, optionally overriding jOOQ Exceptions with Spring Exceptions --> <bean id="dsl" class="org.jooq.impl.DefaultDSLContext"> <constructor-arg ref="config" /> </bean> <bean id="exceptionTranslator" class="org.jooq.example.spring.exception.ExceptionTranslator" /> <!-- Invoking an internal, package-private constructor for the example Implement your own Configuration for more reliable behaviour --> <bean class="org.jooq.impl.DefaultConfiguration" name="config"> <property name="SQLDialect"><value type="org.jooq.SQLDialect">H2</value></property> <property name="connectionProvider" ref="connectionProvider" /> <property name="executeListenerProvider"> <array> <bean class="org.jooq.impl.DefaultExecuteListenerProvider"> <constructor-arg index="0" ref="exceptionTranslator"/> </bean> </array> </property> </bean> <!-- This is the "business-logic" --> <bean id="books" class="org.jooq.example.spring.impl.DefaultBookService"/> </beans>
Run a query using the above configuration:
With the above configuration, you should be ready to run queries pretty quickly. For instance, in an integration-test, you could use Spring to run JUnit:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"/jooq-spring.xml"}) public class QueryTest { @Autowired DSLContext create; @Test public void testJoin() throws Exception { // All of these tables were generated by jOOQ's Maven plugin Book b = BOOK.as("b"); Author a = AUTHOR.as("a"); BookStore s = BOOK_STORE.as("s"); BookToBookStore t = BOOK_TO_BOOK_STORE.as("t"); Result<Record3<String, String, Integer>> result = create.select(a.FIRST_NAME, a.LAST_NAME, countDistinct(s.NAME)) .from(a) .join(b).on(b.AUTHOR_ID.eq(a.ID)) .join(t).on(t.BOOK_ID.eq(b.ID)) .join(s).on(t.BOOK_STORE_NAME.eq(s.NAME)) .groupBy(a.FIRST_NAME, a.LAST_NAME) .orderBy(countDistinct(s.NAME).desc()) .fetch(); assertEquals(2, result.size()); assertEquals("Paulo", result.getValue(0, a.FIRST_NAME)); assertEquals("George", result.getValue(1, a.FIRST_NAME)); assertEquals("Coelho", result.getValue(0, a.LAST_NAME)); assertEquals("Orwell", result.getValue(1, a.LAST_NAME)); assertEquals(Integer.valueOf(3), result.getValue(0, countDistinct(s.NAME))); assertEquals(Integer.valueOf(2), result.getValue(1, countDistinct(s.NAME))); } }
Run a queries in an explicit transaction:
The following example shows how you can use Spring's TransactionManager to explicitly handle transactions:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"/jooq-spring.xml"}) @TransactionConfiguration(transactionManager="transactionManager") public class TransactionTest { @Autowired DSLContext dsl; @Autowired DataSourceTransactionManager txMgr; @Autowired BookService books; @After public void teardown() { // Delete all books that were created in any test dsl.delete(BOOK).where(BOOK.ID.gt(4)).execute(); } @Test public void testExplicitTransactions() { boolean rollback = false; TransactionStatus tx = txMgr.getTransaction(new DefaultTransactionDefinition()); try { // This is a "bug". The same book is created twice, resulting in a // constraint violation exception for (int i = 0; i < 2; i++) dsl.insertInto(BOOK) .set(BOOK.ID, 5) .set(BOOK.AUTHOR_ID, 1) .set(BOOK.TITLE, "Book 5") .execute(); Assert.fail(); } // Upon the constraint violation, we explicitly roll back the transaction. catch (DataAccessException e) { txMgr.rollback(tx); rollback = true; } assertEquals(4, dsl.fetchCount(BOOK)); assertTrue(rollback); } }
Run queries using declarative transactions
Spring-TX has very powerful means to handle transactions declaratively, using the @Transactional
annotation. The BookService
that we had defined in the previous Spring configuration can be seen here:
public interface BookService { /** * Create a new book. * <p> * The implementation of this method has a bug, which causes this method to * fail and roll back the transaction. */ @Transactional void create(int id, int authorId, String title); }
And here is how we interact with it:
@Test public void testDeclarativeTransactions() { boolean rollback = false; try { // The service has a "bug", resulting in a constraint violation exception books.create(5, 1, "Book 5"); Assert.fail(); } catch (DataAccessException ignore) { rollback = true; } assertEquals(4, dsl.fetchCount(BOOK)); assertTrue(rollback); }
Run queries using jOOQ's transaction API
jOOQ has its own programmatic transaction API that can be used with Spring transactions by implementing the jOOQ org.jooq.TransactionProvider SPI and passing that to your jOOQ Configuration. More details about this transaction API can be found in the manual's section about transaction management.
You can try the above example yourself by downloading it from GitHub.
3.4.4. Using jOOQ with Flyway
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
When performing database migrations, we at Data Geekery recommend using jOOQ with Flyway - Database Migrations Made Easy. In this chapter, we're going to look into a simple way to get started with the two frameworks.
Philosophy
There are a variety of ways how jOOQ and Flyway could interact with each other in various development setups. In this tutorial we're going to show just one variant of such framework team play - a variant that we find particularly compelling for most use cases.
The general philosophy behind the following approach can be summarised as this:
- 1. Database increment
- 2. Database migration
- 3. Code re-generation
- 4. Development
The four steps above can be repeated time and again, every time you need to modify something in your database. More concretely, let's consider:
- 1. Database increment - You need a new column in your database, so you write the necessary DDL in a Flyway script
- 2. Database migration - This Flyway script is now part of your deliverable, which you can share with all developers who can migrate their databases with it, the next time they check out your change
- 3. Code re-generation - Once the database is migrated, you regenerate all jOOQ artefacts (see code generation), locally
- 4. Development - You continue developing your business logic, writing code against the udpated, generated database schema
Maven Project Configuration - Properties
The following properties are defined in our pom.xml, to be able to reuse them between plugin configurations:
<properties> <db.url>jdbc:h2:~/flyway-test</db.url> <db.username>sa</db.username> </properties>
0. Maven Project Configuration - Dependencies
While jOOQ and Flyway could be used in standalone migration scripts, in this tutorial, we'll be using Maven for the standard project setup. You will also find the source code of this tutorial on GitHub at https://github.com/jOOQ/jOOQ/tree/main/jOOQ-examples/jOOQ-flyway-example, and the full pom.xml file here.
These are the dependencies that we're using in our Maven configuration:
<!-- We'll add the latest version of jOOQ and our JDBC driver - in this case H2 --> <dependency> <groupId>org.jooq</groupId> <artifactId>jooq</artifactId> <version>3.5.4</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.177</version> </dependency> <!-- For improved logging, we'll be using log4j via slf4j to see what's going on during migration and code generation --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.16</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.5</version> </dependency> <!-- To ensure our code is working, we're using JUnit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency>
0. Maven Project Configuration - Plugins
After the dependencies, let's simply add the Flyway and jOOQ Maven plugins like so. The Flyway plugin:
<plugin> <groupId>org.flywaydb</groupId> <artifactId>flyway-maven-plugin</artifactId> <version>3.0</version> <!-- Note that we're executing the Flyway plugin in the "generate-sources" phase --> <executions> <execution> <phase>generate-sources</phase> <goals> <goal>migrate</goal> </goals> </execution> </executions> <!-- Note that we need to prefix the db/migration path with filesystem: to prevent Flyway from looking for our migration scripts only on the classpath --> <configuration> <url>${db.url}</url> <user>${db.username}</user> <locations> <location>filesystem:src/main/resources/db/migration</location> </locations> </configuration> </plugin>
The above Flyway Maven plugin configuration will read and execute all database migration scripts from src/main/resources/db/migration
prior to compiling Java source code. While the official Flyway documentation suggests that migrations be done in the compile
phase, the jOOQ code generator relies on such migrations having been done prior to code generation.
After the Flyway plugin, we'll add the jOOQ Maven Plugin. For more details, please refer to the manual's section about the code generation configuration.
<plugin> <groupId>org.jooq</groupId> <artifactId>jooq-codegen-maven</artifactId> <version>${org.jooq.version}</version> <!-- The jOOQ code generation plugin is also executed in the generate-sources phase, prior to compilation --> <executions> <execution> <phase>generate-sources</phase> <goals> <goal>generate</goal> </goals> </execution> </executions> <!-- This is a minimal working configuration. See the manual's section about the code generator for more details --> <configuration> <jdbc> <url>${db.url}</url> <user>${db.username}</user> </jdbc> <generator> <database> <includes>.*</includes> <inputSchema>FLYWAY_TEST</inputSchema> </database> <target> <packageName>org.jooq.example.flyway.db.h2</packageName> <directory>target/generated-sources/jooq-h2</directory> </target> </generator> </configuration> </plugin>
This configuration will now read the FLYWAY_TEST
schema and reverse-engineer it into the target/generated-sources/jooq-h2
directory, and within that, into the org.jooq.example.flyway.db.h2
package.
1. Database increments
Now, when we start developing our database. For that, we'll create database increment scripts, which we put into the src/main/resources/db/migration
directory, as previously configured for the Flyway plugin. We'll add these files:
- V1__initialise_database.sql
- V2__create_author_table.sql
- V3__create_book_table_and_records.sql
These three scripts model our schema versions 1-3 (note the capital V!). Here are the scripts' contents
-- V1__initialise_database.sql DROP SCHEMA flyway_test IF EXISTS; CREATE SCHEMA flyway_test;
-- V2__create_author_table.sql CREATE SEQUENCE flyway_test.s_author_id START WITH 1; CREATE TABLE flyway_test.author ( id INT NOT NULL, first_name VARCHAR(50), last_name VARCHAR(50) NOT NULL, date_of_birth DATE, year_of_birth INT, address VARCHAR(50), CONSTRAINT pk_author PRIMARY KEY (ID) );
-- V3__create_book_table_and_records.sql CREATE TABLE flyway_test.book ( id INT NOT NULL, author_id INT NOT NULL, title VARCHAR(400) NOT NULL, CONSTRAINT pk_book PRIMARY KEY (id), CONSTRAINT fk_book_author_id FOREIGN KEY (author_id) REFERENCES flyway_test.author(id) ); INSERT INTO flyway_test.author VALUES (next value for flyway_test.s_author_id, 'George', 'Orwell', '1903-06-25', 1903, null); INSERT INTO flyway_test.author VALUES (next value for flyway_test.s_author_id, 'Paulo', 'Coelho', '1947-08-24', 1947, null); INSERT INTO flyway_test.book VALUES (1, 1, '1984'); INSERT INTO flyway_test.book VALUES (2, 1, 'Animal Farm'); INSERT INTO flyway_test.book VALUES (3, 2, 'O Alquimista'); INSERT INTO flyway_test.book VALUES (4, 2, 'Brida');
2. Database migration and 3. Code regeneration
The above three scripts are picked up by Flyway and executed in the order of the versions. This can be seen very simply by executing:
mvn clean install
And then observing the log output from Flyway...
[INFO] --- flyway-maven-plugin:3.0:migrate (default) @ jooq-flyway-example --- [INFO] Database: jdbc:h2:~/flyway-test (H2 1.4) [INFO] Validated 3 migrations (execution time 00:00.004s) [INFO] Creating Metadata table: "PUBLIC"."schema_version" [INFO] Current version of schema "PUBLIC": << Empty Schema >> [INFO] Migrating schema "PUBLIC" to version 1 [INFO] Migrating schema "PUBLIC" to version 2 [INFO] Migrating schema "PUBLIC" to version 3 [INFO] Successfully applied 3 migrations to schema "PUBLIC" (execution time 00:00.073s).
... and from jOOQ on the console:
[INFO] --- jooq-codegen-maven:3.5.4:generate (default) @ jooq-flyway-example --- [INFO] --- jooq-codegen-maven:3.5.4:generate (default) @ jooq-flyway-example --- [INFO] Using this configuration: ... [INFO] Generating schemata : Total: 1 [INFO] Generating schema : FlywayTest.java [INFO] ---------------------------------------------------------- [....] [INFO] GENERATION FINISHED! : Total: 337.576ms, +4.299ms
4. Development
Note that all of the previous steps are executed automatically, every time someone adds new migration scripts to the Maven module. For instance, a team member might have committed a new migration script, you check it out, rebuild and get the latest jOOQ-generated sources for your own development or integration-test database.
Now, that these steps are done, you can proceed writing your database queries. Imagine the following test case
import org.jooq.Result; import org.jooq.impl.DSL; import org.junit.Test; import java.sql.DriverManager; import static java.util.Arrays.asList; import static org.jooq.example.flyway.db.h2.Tables.*; import static org.junit.Assert.assertEquals; public class AfterMigrationTest { @Test public void testQueryingAfterMigration() throws Exception { try (Connection c = DriverManager.getConnection("jdbc:h2:~/flyway-test", "sa", "")) { Result<?> result = DSL.using(c) .select( AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, BOOK.ID, BOOK.TITLE ) .from(AUTHOR) .join(BOOK) .on(AUTHOR.ID.eq(BOOK.AUTHOR_ID)) .orderBy(BOOK.ID.asc()) .fetch(); assertEquals(4, result.size()); assertEquals(asList(1, 2, 3, 4), result.getValues(BOOK.ID)); } } }
Reiterate
The power of this approach becomes clear once you start performing database modifications this way. Let's assume that the French guy on our team prefers to have things his way:
-- V4__le_french.sql ALTER TABLE flyway_test.book ALTER COLUMN title RENAME TO le_titre;
They check it in, you check out the new database migration script, run
mvn clean install
And then observing the log output:
[INFO] --- flyway-maven-plugin:3.0:migrate (default) @ jooq-flyway-example --- [INFO] --- flyway-maven-plugin:3.0:migrate (default) @ jooq-flyway-example --- [INFO] Database: jdbc:h2:~/flyway-test (H2 1.4) [INFO] Validated 4 migrations (execution time 00:00.005s) [INFO] Current version of schema "PUBLIC": 3 [INFO] Migrating schema "PUBLIC" to version 4 [INFO] Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.016s).
So far so good, but later on:
[ERROR] COMPILATION ERROR : [INFO] ------------------------------------------------------------- [ERROR] C:\...\jOOQ-flyway-example\src\test\java\AfterMigrationTest.java:[24,19] error: cannot find symbol [INFO] 1 error
When we go back to our Java integration test, we can immediately see that the TITLE column is still being referenced, but it no longer exists:
public class AfterMigrationTest { @Test public void testQueryingAfterMigration() throws Exception { try (Connection c = DriverManager.getConnection("jdbc:h2:~/flyway-test", "sa", "")) { Result<?> result = DSL.using(c) .select( AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, BOOK.ID, BOOK.TITLE // ^^^^^ This column no longer exists. We'll have to rename it to LE_TITRE ) .from(AUTHOR) .join(BOOK) .on(AUTHOR.ID.eq(BOOK.AUTHOR_ID)) .orderBy(BOOK.ID.asc()) .fetch(); assertEquals(4, result.size()); assertEquals(asList(1, 2, 3, 4), result.getValues(BOOK.ID)); } } }
Conclusion
This tutorial shows very easily how you can build a rock-solid development process using Flyway and jOOQ to prevent SQL-related errors very early in your development lifecycle - immediately at compile time, rather than in production!
Please, visit the Flyway website for more information about Flyway.
3.5. jOOQ and Java 8
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Java 8 has introduced a great set of enhancements, among which lambda expressions and the new java.util.stream.Stream. These new constructs align very well with jOOQ's fluent API as can be seen in the following examples:
jOOQ and lambda expressions
jOOQ's RecordMapper API is fully Java-8-ready, which basically means that it is a SAM (Single Abstract Method) type, which can be instanciated using a lambda expression. Consider this example:
try (Connection c = getConnection()) { String sql = "select schema_name, is_default " + "from information_schema.schemata " + "order by schema_name"; DSL.using(c) .fetch(sql) // We can use lambda expressions to map jOOQ Records .map(rs -> new Schema( rs.getValue("SCHEMA_NAME", String.class), rs.getValue("IS_DEFAULT", boolean.class) )) // ... and then profit from the new Collection methods .forEach(System.out::println); }
The above example shows how jOOQ's Result.map() method can receive a lambda expression that implements RecordMapper to map from jOOQ Records to your custom types.
jOOQ and the Streams API
jOOQ's Result type extends java.util.List, which opens up access to a variety of new Java features in Java 8. The following example shows how easy it is to transform a jOOQ Result
containing INFORMATION_SCHEMA
meta data to produce DDL statements:
DSL.using(c) .select( COLUMNS.TABLE_NAME, COLUMNS.COLUMN_NAME, COLUMNS.TYPE_NAME ) .from(COLUMNS) .orderBy( COLUMNS.TABLE_CATALOG, COLUMNS.TABLE_SCHEMA, COLUMNS.TABLE_NAME, COLUMNS.ORDINAL_POSITION ) .fetch() // jOOQ ends here .stream() // JDK 8 Streams start here .collect(groupingBy( r -> r.getValue(COLUMNS.TABLE_NAME), LinkedHashMap::new, mapping( r -> new Column( r.getValue(COLUMNS.COLUMN_NAME), r.getValue(COLUMNS.TYPE_NAME) ), toList() ) )) .forEach( (table, columns) -> { // Just emit a CREATE TABLE statement System.out.println( "CREATE TABLE " + table + " ("); // Map each "Column" type into a String // containing the column specification, // and join them using comma and // newline. Done! System.out.println( columns.stream() .map(col -> " " + col.name + " " + col.type) .collect(Collectors.joining(",\n")) ); System.out.println(");"); } );
The above example is explained more in depth in this blog post: http://blog.jooq.org/2014/04/11/java-8-friday-no-more-need-for-orms/. For more information about Java 8, consider these resources:
3.6. jOOQ and JavaFX
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
One of the major improvements of Java 8 is the introduction of JavaFX into the JavaSE. With jOOQ and Java 8 Streams and lambdas, it is now very easy and idiomatic to transform SQL results into JavaFX XYChart.Series
or other, related objects:
Creating a bar chart from a jOOQ Result
As we've seen in the previous section about jOOQ and Java 8, jOOQ integrates seamlessly with Java 8's Streams API. The fluent style can be maintained throughout the data transformation chain.
In this example, we're going to use Open Data from the world bank to show a comparison of countries GDP and debts:
DROP SCHEMA IF EXISTS world; CREATE SCHEMA world; CREATE TABLE world.countries ( code CHAR(2) NOT NULL, year INT NOT NULL, gdp_per_capita DECIMAL(10, 2) NOT NULL, govt_debt DECIMAL(10, 2) NOT NULL ); INSERT INTO world.countries VALUES ('CA', 2009, 40764, 51.3), ('CA', 2010, 47465, 51.4), ('CA', 2011, 51791, 52.5), ('CA', 2012, 52409, 53.5), ('DE', 2009, 40270, 47.6), ('DE', 2010, 40408, 55.5), ('DE', 2011, 44355, 55.1), ('DE', 2012, 42598, 56.9), ('FR', 2009, 40488, 85.0), ('FR', 2010, 39448, 89.2), ('FR', 2011, 42578, 93.2), ('FR', 2012, 39759,103.8), ('GB', 2009, 35455,121.3), ('GB', 2010, 36573, 85.2), ('GB', 2011, 38927, 99.6), ('GB', 2012, 38649,103.2), ('IT', 2009, 35724,121.3), ('IT', 2010, 34673,119.9), ('IT', 2011, 36988,113.0), ('IT', 2012, 33814,131.1), ('JP', 2009, 39473,166.8), ('JP', 2010, 43118,174.8), ('JP', 2011, 46204,189.5), ('JP', 2012, 46548,196.5), ('RU', 2009, 8616, 8.7), ('RU', 2010, 10710, 9.1), ('RU', 2011, 13324, 9.3), ('RU', 2012, 14091, 9.4), ('US', 2009, 46999, 76.3), ('US', 2010, 48358, 85.6), ('US', 2011, 49855, 90.1), ('US', 2012, 51755, 93.8);
Once this data is set up (e.g. in an H2 or PostgreSQL database), we'll run jOOQ's code generator and implement the following code to display our chart:
CategoryAxis xAxis = new CategoryAxis(); NumberAxis yAxis = new NumberAxis(); xAxis.setLabel("Country"); yAxis.setLabel("% of GDP"); BarChart<String, Number> bc = new BarChart<String, Number>(xAxis, yAxis); bc.setTitle("Government Debt"); bc.getData().addAll( // SQL data transformation, executed in the database // ------------------------------------------------- DSL.using(connection) .select( COUNTRIES.YEAR, COUNTRIES.CODE, COUNTRIES.GOVT_DEBT) .from(COUNTRIES) .join( table( select(COUNTRIES.CODE, avg(COUNTRIES.GOVT_DEBT).as("avg")) .from(COUNTRIES) .groupBy(COUNTRIES.CODE) ).as("c1") ) .on(COUNTRIES.CODE.eq(fieldByName(String.class, "c1", COUNTRIES.CODE.getName()))) // order countries by their average projected value .orderBy( fieldByName("avg"), COUNTRIES.CODE, COUNTRIES.YEAR) // The result produced by the above statement looks like this: // +----+----+---------+ // |year|code|govt_debt| // +----+----+---------+ // |2009|RU | 8.70| // |2010|RU | 9.10| // |2011|RU | 9.30| // |2012|RU | 9.40| // |2009|CA | 51.30| // +----+----+---------+ // Java data transformation, executed in application memory // -------------------------------------------------------- // Group results by year, keeping sort order in place .fetchGroups(COUNTRIES.YEAR) // Stream<Entry<Integer, Result<Record3<BigDecimal, String, Integer>>>> .entrySet() .stream() // Map each entry into a { Year -> Projected value } series .map(entry -> new XYChart.Series<>( entry.getKey().toString(), observableArrayList( // Map each country record into a chart Data object entry.getValue() .map(country -> new XYChart.Data<String, Number>( country.getValue(COUNTRIES.CODE), country.getValue(COUNTRIES.GOVT_DEBT) )) ) )) .collect(toList()) );
The above example uses basic SQL-92 syntax where the countries are ordered using aggregate information from a nested SELECT, which is supported in all databases. If you're using a database that supports window functions, e.g. PostgreSQL or any commercial database, you could have also written a simpler query like this:00
DSL.using(connection) .select( COUNTRIES.YEAR, COUNTRIES.CODE, COUNTRIES.GOVT_DEBT) .from(COUNTRIES) // order countries by their average projected value .orderBy( DSL.avg(COUNTRIES.GOVT_DEBT).over(partitionBy(COUNTRIES.CODE)), COUNTRIES.CODE, COUNTRIES.YEAR) .fetch() ; return bc;
When executed, we'll get nice-looking bar charts like these:
The complete example can be downloaded and run from GitHub:
https://github.com/jOOQ/jOOQ/tree/main/jOOQ-examples/jOOQ-javafx-example
3.7. jOOQ and Nashorn
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
With Java 8 and the new built-in JavaScript engine Nashorn, a whole new ecosystem of software can finally make easy use of jOOQ in server-side JavaScript. A very simple example can be seen here:
// Let's assume these objects were generated // by the jOOQ source code generator var Tables = Java.type("org.jooq.db.h2.information_schema.Tables"); var t = Tables.TABLES; var c = Tables.COLUMNS; // This is the equivalent of Java's static imports var count = DSL.count; var row = DSL.row; // We can now execute the following query: print( DSL.using(conn) .select( t.TABLE_SCHEMA, t.TABLE_NAME, c.COLUMN_NAME) .from(t) .join(c) .on(row(t.TABLE_SCHEMA, t.TABLE_NAME) .eq(c.TABLE_SCHEMA, c.TABLE_NAME)) .orderBy( t.TABLE_SCHEMA.asc(), t.TABLE_NAME.asc(), c.ORDINAL_POSITION.asc()) .fetch() );
3.8. jOOQ and Scala
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
As any other library, jOOQ can be easily used in Scala, taking advantage of the many Scala language features such as for example:
- Optional "." to dereference methods from expressions
- Optional "(" and ")" to delimit method argument lists
- Optional ";" at the end of a Scala statement
- Type inference using "var" and "val" keywords
- Lambda expressions and for-comprehension syntax for record iteration and data type conversion
But jOOQ also leverages other useful Scala features, such as
- implicit defs for operator overloading
- Scala Macros (soon to come)
All of the above heavily improve jOOQ's querying DSL API experience for Scala developers.
A short example jOOQ application in Scala might look like this:
import collection.JavaConversions._ // Import implicit defs for iteration over org.jooq.Result // import java.sql.DriverManager // // import org.jooq._ // import org.jooq.impl._ // import org.jooq.impl.DSL._ // import org.jooq.scala.example.h2.Tables._ // import org.jooq.scala.Conversions._ // Import implicit defs for overloaded jOOQ/SQL operators // Note, in jOOQ 3.9, the location of this class has changed // See https://github.com/jOOQ/jOOQ/issues/2684 object Test { // def main(args: Array[String]): Unit = { // val c = DriverManager.getConnection("jdbc:h2:~/test", "sa", ""); // Standard JDBC connection val e = DSL.using(c, SQLDialect.H2); // val x = AUTHOR as "x" // SQL-esque table aliasing // for (r <- e // Iteration over Result. "r" is an org.jooq.Record3 select ( // BOOK.ID * BOOK.AUTHOR_ID, // Using the overloaded "*" operator BOOK.ID + BOOK.AUTHOR_ID * 3 + 4, // Using the overloaded "+" operator BOOK.TITLE || " abc" || " xy" // Using the overloaded "||" operator ) // from BOOK // No need to use parentheses or "." here leftOuterJoin ( // select (x.ID, x.YEAR_OF_BIRTH) // Dereference fields from aliased table from x // limit 1 // asTable x.getName() // ) // on BOOK.AUTHOR_ID === x.ID // Using the overloaded "===" operator where (BOOK.ID <> 2) // Using the olerloaded "<>" operator or (BOOK.TITLE in ("O Alquimista", "Brida")) // Neat IN predicate expression fetch // ) { // println(r) // } // } // }
For more details about jOOQ's Scala integration, please refer to the manual's section about SQL building with Scala.
3.9. jOOQ and Groovy
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
As any other library, jOOQ can be easily used in Groovy, taking advantage of the many Groovy language features such as for example:
- Optional ";" at the end of a Groovy statement
- Type inference for local variables
While this is less impressive than the features available from a Scala integration, it is still useful for those of you using jOOQ's querying DSL with Groovy.
A short example jOOQ application in Groovy might look like this:
package org.jooq.groovy import static org.jooq.impl.DSL.* import static org.jooq.groovy.example.h2.Tables.* import groovy.sql.Sql import org.jooq.* import org.jooq.impl.DSL sql = Sql.newInstance('jdbc:h2:~/groovy-test', 'sa', '', 'org.h2.Driver'); a = AUTHOR.as("a"); b = BOOK.as("b") DSL.using(sql.connection) .select(a.FIRST_NAME, a.LAST_NAME, b.TITLE) .from(a) .join(b).on(a.ID.eq(b.AUTHOR_ID)) .fetchInto ({ r -> println( "${r.getValue(a.FIRST_NAME)} " + "${r.getValue(a.LAST_NAME)} " + "has written ${r.getValue(b.TITLE)}" ) } as RecordHandler)
Note that while Groovy supports some means of operator overloading, we think that these means should be avoided in a jOOQ integration. For instance, a + b
in Groovy maps to a formal a.plus(b)
method invocation, and jOOQ provides the required synonyms in its API to help you write such expressions. Nonetheless, Groovy only offers little typesafety, and as such, operator overloading can lead to many runtime issues.
Another caveat of Groovy operator overloading is the fact that operators such as ==
or >=
map to a.equals(b)
, a.compareTo(b) == 0
, a.compareTo(b) >= 0
respectively. This behaviour does not make sense in a fluent API such as jOOQ.
3.10. jOOQ and NoSQL
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
jOOQ users often get excited about jOOQ's intuitive API and would then wish for NoSQL support.
There are a variety of NoSQL databases that implement some sort of proprietary query language. Some of these query languages even look like SQL. Examples are JCR-SQL2, CQL (Cassandra Query Language), Cypher (Neo4j's Query Language), SOQL (Salesforce Query Language) and many more.
Mapping the jOOQ API onto these alternative query languages would be a very poor fit and a leaky abstraction. We believe in the power and expressivity of the SQL standard and its various dialects. Databases that extend this standard too much, or implement it not thoroughly enough are often not suitable targets for jOOQ. It would be better to build a new, dedicated API for just that one particular query language.
jOOQ is about SQL, and about SQL alone. Read more about our visions in the manual's preface.
3.11. jOOQ and JPA
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Just because you're using jOOQ doesn't mean you have to use it for everything!
When introducing jOOQ into an existing application that uses JPA, the common question is always: "Should we replace JPA by jOOQ?" and "How do we proceed doing that?"
Beware that jOOQ is not a replacement for JPA. Think of jOOQ as a complement. JPA (and ORMs in general) try to solve the object graph persistence problem. In short, this problem is about
- Loading an entity graph into client memory from a database
- Manipulating that graph in the client
- Storing the modification back to the database
As the above graph gets more complex, a lot of tricky questions arise like:
- What's the optimal order of SQL DML operations for loading and storing entities?
- How can we batch the commands more efficiently?
- How can we keep the transaction footprint as low as possible without compromising on ACID?
- How can we implement optimistic locking?
jOOQ only has some of the answers.
While jOOQ does offer updatable records that help running simple CRUD, a batch API, optimistic locking capabilities, jOOQ mainly focuses on executing actual SQL statements.
SQL is the preferred language of database interaction, when any of the following are given:
- You run reports and analytics on large data sets directly in the database
- You import / export data using ETL
- You run complex business logic as SQL queries
Whenever SQL is a good fit, jOOQ is a good fit. Whenever you're operating and persisting the object graph, JPA is a good fit.
3.12. Build your own
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In order to build jOOQ (Open Source Edition) yourself, please download the sources from https://github.com/jOOQ/jOOQ and use Maven to build jOOQ, preferably in Eclipse. jOOQ requires Java 6+ to compile and run.
Some useful hints to build jOOQ yourself:
- Get the latest version of Git or EGit
- Get the latest version of Maven or M2E
- Check out the jOOQ sources from https://github.com/jOOQ/jOOQ
- Optionally, import Maven artefacts into an Eclipse workspace using the following command (see the maven-eclipse-plugin documentation for details):
-
mvn eclipse:eclipse
-
- Build the
jooq-parent
artefact by using any of these commands:-
mvn clean package
create .jar files in${project.build.directory}
-
mvn clean install
install the .jar files in your local repository (e.g.~/.m2
) -
mvn clean {goal} -Dmaven.test.skip=true
don't run unit tests when building artefacts
-
3.13. jOOQ and backwards-compatibility
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Semantic versioning
jOOQ's understanding of backwards compatibility is inspired by the rules of semantic versioning according to http://semver.org. Those rules impose a versioning scheme [X].[Y].[Z] that can be summarised as follows:
- If a patch release includes bugfixes, performance improvements and API-irrelevant new features, [Z] is incremented by one.
- If a minor release includes backwards-compatible, API-relevant new features, [Y] is incremented by one and [Z] is reset to zero.
- If a major release includes backwards-incompatible, API-relevant new features, [X] is incremented by one and [Y], [Z] are reset to zero.
jOOQ's understanding of backwards-compatibility
Backwards-compatibility is important to jOOQ. You've chosen jOOQ as a strategic SQL engine and you don't want your SQL to break.
However, there are some elements of API evolution that would be considered backwards-incompatible in other APIs, but not in jOOQ. As discussed later on in the section about jOOQ's query DSL API, much of jOOQ's API is indeed an internal domain-specific language implemented mostly using Java interfaces. Adding language elements to these interfaces means any of these actions:
- Adding methods to the interface
- Overloading methods for convenience
- Changing the type hierarchy of interfaces
It becomes obvious that it would be impossible to add new language elements (e.g. new SQL functions, new SELECT clauses) to the API without breaking any client code that actually implements those interfaces. Hence, the following rules should be observed:
- jOOQ's DSL interfaces should not be implemented by client code! Extend only those extension points that are explicitly documented as "extendable" (e.g. custom QueryParts).
- Generated code implements such interfaces and extends internal classes, and as such is recommended to be re-generated with a matching code generator version every time the runtime library is upgraded.
- Binary compatibility can be expected from patch releases, but not from minor releases as it is not practical to maintain binary compatibility in an internal DSL.
- Source compatibility can be expected from patch and minor releases.
- Behavioural compatibility can be expected from patch and minor releases.
- Any jOOQ SPI
XYZ
that is meant to be implemented ships with aDefaultXYZ
orAbstractXYZ
, which can be used safely as a default implementation.
jOOQ-codegen and jOOQ-meta
While a reasonable amount of care is spent to maintain these two modules under the rules of semantic versioning, it may well be that minor releases introduce backwards-incompatible changes. This will be announced in the respective release notes and should be the exception.
4. SQL building
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
SQL is a declarative language that is hard to integrate into procedural, object-oriented, functional or any other type of programming languages. jOOQ's philosophy is to give SQL the credit it deserves and integrate SQL itself as an "internal domain specific language" directly into Java.
With this philosophy in mind, SQL building is the main feature of jOOQ. All other features (such as SQL execution and code generation) are mere convenience built on top of jOOQ's SQL building capabilities.
This section explains all about the various syntax elements involved with jOOQ's SQL building capabilities. For a complete overview of all syntax elements, please refer to the manual's sections about SQL to DSL mapping rules.
4.1. The query DSL type
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
jOOQ exposes a lot of interfaces and hides most implementation facts from client code. The reasons for this are:
- Interface-driven design. This allows for modelling queries in a fluent API most efficiently
- Reduction of complexity for client code.
- API guarantee. You only depend on the exposed interfaces, not concrete (potentially dialect-specific) implementations.
The org.jooq.impl.DSL class is the main class from where you will create all jOOQ objects. It serves as a static factory for table expressions, column expressions (or "fields"), conditional expressions and many other QueryParts.
The static query DSL API
With jOOQ 2.0, static factory methods have been introduced in order to make client code look more like SQL. Ideally, when working with jOOQ, you will simply static import all methods from the DSL class:
import static org.jooq.impl.DSL.*;
Note, that when working with Eclipse, you could also add the DSL to your favourites. This will allow to access functions even more fluently:
concat(trim(FIRST_NAME), trim(LAST_NAME)); // ... which is in fact the same as: DSL.concat(DSL.trim(FIRST_NAME), DSL.trim(LAST_NAME));
4.1.1. DSL subclasses
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
There are a couple of subclasses for the general query DSL. Each SQL dialect has its own dialect-specific DSL. For instance, if you're only using the MySQL dialect, you can choose to reference the MySQLDSL instead of the standard DSL:
The advantage of referencing a dialect-specific DSL lies in the fact that you have access to more proprietary RDMBS functionality. This may include:
- MySQL's encryption functions
- PL/SQL constructs, pgplsql, or any other dialect's ROUTINE-language (maybe in the future)
4.2. The DSLContext API
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
DSLContext references a org.jooq.Configuration, an object that configures jOOQ's behaviour when executing queries (see SQL execution for more details). Unlike the static DSL, the DSLContext allow for creating SQL statements that are already "configured" and ready for execution.
Fluent creation of a DSLContext object
The DSLContext object can be created fluently from the DSL type:
// Create it from a pre-existing configuration DSLContext create = DSL.using(configuration); // Create it from ad-hoc arguments DSLContext create = DSL.using(connection, dialect);
If you do not have a reference to a pre-existing Configuration object (e.g. created from org.jooq.impl.DefaultConfiguration), the various overloaded DSL.using()
methods will create one for you.
Contents of a Configuration object
A Configuration can be supplied with these objects:
- org.jooq.SQLDialect : The dialect of your database. This may be any of the currently supported database types (see SQL Dialect for more details)
- org.jooq.conf.Settings : An optional runtime configuration (see Custom Settings for more details)
- org.jooq.ExecuteListenerProvider : An optional reference to a provider class that can provide execute listeners to jOOQ (see ExecuteListeners for more details)
- org.jooq.RecordMapperProvider : An optional reference to a provider class that can provide record mappers to jOOQ (see POJOs with RecordMappers for more details)
-
Any of these:
- java.sql.Connection : An optional JDBC Connection that will be re-used for the whole lifecycle of your Configuration (see Connection vs. DataSource for more details). For simplicity, this is the use-case referenced from this manual, most of the time.
- java.sql.DataSource : An optional JDBC DataSource that will be re-used for the whole lifecycle of your Configuration. If you prefer using DataSources over Connections, jOOQ will internally fetch new Connections from your DataSource, conveniently closing them again after query execution. This is particularly useful in J2EE or Spring contexts (see Connection vs. DataSource for more details)
- org.jooq.ConnectionProvider : A custom abstraction that is used by jOOQ to "acquire" and "release" connections. jOOQ will internally "acquire" new Connections from your ConnectionProvider, conveniently "releasing" them again after query execution. (see Connection vs. DataSource for more details)
Usage of DSLContext
Wrapping a Configuration object, a DSLContext can construct statements, for later execution. An example is given here:
// The DSLContext is "configured" with a Connection and a SQLDialect DSLContext create = DSL.using(connection, dialect); // This select statement contains an internal reference to the DSLContext's Configuration: Select<?> select = create.selectOne(); // Using the internally referenced Configuration, the select statement can now be executed: Result<?> result = select.fetch();
Note that you do not need to keep a reference to a DSLContext. You may as well inline your local variable, and fluently execute a SQL statement as such:
// Execute a statement from a single execution chain: Result<?> result = DSL.using(connection, dialect) .select() .from(BOOK) .where(BOOK.TITLE.like("Animal%")) .fetch();
4.2.1. SQL Dialect
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
While jOOQ tries to represent the SQL standard as much as possible, many features are vendor-specific to a given database and to its "SQL dialect". jOOQ models this using the org.jooq.SQLDialect enum type.
The SQL dialect is one of the main attributes of a Configuration. Queries created from DSLContexts will assume dialect-specific behaviour when rendering SQL and binding bind values.
Some parts of the jOOQ API are officially supported only by a given subset of the supported SQL dialects. For instance, the Oracle CONNECT BY clause, which is supported by the Oracle and CUBRID databases, is annotated with a org.jooq.Support annotation, as such:
/** * Add an Oracle-specific <code>CONNECT BY</code> clause to the query */ @Support({ SQLDialect.CUBRID, SQLDialect.ORACLE }) SelectConnectByConditionStep<R> connectBy(Condition condition);
jOOQ API methods which are not annotated with the org.jooq.Support annotation, or which are annotated with the Support annotation, but without any SQL dialects can be safely used in all SQL dialects. An example for this is the SELECT statement factory method:
/** * Create a new DSL select statement. */ @Support SelectSelectStep<R> select(Field<?>... fields);
jOOQ's SQL clause emulation capabilities
The aforementioned Support annotation does not only designate, which databases natively support a feature. It also indicates that a feature is emulated by jOOQ for some databases lacking this feature. An example of this is the DISTINCT predicate, a predicate syntax defined by SQL:1999 and implemented only by H2, HSQLDB, and Postgres:
A IS DISTINCT FROM B
Nevertheless, the IS DISTINCT FROM
predicate is supported by jOOQ in all dialects, as its semantics can be expressed with an equivalent CASE expression. For more details, see the manual's section about the DISTINCT predicate.
jOOQ and the Oracle SQL dialect
Oracle SQL is much more expressive than many other SQL dialects. It features many unique keywords, clauses and functions that are out of scope for the SQL standard. Some examples for this are
- The CONNECT BY clause, for hierarchical queries
- The PIVOT keyword for creating PIVOT tables
- Packages, object-oriented user-defined types, member procedures as described in the section about stored procedures and functions
- Advanced analytical functions as described in the section about window functions
jOOQ has a historic affinity to Oracle's SQL extensions. If something is supported in Oracle SQL, it has a high probability of making it into the jOOQ API
4.2.2. SQL Dialect Family
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In jOOQ 3.1, the notion of a SQLDialect.family()
was introduced, in order to group several similar SQL dialects into a common family. An example for this is SQL Server, which is supported by jOOQ in various versions:
- SQL Server: The "version-less" SQL Server version. This always maps to the latest supported version of SQL Server
- SQL Server 2012: The SQL Server version 2012
- SQL Server 2008: The SQL Server version 2008
In the above list, SQLSERVER
is both a dialect and a family of three dialects. This distinction is used internally by jOOQ to distinguish whether to use the OFFSET .. FETCH clause (SQL Server 2012), or whether to emulate it using ROW_NUMBER() OVER()
(SQL Server 2008).
4.2.3. Connection vs. DataSource
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Interact with JDBC Connections
While you can use jOOQ for SQL building only, you can also run queries against a JDBC java.sql.Connection. Internally, jOOQ creates java.sql.Statement or java.sql.PreparedStatement objects from such a Connection, in order to execute statements. The normal operation mode is to provide a Configuration with a JDBC Connection, whose lifecycle you will control yourself. This means that jOOQ will not actively close connections, rollback or commit transactions.
Note, in this case, jOOQ will internally use a org.jooq.impl.DefaultConnectionProvider, which you can reference directly if you prefer that. The DefaultConnectionProvider exposes various transaction-control methods, such as commit(), rollback(), etc.
Interact with JDBC DataSources
If you're in a J2EE or Spring context, however, you may wish to use a javax.sql.DataSource instead. Connections obtained from such a DataSource will be closed after query execution by jOOQ. The semantics of such a close operation should be the returning of the connection into a connection pool, not the actual closing of the underlying connection. Typically, this makes sense in an environment using distributed JTA transactions. An example of using DataSources with jOOQ can be seen in the tutorial section about using jOOQ with Spring.
Note, in this case, jOOQ will internally use a org.jooq.impl.DataSourceConnectionProvider, which you can reference directly if you prefer that.
Inject custom behaviour
If your specific environment works differently from any of the above approaches, you can inject your own custom implementation of a ConnectionProvider into jOOQ. This is the API contract you have to fulfil:
public interface ConnectionProvider { // Provide jOOQ with a connection Connection acquire() throws DataAccessException; // Get a connection back from jOOQ void release(Connection connection) throws DataAccessException; }
4.2.4. Custom data
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In advanced use cases of integrating your application with jOOQ, you may want to put custom data into your Configuration, which you can then access from your...
Here is an example of how to use the custom data API. Let's assume that you have written an ExecuteListener, that prevents INSERT
statements, when a given flag is set to true
:
// Implement an ExecuteListener public class NoInsertListener extends DefaultExecuteListener { @Override public void start(ExecuteContext ctx) { // This listener is active only, when your custom flag is set to true if (Boolean.TRUE.equals(ctx.configuration().data("com.example.my-namespace.no-inserts"))) { // If active, fail this execution, if an INSERT statement is being executed if (ctx.query() instanceof Insert) { throw new DataAccessException("No INSERT statements allowed"); } } } }
See the manual's section about ExecuteListeners to learn more about how to implement an ExecuteListener
.
Now, the above listener can be added to your Configuration, but you will also need to pass the flag to the Configuration
, in order for the listener to work:
// Create your Configuration Configuration configuration = new DefaultConfiguration().set(connection).set(dialect); // Set a new execute listener provider onto the configuration: configuration.set(new DefaultExecuteListenerProvider(new NoInsertListener())); // Use any String literal to identify your custom data configuration.data("com.example.my-namespace.no-inserts", true); // Try to execute an INSERT statement try { DSL.using(configuration) .insertInto(AUTHOR, AUTHOR.ID, AUTHOR.LAST_NAME) .values(1, "Orwell") .execute(); // You shouldn't get here Assert.fail(); } // Your NoInsertListener should be throwing this exception here: catch (DataAccessException expected) { Assert.assertEquals("No INSERT statements allowed", expected.getMessage()); }
Using the data()
methods, you can store and retrieve custom data in your Configurations
.
4.2.5. Custom ExecuteListeners
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
ExecuteListeners
are a useful tool to...
- implement custom logging
- apply triggers written in Java
- collect query execution statistics
ExecuteListeners are hooked into your Configuration by returning them from an org.jooq.ExecuteListenerProvider:
// Create your Configuration Configuration configuration = new DefaultConfiguration().set(connection).set(dialect); // Hook your listener providers into the configuration: configuration.set( new DefaultExecuteListenerProvider(new MyFirstListener()), new DefaultExecuteListenerProvider(new PerformanceLoggingListener()), new DefaultExecuteListenerProvider(new NoInsertListener()) );
See the manual's section about ExecuteListeners to see examples of such listener implementations.
4.2.6. Custom Settings
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The jOOQ Configuration allows for some optional configuration elements to be used by advanced users. The org.jooq.conf.Settings class is a JAXB-annotated type, that can be provided to a Configuration in several ways:
- In the DSLContext constructor (
DSL.using()
). This will override default settings below - in the org.jooq.impl.DefaultConfiguration constructor. This will override default settings below
- From a location specified by a JVM parameter: -Dorg.jooq.settings
- From the classpath at /jooq-settings.xml
- From the settings defaults, as specified in http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd
The most specific settings for a given context will apply.
If you wish to configure your settings through XML, but explicitly load them for a given Configuration
, you can do so as well, using JAXB:
Settings settings = JAXB.unmarshal(new File("/path/to/settings.xml"), Settings.class);
Example
For example, if you want to indicate to jOOQ, that it should inline all bind variables, and execute static java.sql.Statement instead of binding its variables to java.sql.PreparedStatement, you can do so by creating the following DSLContext:
Settings settings = new Settings(); settings.setStatementType(StatementType.STATIC_STATEMENT); DSLContext create = DSL.using(connection, dialect, settings);
More details
Please refer to the jOOQ runtime configuration XSD for more details:
http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd
4.2.6.1. Object qualification
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
By default, jOOQ fully qualifies all objects with their catalog and schema names, if such qualification is made available by the code generator. For instance, the following SQL statement containing full qualification may be produced by jOOQ code with seemingly no qualification:
-- Full qualification on columns and tables SELECT schema.table.column FROM schema.table
DSL.using(configuration) .select(TABLE.COLUMN) // Column only qualified with table .from(TABLE) // No qualification on table
While the jOOQ code is also implicitly fully qualified (see implied imports), it may not be desireable to use fully qualified object names in SQL. The renderSchema
setting is used for this.
Programmatic configuration
new Settings() .withRenderSchema(false) // Defaults to true
XML configuration
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd"> <renderSchema>false</renderSchema> </settings>
By turning off the rendering of full qualification as can be seen above, it will be possible to use code generated from one schema on an entirely different schema of the same structure, e.g. for multitenancy purposes.
More sophisticated multitenancy approaches are available through the render mapping feature.
4.2.6.2. Runtime schema and table mapping
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Mapping your DEV schema to a productive environment
You may wish to design your database in a way that you have several instances of your schema. This is useful when you want to cleanly separate data belonging to several customers / organisation units / branches / users and put each of those entities' data in a separate database or schema.
In our AUTHOR example this would mean that you provide a book reference database to several companies, such as My Book World and Books R Us. In that case, you'll probably have a schema setup like this:
- DEV: Your development schema. This will be the schema that you base code generation upon, with jOOQ
- MY_BOOK_WORLD: The schema instance for My Book World
- BOOKS_R_US: The schema instance for Books R Us
Mapping DEV to MY_BOOK_WORLD with jOOQ
When a user from My Book World logs in, you want them to access the MY_BOOK_WORLD schema using classes generated from DEV. This can be achieved with the org.jooq.conf.RenderMapping class, that you can equip your Configuration's settings with. Take the following example:
Programmatic configuration
Settings settings = new Settings() .withRenderMapping(new RenderMapping() .withSchemata( new MappedSchema().withInput("DEV") .withOutput("MY_BOOK_WORLD"), new MappedSchema().withInput("LOG") .withOutput("MY_BOOK_WORLD_LOG")));
XML configuration
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd"> <renderMapping> <schemata> <schema> <input>DEV</input> <output>MY_BOOK_WORLD</output> </schema> <schema> <input>LOG</input> <output>MY_BOOK_WORLD_LOG</output> </schema> </schemata> </renderMapping> </settings>
The query executed with a Configuration equipped with the above mapping will in fact produce this SQL statement:
SELECT * FROM MY_BOOK_WORLD.AUTHOR
DSL.using(connection, dialect, settings) .selectFrom(DEV.AUTHOR)
This works because AUTHOR
was generated from the DEV
schema, which is mapped to the MY_BOOK_WORLD
schema by the above settings.
Mapping of tables
Not only schemata can be mapped, but also tables. If you are not the owner of the database your application connects to, you might need to install your schema with some sort of prefix to every table. In our examples, this might mean that you will have to map DEV.AUTHOR to something MY_BOOK_WORLD.MY_APP__AUTHOR, where MY_APP__ is a prefix applied to all of your tables. This can be achieved by creating the following mapping:
Programmatic configuration
Settings settings = new Settings() .withRenderMapping(new RenderMapping() .withSchemata( new MappedSchema().withInput("DEV") .withTables( new MappedTable().withInput("AUTHOR") .withOutput("MY_APP__AUTHOR"))));
XML configuration
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd"> <renderMapping> <schemata> <schema> <input>DEV</input> <tables> <table> <input>AUTHOR</input> <output>MY_APP__AUTHOR</output> </table> </tables> </schema> </schemata> </renderMapping> </settings>
The query executed with a Configuration equipped with the above mapping will in fact produce this SQL statement:
SELECT * FROM DEV.MY_APP__AUTHOR
Table mapping and schema mapping can be applied independently, by specifying several MappedSchema entries in the above configuration. jOOQ will process them in order of appearance and map at first match. Note that you can always omit a MappedSchema's output value, in case of which, only the table mapping is applied. If you omit a MappedSchema's input value, the table mapping is applied to all schemata!
Hard-wiring mappings at code-generation time
Note that the manual's section about code generation schema mapping explains how you can hard-wire your schema mappings at code generation time
4.2.6.3. Identifier style
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
By default, jOOQ will always generate quoted names for all identifiers (even if this manual omits this for readability). For instance:
SELECT "TABLE"."COLUMN" FROM "TABLE" -- SQL standard style SELECT `TABLE`.`COLUMN` FROM `TABLE` -- MySQL style SELECT [TABLE].[COLUMN] FROM [TABLE] -- SQL Server style
Quoting has the following effect on identifiers in most (but not all) databases:
- It allows for using reserved names as object names, e.g. a table called
"FROM"
is usually possible only when quoted. - It allows for using special characters in object names, e.g. a column called
"FIRST NAME"
can be achieved only with quoting. - It turns what are mostly case-insensitive identifiers into case-sensitive ones, e.g.
"name"
and"NAME"
are different identifiers, whereasname
andNAME
are not. Please consider your database manual to learn what the proper default case and default case sensitivity is.
The renderNameStyle
setting allows for overriding the name of all identifiers in jOOQ to a consistent style. Possible options are:
-
QUOTED
(the default): This will generate all names in their proper case with quotes around them. -
AS_IS
: This will generate all names in their proper case without quotes. -
LOWER
: This will transform all names to lower case. -
UPPER
: This will transform all names to upper case.
Programmatic configuration
Settings settings = new Settings() .withRenderNameStyle(RenderNameStyle.AS_IS); // Defaults to QUOTED
XML configuration
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd"> <renderNameStyle>AS_IS</renderNameStyle> </settings>
4.2.6.4. Keyword style
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In all SQL dialects, keywords are case insensitive, and this is also the default in jOOQ, which mostly generates lower-case keywords.
Users may wish to adapt this and they have these options for the renderKeywordStyle
setting:
-
LOWER
(the default): Generate keywords in lower case. -
UPPER
: Generate keywords in upper case.
Programmatic configuration
Settings settings = new Settings() .withRenderKeywordStyle(RenderKeywordStyle.UPPER); // Defaults to LOWER
XML configuration
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd"> <renderKeywordStyle>UPPER</renderKeywordStyle> </settings>
4.2.6.5. Parameter types
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Bind values or bind parameters come in different flavours in different SQL databases. JDBC standardises on their syntax by allowing only ?
(question mark) characters as placeholders for bind variables. Thus, jOOQ, by default, generates ?
placeholders for JDBC consumptions.
Users who wish to use jOOQ with a different backend than JDBC can specify that all jOOQ bind values, including indexed parameters and named parameters generate alternative strings, other than ?
. These are the current options:
-
INDEXED
(the default): Generates indexed parameter placeholders using?
. -
NAMED
: Generates named parameter placeholders, such as:param
for parameters that are named explicitly or:1
for unnamed, indexed parameters. -
NAMED_OR_INLINED:
Generates named parameter placeholders for parameters that are named explicitly and inlines all unnamed parameters. -
INLINED
: Inlines all parameters.
An example:
-- INDEXED SELECT FIRST_NAME || ? FROM AUTHOR WHERE ID = ? -- NAMED SELECT FIRST_NAME || :1 FROM AUTHOR WHERE ID = :x -- NAMED_OR_INLINED SELECT FIRST_NAME || 'x' FROM AUTHOR WHERE ID = :x -- INLINED SELECT FIRST_NAME || 'x' FROM AUTHOR WHERE ID = 42
Param<String> x = val("x"); Param<Integer> i = param("x", 42); DSL.using(configuration) .select(FIRST_NAME.concat(x)) .from(AUTHOR) .where(ID.eq(i)) .fetch();
Programmatic configuration
Settings settings = new Settings() .withParamType(ParamType.NAMED); // Defaults to INDEXED
XML configuration
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd"> <paramType>NAMED</paramType> </settings>
The following setting statementType may override this setting.
4.2.6.6. Statement Type
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
JDBC knows two types of statements:
- java.sql.PreparedStatement: This allows for sending bind variables to the server. jOOQ uses prepared statements by default.
- java.sql.Statement: Also "static statement". These do not support bind variables and may be useful for one-shot commands like DDL statements.
The statementType
setting allows for overriding the default of using prepared statements internally. There are two possible options for this setting:
-
PREPARED_STATEMENT
(the default): Use prepared statements. -
STATIC_STATEMENT
: Use static statements. This enforces theparamType == INLINED
. See parameter types
Programmatic configuration
Settings settings = new Settings() .withStatementType(StatementType.STATIC_STATEMENT); // Defaults to PREPARED_STATEMENT
XML configuration
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd"> <statementType>STATIC_STATEMENT</statementType> </settings>
4.2.6.7. Execute Logging
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The executeLogging
setting turns off the default logging implemented through org.jooq.tools.LoggerListener
Programmatic configuration
Settings settings = new Settings() .withExecuteLogging(false); // Defaults to true
XML configuration
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd"> <executeLogging>false</executeLogging> </settings>
4.2.6.8. Optimistic Locking
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The executeWithOptimisticLocking
setting governs the behaviour of the jOOQ optimistic locking feature:
Programmatic configuration
Settings settings = new Settings() .withExecuteWithOptimisticLocking(true); // Defaults to false
XML configuration
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd"> <executeWithOptimisticLocking>true</executeWithOptimisticLocking> </settings>
For more details, please refer to the manual's section about the optimistic locking feature.
4.2.6.9. Auto-attach Records
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
By default, all records fetched through jOOQ are "attached" to the configuration that created them. This allows for features like updatable records as can be seen here:
AuthorRecord author = DSL.using(configuration) // This configuration will be attached to any record produced by the below query. .selectFrom(AUTHOR) .where(AUTHOR.ID.eq(1)) .fetchOne(); author.setLastName("Smith"); author.store(); // This store call operates on the "attached" configuration.
In some cases (e.g. when serialising records), it may be desirable not to attach the Configuration
that created a record to the record. This can be achieved with the attachRecords
setting:
Programmatic configuration
Settings settings = new Settings() .withAttachRecords(false); // Defaults to true
XML configuration
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd"> <attachRecords>false</attachRecords> </settings>
4.2.6.10. Updatable Primary Keys
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In most database design guidelines, primary key values are expected to never change - an assumption that is essential to a normalised database.
As always, there are exceptions to these rules, and users may wish to allow for updatable primary key values in the updatable records feature (note: any value can always be updated through ordinary update statements). An example:
AuthorRecord author = DSL.using(configuration) // This configuration will be attached to any record produced by the below query. .selectFrom(AUTHOR) .where(AUTHOR.ID.eq(1)) .fetchOne(); author.setId(2); author.store(); // The behaviour of this store call is governed by the updatablePrimaryKeys flag
The above store call depends on the value of the updatablePrimaryKeys
flag:
-
false
(the default): Since immutability of primary keys is assumed, the store call will create a new record (a copy) with the new primary key value. -
true
: Since mutablity of primary keys is allowed, the store call will change the primary key value from1
to2
.
Programmatic configuration
Settings settings = new Settings() .withUpdatablePrimaryKeys(true); // Defaults to false
XML configuration
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd"> <updatablePrimaryKeys>true</updatablePrimaryKeys> </settings>
4.2.6.11. Reflection caching
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
All operations of the DefaultRecordMapper are cached in the Configuration
by default for improved mapping and reflection speed. Users who prefer to override this cache, or work with their own custom record mapper provider may wish to turn off the out-of-the-box caching feature.
Programmatic configuration
Settings settings = new Settings() .withReflectionCaching(false); // Defaults to true
XML configuration
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd"> <reflectionCaching>false</reflectionCaching> </settings>
4.2.6.12. Fetch Warnings
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Apart from JDBC exceptions, there is also the possibility to handle java.sql.SQLWarning, which are made available to jOOQ users through the java.sql.ExecuteListener SPI and the log
Users who do not wish to get these notifications (e.g. for performance reasons), may turn off fetching of warnings through the fetchWarnings
setting:
Programmatic configuration
Settings settings = new Settings() .withFetchWarnings(false); // Defaults to true
XML configuration
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd"> <fetchWarnings>false</fetchWarnings> </settings>
4.2.6.13. Backslash Escaping
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Some databases (mainly MySQL and MariaDB) unfortunately chose to go an alternative, non-SQL-standard route when escaping string literals. Here's an example of how to escape a string containing apostrophes in different dialects:
SELECT 'I''m sure this is OK' AS val -- Standard SQL escaping of apostrophe by doubling it. SELECT 'I\'m certain this causes trouble' AS val -- Vendor-specific escaping of apostrophe by using a backslash.
As most databases don't support backslash escaping (and MySQL also allows for turning it off!), jOOQ by default also doesn't support it when inlining bind variables. However, this can lead to SQL injection vulnerabilities and syntax errors when not dealing with it carefully!
This feature is turned on by default and for historic reasons for MySQL and MariaDB.
-
DEFAULT
(the - surprise! - default): Turns the featureON
for MySQL and MariaDB andOFF
for all other dialects -
ON
: Turn the feature on. -
OFF
: Turn the feature off.
Programmatic configuration
Settings settings = new Settings() .withBackslashEscaping(BackslashEscaping.OFF); // Default to DEFAULT
XML configuration
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd"> <backslashEscaping>OFF</backslashEscaping> </settings>
4.2.6.14. Scalar subqueries for stored functions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
This setting is useful mostly for the Oracle database, which implements a feature called scalar subquery caching, which is a good tool to avoid the expensive PL/SQL-to-SQL context switch when predicates make use of stored function calls.
With this setting in place, all stored function calls embedded in SQL statements will be wrapped in a scalar subquery:
SELECT (SELECT my_package.format(LANGUAGE_ID) FROM dual) FROM BOOK
DSL.using(configuration) .select(MyPackage.format(BOOK.LANGUAGE_ID)) .from(BOOK)
If our table contains thousands of books, but only a dozen of LANGUAGE_ID
values, then with scalar subquery caching, we can avoid most of the function calls and cache the result per LANGUAGE_ID
.
Programmatic configuration
Settings settings = new Settings() .withRenderScalarSubqueriesForStoredFunctions(true);
XML configuration
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.5.0.xsd"> <renderScalarSubqueriesForStoredFunctions>true</renderScalarSubqueriesForStoredFunctions> </settings>
4.2.7. Thread safety
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
org.jooq.Configuration, and by consequence org.jooq.DSLContext, make no thread safety guarantees, but by carefully observing a few rules, they can be shared in a thread safe way. We encourage sharing Configuration
instances, because they contain caches for work not worth repeating, such as reflection field and method lookups for org.jooq.impl.DefaultRecordMapper. If you're using Spring or CDI for dependency injection, you will want to be able to inject a DSLContext
instance everywhere you use it.
The following needs to be considered when attempting to share Configuration
and DSLContext
among threads:
-
Configuration
is mutable for historic reasons. Calls to variousConfiguration.set()
methods must be avoided after initialisation, should aConfiguration
(and by consequenceDSLContext
) instance be shared among threads. If you wish to modify some elements of aConfiguration
for single use, use theConfiguration.derive()
methods instead, which create a copy. -
Configuration
components, such as org.jooq.conf.Settings are mutable as well. The same rules for modification apply here. -
Configuration
allows for supplying user-defined SPI implementations (see above for examples). All of these must be thread safe as well, for their wrappingConfiguration
to be thread safe. If you are using a org.jooq.impl.DataSourceConnectionProvider, for instance, you must make sure that your javax.sql.DataSource is thread safe as well. This is usually the case when you use a third party connection pool.
As can be seen above, Configuration
was designed to work in a thread safe way, despite it not making any such guarantee.
4.3. SQL Statements (DML)
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
jOOQ currently supports 5 types of SQL statements. All of these statements are constructed from a DSLContext instance with an optional JDBC Connection or DataSource. If supplied with a Connection or DataSource, they can be executed. Depending on the query type, executed queries can return results.
4.3.1. jOOQ's DSL and model API
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
jOOQ ships with its own DSL (or Domain Specific Language) that emulates SQL in Java. This means, that you can write SQL statements almost as if Java natively supported it, just like .NET's C# does with LINQ to SQL.
Here is an example to illustrate what that means:
-- Select all books by authors born after 1920, -- named "Paulo" from a catalogue: SELECT * FROM author a JOIN book b ON a.id = b.author_id WHERE a.year_of_birth > 1920 AND a.first_name = 'Paulo' ORDER BY b.title
Result<Record> result = create.select() .from(AUTHOR.as("a")) .join(BOOK.as("b")).on(a.ID.eq(b.AUTHOR_ID)) .where(a.YEAR_OF_BIRTH.gt(1920) .and(a.FIRST_NAME.eq("Paulo"))) .orderBy(b.TITLE) .fetch();
We'll see how the aliasing works later in the section about aliased tables
jOOQ as an internal domain specific language in Java (a.k.a. the DSL API)
Many other frameworks have similar APIs with similar feature sets. Yet, what makes jOOQ special is its informal BNF notation modelling a unified SQL dialect suitable for many vendor-specific dialects, and implementing that BNF notation as a hierarchy of interfaces in Java. This concept is extremely powerful, when using jOOQ in modern IDEs with syntax completion. Not only can you code much faster, your SQL code will be compile-checked to a certain extent. An example of a DSL query equivalent to the previous one is given here:
DSLContext create = DSL.using(connection, dialect); Result<?> result = create.select() .from(AUTHOR) .join(BOOK).on(BOOK.AUTHOR_ID.eq(AUTHOR.ID)) .fetch();
Unlike other, simpler frameworks that use "fluent APIs" or "method chaining", jOOQ's BNF-based interface hierarchy will not allow bad query syntax. The following will not compile, for instance:
DSLContext create = DSL.using(connection, dialect); Result<?> result = create.select() .join(BOOK).on(BOOK.AUTHOR_ID.eq(AUTHOR.ID)) // ^^^^ "join" is not possible here .from(AUTHOR) .fetch(); Result<?> result = create.select() .from(AUTHOR) .join(BOOK) .fetch(); // ^^^^^ "on" is missing here Result<?> result = create.select(rowNumber()) // ^^^^^^^^^ "over()" is missing here .from(AUTHOR) .fetch(); Result<?> result = create.select() .from(AUTHOR) .where(AUTHOR.ID.in(select(BOOK.TITLE).from(BOOK))) // ^^^^^^^^^^^^^^^^^^ // AUTHOR.ID is of type Field<Integer> but subselect returns Record1<String> .fetch(); Result<?> result = create.select() .from(AUTHOR) .where(AUTHOR.ID.in(select(BOOK.AUTHOR_ID, BOOK.ID).from(BOOK))) // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // AUTHOR.ID is of degree 1 but subselect returns Record2<Integer, Integer> .fetch();
History of SQL building and incremental query building (a.k.a. the model API)
Historically, jOOQ started out as an object-oriented SQL builder library like any other. This meant that all queries and their syntactic components were modeled as so-called QueryParts, which delegate SQL rendering and variable binding to child components. This part of the API will be referred to as the model API (or non-DSL API), which is still maintained and used internally by jOOQ for incremental query building. An example of incremental query building is given here:
DSLContext create = DSL.using(connection, dialect); SelectQuery<Record> query = create.selectQuery(); query.addFrom(AUTHOR); // Join books only under certain circumstances if (join) { query.addJoin(BOOK, BOOK.AUTHOR_ID.eq(AUTHOR.ID)); } Result<?> result = query.fetch();
This query is equivalent to the one shown before using the DSL syntax. In fact, internally, the DSL API constructs precisely this SelectQuery object. Note, that you can always access the SelectQuery object to switch between DSL and model APIs:
DSLContext create = DSL.using(connection, dialect); SelectFinalStep<?> select = create.select().from(AUTHOR); // Add the JOIN clause on the internal QueryObject representation SelectQuery<?> query = select.getQuery(); query.addJoin(BOOK, BOOK.AUTHOR_ID.eq(AUTHOR.ID));
Mutability
Note, that for historic reasons, the DSL API mixes mutable and immutable behaviour with respect to the internal representation of the QueryPart being constructed. While creating conditional expressions, column expressions (such as functions) assumes immutable behaviour, creating SQL statements does not. In other words, the following can be said:
// Conditional expressions (immutable) // ----------------------------------- Condition a = BOOK.TITLE.eq("1984"); Condition b = BOOK.TITLE.eq("Animal Farm"); // The following can be said a != a.or(b); // or() does not modify a a.or(b) != a.or(b); // or() always creates new objects // Statements (mutable) // -------------------- SelectFromStep<?> s1 = select(); SelectJoinStep<?> s2 = s1.from(BOOK); SelectJoinStep<?> s3 = s1.from(AUTHOR); // The following can be said s1 == s2; // The internal object is always the same s2 == s3; // The internal object is always the same
On the other hand, beware that you can always extract and modify bind values from any QueryPart
.
4.3.2. The WITH clause
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SQL:1999 standard specifies the WITH
clause to be an optional clause for the SELECT statement, in order to specify common table expressions (also: CTE). Many other databases (such as PostgreSQL, SQL Server) also allow for using common table expressions also in other DML clauses, such as the INSERT statement, UPDATE statement, DELETE statement, or MERGE statement.
When using common table expressions with jOOQ, there are essentially two approaches:
- Declaring and assigning common table expressions explicitly to names
- Inlining common table expressions into a SELECT statement
Explicit common table expressions
The following example makes use of names to construct common table expressions, which can then be supplied to a WITH
clause or a FROM
clause of a SELECT statement:
-- Pseudo-SQL for a common table expression specification "t1" ("f1", "f2") AS (SELECT 1, 'a')
// Code for creating a CommonTableExpression instance name("t1").fields("f1", "f2").as(select(val(1), val("a")));
The above expression can be assigned to a variable in Java and then be used to create a full SELECT statement:
WITH "t1" ("f1", "f2") AS (SELECT 1, 'a'), "t2" ("f3", "f4") AS (SELECT 2, 'b') SELECT "t1"."f1" + "t2"."f3" AS "add", "t1"."f2" || "t2"."f4" AS "concat" FROM "t1", "t2" ;
CommonTableExpression<Record2<Integer, String>> t1 = name("t1").fields("f1", "f2").as(select(val(1), val("a"))); CommonTableExpression<Record2<Integer, String>> t2 = name("t2").fields("f3", "f4").as(select(val(2), val("b"))); Result<?> result2 = create.with(t1) .with(t2) .select( t1.field("f1").add(t2.field("f3")).as("add"), t1.field("f2").concat(t2.field("f4")).as("concat")) .from(t1, t2) .fetch();
Note that the org.jooq.CommonTableExpression type extends the commonly used org.jooq.Table type, and can thus be used wherever a table can be used.
Inlined common table expressions
If you're just operating on plain SQL, you may not need to keep intermediate references to such common table expressions. An example of such usage would be this:
WITH "a" AS (SELECT 1 AS "x", 'a' AS "y" ) SELECT FROM "a" ;
create.with("a").as(select( val(1).as("x"), val("a").as("y") )) .select() .from(tableByName("a")) .fetch();
Recursive common table expressions
The various SQL dialects do not agree on the use of RECURSIVE
when writing recursive common table expressions. When using jOOQ, always use the DSLContext.withRecursive() or DSL.withRecursive() methods, and jOOQ will render the RECURSIVE
keyword, if needed.
4.3.3. The SELECT statement
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
When you don't just perform CRUD (i.e. SELECT * FROM your_table WHERE ID = ?), you're usually generating new record types using custom projections. With jOOQ, this is as intuitive, as if using SQL directly. A more or less complete example of the "standard" SQL syntax, plus some extensions, is provided by a query like this:
SELECT from a complex table expression
-- get all authors' first and last names, and the number -- of books they've written in German, if they have written -- more than five books in German in the last three years -- (from 2011), and sort those authors by last names -- limiting results to the second and third row, locking -- the rows for a subsequent update... whew! SELECT AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, COUNT(*) FROM AUTHOR JOIN BOOK ON AUTHOR.ID = BOOK.AUTHOR_ID WHERE BOOK.LANGUAGE = 'DE' AND BOOK.PUBLISHED > '2008-01-01' GROUP BY AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME HAVING COUNT(*) > 5 ORDER BY AUTHOR.LAST_NAME ASC NULLS FIRST LIMIT 2 OFFSET 1 FOR UPDATE
// And with jOOQ... DSLContext create = DSL.using(connection, dialect); create.select(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, count()) .from(AUTHOR) .join(BOOK).on(BOOK.AUTHOR_ID.eq(AUTHOR.ID)) .where(BOOK.LANGUAGE.eq("DE")) .and(BOOK.PUBLISHED.gt("2008-01-01")) .groupBy(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .having(count().gt(5)) .orderBy(AUTHOR.LAST_NAME.asc().nullsFirst()) .limit(2) .offset(1) .forUpdate() .fetch();
Details about the various clauses of this query will be provided in subsequent sections.
SELECT from single tables
A very similar, but limited API is available, if you want to select from single tables in order to retrieve TableRecords or even UpdatableRecords. The decision, which type of select to create is already made at the very first step, when you create the SELECT
statement with the DSL or DSLContext types:
public <R extends Record> SelectWhereStep<R> selectFrom(Table<R> table);
As you can see, there is no way to further restrict/project the selected fields. This just selects all known TableFields in the supplied Table, and it also binds <R extends Record> to your Table's associated Record. An example of such a Query would then be:
BookRecord book = create.selectFrom(BOOK) .where(BOOK.LANGUAGE.eq("DE")) .orderBy(BOOK.TITLE) .fetchAny();
The "reduced" SELECT API is limited in the way that it skips DSL access to any of these clauses:
In most parts of this manual, it is assumed that you do not use the "reduced" SELECT API. For more information about the simple SELECT API, see the manual's section about fetching strongly or weakly typed records.
4.3.3.1. SELECT clause
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SELECT clause lets you project your own record types, referencing table fields, functions, arithmetic expressions, etc. The DSL type provides several methods for expressing a SELECT clause:
-- The SELECT clause SELECT BOOK.ID, BOOK.TITLE SELECT BOOK.ID, TRIM(BOOK.TITLE)
// Provide a varargs Fields list to the SELECT clause: Select<?> s1 = create.select(BOOK.ID, BOOK.TITLE); Select<?> s2 = create.select(BOOK.ID, trim(BOOK.TITLE));
Some commonly used projections can be easily created using convenience methods:
-- Simple SELECTs SELECT COUNT(*) SELECT 0 -- Not a bind variable SELECT 1 -- Not a bind variable
// Select commonly used values Result<?> result1 = create.selectCount().fetch(); Result<?> result2 = create.selectZero().fetch(); Result<?> result3 = create.selectOne().fetch();
Which are short forms for creating Column expressions from the org.jooq.impl.DSL API
-- Simple SELECTs SELECT COUNT(*) SELECT 0 -- Not a bind variable SELECT ? -- A bind variable
// Select commonly used values Result<?> result1 = create.select(count()).fetch(); Result<?> result2 = create.select(inline(0)).fetch(); Result<?> result3 = create.select(val(1)).fetch();
See more details about functions and expressions in the manual's section about Column expressions
The SELECT DISTINCT clause
The DISTINCT keyword can be included in the method name, constructing a SELECT clause
SELECT DISTINCT BOOK.TITLE
Select<?> select1 = create.selectDistinct(BOOK.TITLE).fetch();
SELECT *
jOOQ does not explicitly support the asterisk operator in projections. However, you can omit the projection as in these examples:
// Explicitly selects all columns available from BOOK create.select().from(BOOK).fetch(); // Explicitly selects all columns available from BOOK and AUTHOR create.select().from(BOOK, AUTHOR).fetch(); create.select().from(BOOK).crossJoin(AUTHOR).fetch(); // Renders a SELECT * statement, as columns are unknown to jOOQ create.select().from(tableByName("BOOK")).fetch();
Typesafe projections with degree up to 22
Since jOOQ 3.0, records and row value expressions up to degree 22 are now generically typesafe. This is reflected by an overloaded SELECT
(and SELECT DISTINCT
) API in both DSL and DSLContext. An extract from the DSL type:
// Non-typesafe select methods: public static SelectSelectStep<Record> select(Collection<? extends Field<?>> fields); public static SelectSelectStep<Record> select(Field<?>... fields); // Typesafe select methods: public static <T1> SelectSelectStep<Record1<T1>> select(Field<T1> field1); public static <T1, T2> SelectSelectStep<Record2<T1, T2>> select(Field<T1> field1, Field<T2> field2); public static <T1, T2, T3> SelectSelectStep<Record3<T1, T2, T3>> select(Field<T1> field1, Field<T2> field2, Field<T3> field3); // [...]
Since the generic R type is bound to some Record[N], the associated T type information can be used in various other contexts, e.g. the IN predicate. Such a SELECT
statement can be assigned typesafely:
Select<Record2<Integer, String>> s1 = create.select(BOOK.ID, BOOK.TITLE); Select<Record2<Integer, String>> s2 = create.select(BOOK.ID, trim(BOOK.TITLE));
For more information about typesafe record types with degree up to 22, see the manual's section about Record1 to Record22.
4.3.3.2. FROM clause
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SQL FROM clause allows for specifying any number of table expressions to select data from. The following are examples of how to form normal FROM clauses:
SELECT 1 FROM BOOK SELECT 1 FROM BOOK, AUTHOR SELECT 1 FROM BOOK "b", AUTHOR "a"
create.selectOne().from(BOOK).fetch(); create.selectOne().from(BOOK, AUTHOR).fetch(); create.selectOne().from(BOOK.as("b"), AUTHOR.as("a")).fetch();
Read more about aliasing in the manual's section about aliased tables.
More advanced table expressions
Apart from simple tables, you can pass any arbitrary table expression to the jOOQ FROM clause. This may include unnested cursors in Oracle:
SELECT * FROM TABLE( DBMS_XPLAN.DISPLAY_CURSOR(null, null, 'ALLSTATS') );
create.select() .from(table( DbmsXplan.displayCursor(null, null, "ALLSTATS") ).fetch();
Note, in order to access the DbmsXplan package, you can use the code generator to generate Oracle's SYS schema.
Selecting FROM DUAL with jOOQ
In many SQL dialects, FROM is a mandatory clause, in some it isn't. jOOQ allows you to omit the FROM clause, returning just one record. An example:
SELECT 1 FROM DUAL SELECT 1
DSL.using(SQLDialect.ORACLE).selectOne().fetch(); DSL.using(SQLDialect.POSTGRES).selectOne().fetch();
Read more about dual or dummy tables in the manual's section about the DUAL table. The following are examples of how to form normal FROM clauses:
4.3.3.3. JOIN operator
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
jOOQ supports many different types of standard SQL JOIN operations:
- [ INNER ] JOIN
- LEFT [ OUTER ] JOIN
- RIGHT [ OUTER ] JOIN
- FULL OUTER JOIN
- CROSS JOIN
- NATURAL JOIN
- NATURAL LEFT [ OUTER ] JOIN
- NATURAL RIGHT [ OUTER ] JOIN
Besides, jOOQ also supports
- CROSS APPLY (T-SQL and Oracle 12c specific)
- OUTER APPLY (T-SQL and Oracle 12c specific)
- LATERAL derived tables (PostgreSQL and Oracle 12c)
- partitioned outer join
All of these JOIN methods can be called on org.jooq.Table types, or directly after the FROM clause for convenience. The following example joins AUTHOR and BOOK
DSLContext create = DSL.using(connection, dialect); // Call "join" directly on the AUTHOR table Result<?> result = create.select() .from(AUTHOR.join(BOOK) .on(BOOK.AUTHOR_ID.eq(AUTHOR.ID))) .fetch(); // Call "join" on the type returned by "from" Result<?> result = create.select() .from(AUTHOR) .join(BOOK) .on(BOOK.AUTHOR_ID.eq(AUTHOR.ID)) .fetch();
The two syntaxes will produce the same SQL statement. However, calling "join" on org.jooq.Table objects allows for more powerful, nested JOIN expressions (if you can handle the parentheses):
SELECT * FROM AUTHOR LEFT OUTER JOIN ( BOOK JOIN BOOK_TO_BOOK_STORE ON BOOK_TO_BOOK_STORE.BOOK_ID = BOOK.ID ) ON BOOK.AUTHOR_ID = AUTHOR.ID
// Nest joins and provide JOIN conditions only at the end create.select() .from(AUTHOR .leftOuterJoin(BOOK .join(BOOK_TO_BOOK_STORE) .on(BOOK_TO_BOOK_STORE.BOOK_ID.eq(BOOK.ID))) .on(BOOK.AUTHOR_ID.eq(AUTHOR.ID))) .fetch();
- See the section about conditional expressions to learn more about the many ways to create org.jooq.Condition objects in jOOQ.
- See the section about table expressions to learn about the various ways of referencing org.jooq.Table objects in jOOQ
JOIN ON KEY, convenience provided by jOOQ
Surprisingly, the SQL standard does not allow to formally JOIN on well-known foreign key relationship information. Naturally, when you join BOOK to AUTHOR, you will want to do that based on the BOOK.AUTHOR_ID foreign key to AUTHOR.ID primary key relation. Not being able to do this in SQL leads to a lot of repetitive code, re-writing the same JOIN predicate again and again - especially, when your foreign keys contain more than one column. With jOOQ, when you use code generation, you can use foreign key constraint information in JOIN expressions as such:
SELECT * FROM AUTHOR JOIN BOOK ON BOOK.AUTHOR_ID = AUTHOR.ID
create.select() .from(AUTHOR) .join(BOOK).onKey() .fetch();
In case of ambiguity, you can also supply field references for your foreign keys, or the generated foreign key reference to the onKey() method.
Note that formal support for the Sybase JOIN ON KEY
syntax is on the roadmap.
The JOIN USING syntax
Most often, you will provide jOOQ with JOIN conditions in the JOIN .. ON clause. SQL supports a different means of specifying how two tables are to be joined. This is the JOIN .. USING clause. Instead of a condition, you supply a set of fields whose names are common to both tables to the left and right of a JOIN operation. This can be useful when your database schema has a high degree of relational normalisation. An example:
-- Assuming that both tables contain AUTHOR_ID columns SELECT * FROM AUTHOR JOIN BOOK USING (AUTHOR_ID)
// join(...).using(...) create.select() .from(AUTHOR) .join(BOOK).using(AUTHOR.AUTHOR_ID) .fetch();
In schemas with high degrees of normalisation, you may also choose to use NATURAL JOIN, which takes no JOIN arguments as it joins using all fields that are common to the table expressions to the left and to the right of the JOIN operator. An example:
-- Assuming that both tables contain AUTHOR_ID columns SELECT * FROM AUTHOR NATURAL JOIN BOOK
// naturalJoin(...) create.select() .from(AUTHOR) .naturalJoin(BOOK) .fetch();
Oracle's partitioned OUTER JOIN
Oracle SQL ships with a special syntax available for OUTER JOIN clauses. According to the Oracle documentation about partitioned outer joins this can be used to fill gaps for simplified analytical calculations. jOOQ only supports putting the PARTITION BY clause to the right of the OUTER JOIN clause. The following example will create at least one record per AUTHOR and per existing value in BOOK.PUBLISHED_IN, regardless if an AUTHOR has actually published a book in that year.
SELECT * FROM AUTHOR LEFT OUTER JOIN BOOK PARTITION BY (PUBLISHED_IN) ON BOOK.AUTHOR_ID = AUTHOR.ID
create.select() .from(AUTHOR) .leftOuterJoin(BOOK) .partitionBy(BOOK.PUBLISHED_IN) .on(BOOK.AUTHOR_ID.eq(AUTHOR.ID)) .fetch();
T-SQL's CROSS APPLY and OUTER APPLY
T-SQL has long known what the SQL standard calls lateral derived tables, lateral joins using the APPLY
keyword. To every row resulting from the table expression on the left, we apply the table expression on the right. This is extremely useful for table-valued functions, which are also supported by jOOQ. Some examples:
DSL.using(configuration) .select() .from(AUTHOR, lateral(select(count().as("c")) .from(BOOK) .where(BOOK.AUTHOR_ID.eq(AUTHOR.ID))) ) .fetch("c", int.class);
The above example shows standard usage of the LATERAL
keyword to connect a derived table to the previous table in the FROM clause. A similar statement can be written in T-SQL:
DSL.using(configuration) .from(AUTHOR) .crossApply( select(count().as("c")) .from(BOOK) .where(BOOK.AUTHOR_ID.eq(AUTHOR.ID)) ) .fetch("c", int.class)
LATERAL JOIN
or CROSS APPLY
are particularly useful together with table valued functions, which are also supported by jOOQ.
4.3.3.4. WHERE clause
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The WHERE clause can be used for JOIN or filter predicates, in order to restrict the data returned by the table expressions supplied to the previously specified from clause and join clause. Here is an example:
SELECT * FROM BOOK WHERE AUTHOR_ID = 1 AND TITLE = '1984'
create.select() .from(BOOK) .where(BOOK.AUTHOR_ID.eq(1)) .and(BOOK.TITLE.eq("1984")) .fetch();
The above syntax is convenience provided by jOOQ, allowing you to connect the org.jooq.Condition supplied in the WHERE clause with another condition using an AND operator. You can of course also create a more complex condition and supply that to the WHERE clause directly (observe the different placing of parentheses). The results will be the same:
SELECT * FROM BOOK WHERE AUTHOR_ID = 1 AND TITLE = '1984'
create.select() .from(BOOK) .where(BOOK.AUTHOR_ID.eq(1).and( BOOK.TITLE.eq("1984"))) .fetch();
You will find more information about creating conditional expressions later in the manual.
4.3.3.5. CONNECT BY clause
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The Oracle database knows a very succinct syntax for creating hierarchical queries: the CONNECT BY clause, which is fully supported by jOOQ, including all related functions and pseudo-columns. A more or less formal definition of this clause is given here:
-- SELECT .. -- FROM .. -- WHERE .. CONNECT BY [ NOCYCLE ] condition [ AND condition, ... ] [ START WITH condition ] -- GROUP BY .. -- ORDER [ SIBLINGS ] BY ..
An example for an iterative query, iterating through values between 1 and 5 is this:
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 5
// Get a table with elements 1, 2, 3, 4, 5 create.select(level()) .connectBy(level().le(5)) .fetch();
Here's a more complex example where you can recursively fetch directories in your database, and concatenate them to a path:
SELECT SUBSTR(SYS_CONNECT_BY_PATH(DIRECTORY.NAME, '/'), 2) FROM DIRECTORY CONNECT BY PRIOR DIRECTORY.ID = DIRECTORY.PARENT_ID START WITH DIRECTORY.PARENT_ID IS NULL ORDER BY 1
.select( substring(sysConnectByPath(DIRECTORY.NAME, "/"), 2)) .from(DIRECTORY) .connectBy( prior(DIRECTORY.ID).eq(DIRECTORY.PARENT_ID)) .startWith(DIRECTORY.PARENT_ID.isNull()) .orderBy(1) .fetch();
The output might then look like this
+------------------------------------------------+ |substring | +------------------------------------------------+ |C: | |C:/eclipse | |C:/eclipse/configuration | |C:/eclipse/dropins | |C:/eclipse/eclipse.exe | +------------------------------------------------+ |...21 record(s) truncated...
Some of the supported functions and pseudo-columns are these (available from the DSL):
- LEVEL
- CONNECT_BY_IS_CYCLE
- CONNECT_BY_IS_LEAF
- CONNECT_BY_ROOT
- SYS_CONNECT_BY_PATH
- PRIOR
Note that this syntax is also supported in the CUBRID database and might be emulated in other dialects supporting common table expressions in the future.
ORDER SIBLINGS
The Oracle database allows for specifying a SIBLINGS keyword in the ORDER BY clause. Instead of ordering the overall result, this will only order siblings among each other, keeping the hierarchy intact. An example is given here:
SELECT DIRECTORY.NAME FROM DIRECTORY CONNECT BY PRIOR DIRECTORY.ID = DIRECTORY.PARENT_ID START WITH DIRECTORY.PARENT_ID IS NULL ORDER SIBLINGS BY 1
.select(DIRECTORY.NAME) .from(DIRECTORY) .connectBy( prior(DIRECTORY.ID).eq(DIRECTORY.PARENT_ID)) .startWith(DIRECTORY.PARENT_ID.isNull()) .orderSiblingsBy(1) .fetch();
4.3.3.6. GROUP BY clause
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
GROUP BY can be used to create unique groups of data, to form aggregations, to remove duplicates and for other reasons. It will transform your previously defined set of table expressions, and return only one record per unique group as specified in this clause. For instance, you can group books by BOOK.AUTHOR_ID:
SELECT AUTHOR_ID, COUNT(*) FROM BOOK GROUP BY AUTHOR_ID
create.select(BOOK.AUTHOR_ID, count()) .from(BOOK) .groupBy(BOOK.AUTHOR_ID) .fetch();
The above example counts all books per author.
Note, as defined in the SQL standard, when grouping, you may no longer project any columns that are not a formal part of the GROUP BY clause, or aggregate functions.
Empty GROUP BY clauses
jOOQ supports empty GROUP BY ()
clause as well. This will result in SELECT statements that return only one record.
SELECT COUNT(*) FROM BOOK GROUP BY ()
create.selectCount() .from(BOOK) .groupBy() .fetch();
ROLLUP(), CUBE() and GROUPING SETS()
Some databases support the SQL standard grouping functions and some extensions thereof. See the manual's section about grouping functions for more details.
4.3.3.7. HAVING clause
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The HAVING clause is commonly used to further restrict data resulting from a previously issued GROUP BY clause. An example, selecting only those authors that have written at least two books:
SELECT AUTHOR_ID, COUNT(*) FROM BOOK GROUP BY AUTHOR_ID HAVING COUNT(*) >= 2
create.select(BOOK.AUTHOR_ID, count()) .from(BOOK) .groupBy(AUTHOR_ID) .having(count().ge(2)) .fetch();
According to the SQL standard, you may omit the GROUP BY clause and still issue a HAVING clause. This will implicitly GROUP BY (). jOOQ also supports this syntax. The following example selects one record, only if there are at least 4 books in the books table:
SELECT COUNT(*) FROM BOOK HAVING COUNT(*) >= 4
create.select(count(*)) .from(BOOK) .having(count().ge(4)) .fetch();
4.3.3.8. WINDOW clause
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SQL:2003 standard supports a WINDOW
clause that allows for specifying WINDOW
frames for reuse in SELECT clauses and ORDER BY clauses.
SELECT LAG(first_name, 1) OVER w "prev", first_name, LEAD(first_name, 1) OVER w "next" FROM author WINDOW w AS (ORDER first_name) ORDER BY first_name DESC
WindowDefinition w = name("w").as( orderBy(PEOPLE.FIRST_NAME)); select( lag(AUTHOR.FIRST_NAME, 1).over(w).as("prev"), AUTHOR.FIRST_NAME, lead(AUTHOR.FIRST_NAME, 1).over(w).as("next")) .from(AUTHOR) .window(w) .orderBy(AUTHOR.FIRST_NAME.desc()) .fetch();
Note that in order to create such a window definition, we need to first create a name reference using DSL.name().
Even if only PostgreSQL and Sybase SQL Anywhere natively support this great feature, jOOQ can emulate it by expanding any org.jooq.WindowDefinition and org.jooq.WindowSpecification types that you pass to the window()
method - if the database supports window functions at all.
Some more information about window functions and the WINDOW
clause can be found on our blog: http://blog.jooq.org/2013/11/03/probably-the-coolest-sql-feature-window-functions/
4.3.3.9. ORDER BY clause
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Databases are allowed to return data in any arbitrary order, unless you explicitly declare that order in the ORDER BY clause. In jOOQ, this is straight-forward:
SELECT AUTHOR_ID, TITLE FROM BOOK ORDER BY AUTHOR_ID ASC, TITLE DESC
create.select(BOOK.AUTHOR_ID, BOOK.TITLE) .from(BOOK) .orderBy(BOOK.AUTHOR_ID.asc(), BOOK.TITLE.desc()) .fetch();
Any jOOQ column expression (or field) can be transformed into an org.jooq.SortField by calling the asc() and desc() methods.
Ordering by field index
The SQL standard allows for specifying integer literals (literals, not bind values!) to reference column indexes from the projection (SELECT clause). This may be useful if you do not want to repeat a lengthy expression, by which you want to order - although most databases also allow for referencing aliased column references in the ORDER BY clause. An example of this is given here:
SELECT AUTHOR_ID, TITLE FROM BOOK ORDER BY 1 ASC, 2 DESC
create.select(BOOK.AUTHOR_ID, BOOK.TITLE) .from(BOOK) .orderBy(one().asc(), inline(2).desc()) .fetch();
Note, how one()
is used as a convenience short-cut for inline(1)
Ordering and NULLS
A few databases support the SQL standard "null ordering" clause in sort specification lists, to define whether NULL
values should come first or last in an ordered result.
SELECT AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME FROM AUTHOR ORDER BY LAST_NAME ASC, FIRST_NAME ASC NULLS LAST
create.select( AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .from(AUTHOR) .orderBy(AUTHOR.LAST_NAME.asc(), AUTHOR.FIRST_NAME.asc().nullsLast()) .fetch();
If your database doesn't support this syntax, jOOQ emulates it using a CASE expression as follows
SELECT AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME FROM AUTHOR ORDER BY LAST_NAME ASC, CASE WHEN FIRST_NAME IS NULL THEN 1 ELSE 0 END ASC, FIRST_NAME ASC
Ordering using CASE expressions
Using CASE expressions in SQL ORDER BY clauses is a common pattern, if you want to introduce some sort indirection / sort mapping into your queries. As with SQL, you can add any type of column expression into your ORDER BY clause. For instance, if you have two favourite books that you always want to appear on top, you could write:
SELECT * FROM BOOK ORDER BY CASE TITLE WHEN '1984' THEN 0 WHEN 'Animal Farm' THEN 1 ELSE 2 END ASC
create.select() .from(BOOK) .orderBy(choose(BOOK.TITLE) .when("1984", 0) .when("Animal Farm", 1) .otherwise(2).asc()) .fetch();
But writing these things can become quite verbose. jOOQ supports a convenient syntax for specifying sort mappings. The same query can be written in jOOQ as such:
create.select() .from(BOOK) .orderBy(BOOK.TITLE.sortAsc("1984", "Animal Farm")) .fetch();
More complex sort indirections can be provided using a Map:
create.select() .from(BOOK) .orderBy(BOOK.TITLE.sort(new HashMap<String, Integer>() {{ put("1984", 1); put("Animal Farm", 13); put("The jOOQ book", 10); }})) .fetch();
Of course, you can combine this feature with the previously discussed NULLS FIRST / NULLS LAST feature. So, if in fact these two books are the ones you like least, you can put all NULLS FIRST (all the other books):
create.select() .from(BOOK) .orderBy(BOOK.TITLE.sortAsc("1984", "Animal Farm").nullsFirst()) .fetch();
jOOQ's understanding of SELECT .. ORDER BY
The SQL standard defines that a "query expression" can be ordered, and that query expressions can contain UNION, INTERSECT and EXCEPT clauses, whose subqueries cannot be ordered. While this is defined as such in the SQL standard, many databases allowing for the LIMIT clause in one way or another, do not adhere to this part of the SQL standard. Hence, jOOQ allows for ordering all SELECT statements, regardless whether they are constructed as a part of a UNION or not. Corner-cases are handled internally by jOOQ, by introducing synthetic subselects to adhere to the correct syntax, where this is needed.
Oracle's ORDER SIBLINGS BY clause
jOOQ also supports Oracle's SIBLINGS keyword to be used with ORDER BY clauses for hierarchical queries using CONNECT BY
4.3.3.10. LIMIT .. OFFSET clause
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
While being extremely useful for every application that does pagination, or just to limit result sets to reasonable sizes, this clause is not yet part of any SQL standard (up until SQL:2008). Hence, there exist a variety of possible implementations in various SQL dialects, concerning this limit clause. jOOQ chose to implement the LIMIT .. OFFSET clause as understood and supported by MySQL, H2, HSQLDB, Postgres, and SQLite. Here is an example of how to apply limits with jOOQ:
create.select().from(BOOK).limit(1).offset(2).fetch();
This will limit the result to 1 books starting with the 2nd book (starting at offset 0!). limit() is supported in all dialects, offset() in all but Sybase ASE, which has no reasonable means to emulate it. This is how jOOQ trivially emulates the above query in various SQL dialects with native OFFSET
pagination support:
-- MySQL, H2, HSQLDB, Postgres, and SQLite SELECT * FROM BOOK LIMIT 1 OFFSET 2 -- CUBRID supports a MySQL variant of the LIMIT .. OFFSET clause SELECT * FROM BOOK LIMIT 2, 1 -- Derby, SQL Server 2012, Oracle 12c (syntax not yet supported by jOOQ), the SQL:2008 standard SELECT * FROM BOOK OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY -- Informix has SKIP .. FIRST support SELECT SKIP 2 FIRST 1 * FROM BOOK -- Ingres (almost the SQL:2008 standard) SELECT * FROM BOOK OFFSET 2 FETCH FIRST 1 ROWS ONLY -- Firebird SELECT * FROM BOOK ROWS 2 TO 3 -- Sybase SQL Anywhere SELECT TOP 1 START AT 3 * FROM BOOK -- DB2 (almost the SQL:2008 standard, without OFFSET) SELECT * FROM BOOK FETCH FIRST 1 ROWS ONLY -- Sybase ASE, SQL Server 2008 (without OFFSET) SELECT TOP 1 * FROM BOOK
Things get a little more tricky in those databases that have no native idiom for OFFSET
pagination (actual queries may vary):
-- DB2 (with OFFSET), SQL Server 2008 (with OFFSET) SELECT * FROM ( SELECT BOOK.*, ROW_NUMBER() OVER (ORDER BY ID ASC) AS RN FROM BOOK ) AS X WHERE RN > 2 AND RN <= 3 -- DB2 (with OFFSET), SQL Server 2008 (with OFFSET) SELECT * FROM ( SELECT DISTINCT BOOK.ID, BOOK.TITLE, DENSE_RANK() OVER (ORDER BY ID ASC, TITLE ASC) AS RN FROM BOOK ) AS X WHERE RN > 2 AND RN <= 3 -- Oracle 11g and less SELECT * FROM ( SELECT b.*, ROWNUM RN FROM ( SELECT * FROM BOOK ORDER BY ID ASC ) b WHERE ROWNUM <= 3 ) WHERE RN > 2
As you can see, jOOQ will take care of the incredibly painful ROW_NUMBER() OVER() (or ROWNUM for Oracle) filtering in subselects for you, you'll just have to write limit(1).offset(2) in any dialect.
Side-note: If you're interested in understanding why we chose ROWNUM for Oracle, please refer to this very interesting benchmark, comparing the different approaches of doing pagination in Oracle: http://www.inf.unideb.hu/~gabora/pagination/results.html.
SQL Server's ORDER BY, TOP and subqueries
As can be seen in the above example, writing correct SQL can be quite tricky, depending on the SQL dialect. For instance, with SQL Server, you cannot have an ORDER BY clause in a subquery, unless you also have a TOP clause. This is illustrated by the fact that jOOQ renders a TOP 100 PERCENT clause for you. The same applies to the fact that ROW_NUMBER() OVER() needs an ORDER BY windowing clause, even if you don't provide one to the jOOQ query. By default, jOOQ adds ordering by the first column of your projection.
Keyset pagination
Note, the LIMIT
clause can also be used with the SEEK clause for keyset pagination.
4.3.3.11. SEEK clause
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The previous chapter talked about OFFSET pagination using LIMIT .. OFFSET
, or OFFSET .. FETCH
or some other vendor-specific variant of the same. This can lead to significant performance issues when reaching a high page number, as all unneeded records need to be skipped by the database.
A much faster and more stable way to perform pagination is the so-called keyset pagination method also called seek method. jOOQ supports a synthetic seek()
clause, that can be used to perform keyset pagination. Imagine we have these data:
| ID | VALUE | PAGE_BOUNDARY | |------|-------|---------------| | ... | ... | ... | | 474 | 2 | 0 | | 533 | 2 | 1 | <-- Before page 6 | 640 | 2 | 0 | | 776 | 2 | 0 | | 815 | 2 | 0 | | 947 | 2 | 0 | | 37 | 3 | 1 | <-- Last on page 6 | 287 | 3 | 0 | | 450 | 3 | 0 | | ... | ... | ... |
Now, if we want to display page 6 to the user, instead of going to page 6 by using a record OFFSET
, we could just fetch the record strictly after the last record on page 5, which yields the values (533, 2)
. This is how you would do it with SQL or with jOOQ:
SELECT id, value FROM t WHERE (value, id) > (2, 533) ORDER BY value, id LIMIT 5
DSL.using(configuration) .select(T.ID, T.VALUE) .from(T) .orderBy(T.VALUE, T.ID) .seek(2, 533) .limit(5) .fetch();
As you can see, the jOOQ SEEK
clause is a synthetic clause that does not really exist in SQL. However, the jOOQ syntax is far more intuitive for a variety of reasons:
- It replaces
OFFSET
where you would expect - It doesn't force you to mix regular predicates with "seek" predicates
- It is typesafe
- It emulates row value expression predicates for you, in those databases that do not support them
This query now yields:
| ID | VALUE | |-----|-------| | 640 | 2 | | 776 | 2 | | 815 | 2 | | 947 | 2 | | 37 | 3 |
Note that you cannot combine the SEEK
clause with the OFFSET
clause.
More information about this great feature can be found in the jOOQ blog:
- http://blog.jooq.org/2013/10/26/faster-sql-paging-with-jooq-using-the-seek-method/
- http://blog.jooq.org/2013/11/18/faster-sql-pagination-with-keysets-continued/
Further information about offset pagination vs. keyset pagination performance can be found on our partner page:
4.3.3.12. FOR UPDATE clause
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
For inter-process synchronisation and other reasons, you may choose to use the SELECT .. FOR UPDATE clause to indicate to the database, that a set of cells or records should be locked by a given transaction for subsequent updates. With jOOQ, this can be achieved as such:
SELECT * FROM BOOK WHERE ID = 3 FOR UPDATE
create.select() .from(BOOK) .where(BOOK.ID.eq(3)) .forUpdate() .fetch();
The above example will produce a record-lock, locking the whole record for updates. Some databases also support cell-locks using FOR UPDATE OF ..
SELECT * FROM BOOK WHERE ID = 3 FOR UPDATE OF TITLE
create.select() .from(BOOK) .where(BOOK.ID.eq(3)) .forUpdate().of(BOOK.TITLE) .fetch();
Oracle goes a bit further and also allows to specify the actual locking behaviour. It features these additional clauses, which are all supported by jOOQ:
-
FOR UPDATE NOWAIT
: This is the default behaviour. If the lock cannot be acquired, the query fails immediately -
FOR UPDATE WAIT n
: Try to wait for [n] seconds for the lock acquisition. The query will fail only afterwards -
FOR UPDATE SKIP LOCKED
: This peculiar syntax will skip all locked records. This is particularly useful when implementing queue tables with multiple consumers
With jOOQ, you can use those Oracle extensions as such:
create.select().from(BOOK).where(BOOK.ID.eq(3)).forUpdate().nowait().fetch(); create.select().from(BOOK).where(BOOK.ID.eq(3)).forUpdate().wait(5).fetch(); create.select().from(BOOK).where(BOOK.ID.eq(3)).forUpdate().skipLocked().fetch();
FOR UPDATE in CUBRID and SQL Server
The SQL standard specifies a FOR UPDATE
clause to be applicable for cursors. Most databases interpret this as being applicable for all SELECT
statements. An exception to this rule are the CUBRID and SQL Server databases, that do not allow for any FOR UPDATE
clause in a regular SQL SELECT
statement. jOOQ emulates the FOR UPDATE
behaviour, by locking record by record with JDBC. JDBC allows for specifying the flags TYPE_SCROLL_SENSITIVE
, CONCUR_UPDATABLE
for any statement, and then using ResultSet.updateXXX() methods to produce a cell-lock / row-lock. Here's a simplified example in JDBC:
try ( PreparedStatement stmt = connection.prepareStatement( "SELECT * FROM author WHERE id IN (3, 4, 5)", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = stmt.executeQuery() ) { while (rs.next()) { // UPDATE the primary key for row-locks, or any other columns for cell-locks rs.updateObject(1, rs.getObject(1)); rs.updateRow(); // Do more stuff with this record } }
The main drawback of this approach is the fact that the database has to maintain a scrollable cursor, whose records are locked one by one. This can cause a major risk of deadlocks or race conditions if the JDBC driver can recover from the unsuccessful locking, if two Java threads execute the following statements:
-- thread 1 SELECT * FROM author ORDER BY id ASC; -- thread 2 SELECT * FROM author ORDER BY id DESC;
So use this technique with care, possibly only ever locking single rows!
Pessimistic (shared) locking with the FOR SHARE
clause
Some databases (MySQL, Postgres) also allow to issue a non-exclusive lock explicitly using a FOR SHARE
clause. This is also supported by jOOQ
Optimistic locking in jOOQ
Note, that jOOQ also supports optimistic locking, if you're doing simple CRUD. This is documented in the section's manual about optimistic locking.
4.3.3.13. UNION, INTERSECTION and EXCEPT
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
SQL allows to perform set operations as understood in standard set theory on result sets. These operations include unions, intersections, subtractions. For two subselects to be combinable by such a set operator, each subselect must return a table expression of the same degree and type.
UNION and UNION ALL
These operators combine two results into one. While UNION
removes all duplicate records resulting from this combination, UNION ALL
leaves subselect results as they are. Typically, you should prefer UNION ALL
over UNION
, if you don't really need to remove duplicates. The following example shows how to use such a UNION
operation in jOOQ.
SELECT * FROM BOOK WHERE ID = 3 UNION ALL SELECT * FROM BOOK WHERE ID = 5
create.selectFrom(BOOK).where(BOOK.ID.eq(3)) .unionAll( create.selectFrom(BOOK).where(BOOK.ID.eq(5))) .fetch();
INTERSECT [ ALL ] and EXCEPT [ ALL ]
INTERSECT
is the operation that produces only those values that are returned by both subselects. EXCEPT
is the operation that returns only those values that are returned exclusively in the first subselect. Both operators will remove duplicates from their results. The SQL standard allows to specify the ALL
keyword for both of these operators as well, but this is hardly supported in any database. jOOQ does not support INTERSECT ALL
, EXEPT ALL
operations either.
jOOQ's set operators and how they're different from standard SQL
As previously mentioned in the manual's section about the ORDER BY clause, jOOQ has slightly changed the semantics of these set operators. While in SQL, a subselect may not contain any ORDER BY clause or LIMIT clause (unless you wrap the subselect into a nested SELECT), jOOQ allows you to do so. In order to select both the youngest and the oldest author from the database, you can issue the following statement with jOOQ (rendered to the MySQL dialect):
(SELECT * FROM AUTHOR ORDER BY DATE_OF_BIRTH ASC LIMIT 1) UNION (SELECT * FROM AUTHOR ORDER BY DATE_OF_BIRTH DESC LIMIT 1) ORDER BY 1
create.selectFrom(AUTHOR) .orderBy(AUTHOR.DATE_OF_BIRTH.asc()).limit(1) .union( selectFrom(AUTHOR) .orderBy(AUTHOR.DATE_OF_BIRTH.desc()).limit(1)) .orderBy(1) .fetch();
In case your database doesn't support ordered UNION
subselects, the subselects are nested in derived tables:
SELECT * FROM ( SELECT * FROM AUTHOR ORDER BY DATE_OF_BIRTH ASC LIMIT 1 ) UNION SELECT * FROM ( SELECT * FROM AUTHOR ORDER BY DATE_OF_BIRTH DESC LIMIT 1 ) ORDER BY 1
Projection typesafety for degrees between 1 and 22
Two subselects that are combined by a set operator are required to be of the same degree and, in most databases, also of the same type. jOOQ 3.0's introduction of Typesafe Record[N] types helps compile-checking these constraints:
// Some sample SELECT statements Select<Record2<Integer, String>> s1 = select(BOOK.ID, BOOK.TITLE).from(BOOK); Select<Record1<Integer>> s2 = selectOne(); Select<Record2<Integer, Integer>> s3 = select(one(), zero()); Select<Record2<Integer, String>> s4 = select(one(), inline("abc")); // Let's try to combine them: s1.union(s2); // Doesn't compile because of a degree mismatch. Expected: Record2<...>, got: Record1<...> s1.union(s3); // Doesn't compile because of a type mismatch. Expected: <Integer, String>, got: <Integer, Integer> s1.union(s4); // OK. The two Record[N] types match
4.3.3.14. Oracle-style hints
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
If you are closely coupling your application to an Oracle (or CUBRID) database, you might need to be able to pass hints of the form /*+HINT*/
with your SQL statements to the Oracle database. For example:
SELECT /*+ALL_ROWS*/ FIRST_NAME, LAST_NAME FROM AUTHOR
This can be done in jOOQ using the .hint()
clause in your SELECT statement:
create.select(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .hint("/*+ALL_ROWS*/") .from(AUTHOR) .fetch();
Note that you can pass any string in the .hint()
clause. If you use that clause, the passed string will always be put in between the SELECT [DISTINCT]
keywords and the actual projection list. This can be useful in other databases too, such as MySQL, for instance:
SELECT SQL_CALC_FOUND_ROWS field1, field2 FROM table1
create.select(field1, field2) .hint("SQL_CALC_FOUND_ROWS") .from(table1) .fetch()
4.3.3.15. Lexical and logical SELECT clause order
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
SQL has a lexical and a logical order of SELECT
clauses. The lexical order of SELECT
clauses is inspired by the English language. As SQL statements are commands for the database, it is natural to express a statement in an imperative tense, such as "SELECT this and that!".
Logical SELECT clause order
The logical order of SELECT
clauses, however, does not correspond to the syntax. In fact, the logical order is this:
- The FROM clause: First, all data sources are defined and joined
- The WHERE clause: Then, data is filtered as early as possible
- The CONNECT BY clause: Then, data is traversed iteratively or recursively, to produce new tuples
- The GROUP BY clause: Then, data is reduced to groups, possibly producing new tuples if grouping functions like ROLLUP(), CUBE(), GROUPING SETS() are used
- The HAVING clause: Then, data is filtered again
-
The SELECT clause: Only now, the projection is evaluated. In case of a
SELECT DISTINCT
statement, data is further reduced to remove duplicates -
The UNION clause: Optionally, the above is repeated for several
UNION
-connected subqueries. Unless this is aUNION ALL
clause, data is further reduced to remove duplicates - The ORDER BY clause: Now, all remaining tuples are ordered
- The LIMIT clause: Then, a paginating view is created for the ordered tuples
- The FOR UPDATE clause: Finally, pessimistic locking is applied
The SQL Server documentation also explains this, with slightly different clauses:
-
FROM
-
ON
-
JOIN
-
WHERE
-
GROUP BY
-
WITH CUBE
orWITH ROLLUP
-
HAVING
-
SELECT
-
DISTINCT
-
ORDER BY
-
TOP
As can be seen, databases have to logically reorder a SQL statement in order to determine the best execution plan.
Alternative syntaxes: LINQ, SLICK
Some "higher-level" abstractions, such as C#'s LINQ or Scala's SLICK try to inverse the lexical order of SELECT
clauses to what appears to be closer to the logical order. The obvious advantage of moving the SELECT
clause to the end is the fact that the projection type, which is the record type returned by the SELECT
statement can be re-used more easily in the target environment of the internal domain specific language.
A LINQ example:
// LINQ-to-SQL looks somewhat similar to SQL // AS clause // FROM clause From p In db.Products // WHERE clause Where p.UnitsInStock <= p.ReorderLevel AndAlso Not p.Discontinued // SELECT clause Select p
A SLICK example:
// "for" is the "entry-point" to the DSL val q = for { // FROM clause WHERE clause c <- Coffees if c.supID === 101 // SELECT clause and projection to a tuple } yield (c.name, c.price)
While this looks like a good idea at first, it only complicates translation to more advanced SQL statements while impairing readability for those users that are used to writing SQL. jOOQ is designed to look just like SQL. This is specifically true for SLICK, which not only changed the SELECT
clause order, but also heavily "integrated" SQL clauses with the Scala language.
For these reasons, the jOOQ DSL API is modelled in SQL's lexical order.
4.3.4. The INSERT statement
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The INSERT
statement is used to insert new records into a database table. Records can either be supplied using a VALUES()
constructor, or a SELECT
statement. jOOQ supports both types of INSERT
statements. An example of an INSERT
statement using a VALUES()
constructor is given here:
INSERT INTO AUTHOR (ID, FIRST_NAME, LAST_NAME) VALUES (100, 'Hermann', 'Hesse');
create.insertInto(AUTHOR, AUTHOR.ID, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .values(100, "Hermann", "Hesse") .execute();
Note that for explicit degrees up to 22, the VALUES()
constructor provides additional typesafety. The following example illustrates this:
InsertValuesStep3<AuthorRecord, Integer, String, String> step = create.insertInto(AUTHOR, AUTHOR.ID, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME); step.values("A", "B", "C"); // ^^^ Doesn't compile, the expected type is Integer
INSERT multiple rows with the VALUES() constructor
The SQL standard specifies that multiple rows can be supplied to the VALUES() constructor in an INSERT statement. Here's an example of a multi-record INSERT
INSERT INTO AUTHOR (ID, FIRST_NAME, LAST_NAME) VALUES (100, 'Hermann', 'Hesse'), (101, 'Alfred', 'Döblin');
create.insertInto(AUTHOR, AUTHOR.ID, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .values(100, "Hermann", "Hesse") .values(101, "Alfred", "Döblin") .execute()
jOOQ tries to stay close to actual SQL. In detail, however, Java's expressiveness is limited. That's why the values() clause is repeated for every record in multi-record inserts.
Some RDBMS do not support inserting several records in a single statement. In those cases, jOOQ emulates multi-record INSERTs using the following SQL:
INSERT INTO AUTHOR (ID, FIRST_NAME, LAST_NAME) SELECT 100, 'Hermann', 'Hesse' FROM DUAL UNION ALL SELECT 101, 'Alfred', 'Döblin' FROM DUAL;
create.insertInto(AUTHOR, AUTHOR.ID, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .values(100, "Hermann", "Hesse") .values(101, "Alfred", "Döblin") .execute();
INSERT using jOOQ's alternative syntax
MySQL (and some other RDBMS) allow for using a non-SQL-standard, UPDATE-like syntax for INSERT statements. This is also supported in jOOQ (and emulated for all databases), should you prefer that syntax. The above INSERT statement can also be expressed as follows:
create.insertInto(AUTHOR) .set(AUTHOR.ID, 100) .set(AUTHOR.FIRST_NAME, "Hermann") .set(AUTHOR.LAST_NAME, "Hesse") .newRecord() .set(AUTHOR.ID, 101) .set(AUTHOR.FIRST_NAME, "Alfred") .set(AUTHOR.LAST_NAME, "Döblin") .execute();
As you can see, this syntax is a bit more verbose, but also more readable, as every field can be matched with its value. Internally, the two syntaxes are strictly equivalent.
MySQL's INSERT .. ON DUPLICATE KEY UPDATE
The MySQL database supports a very convenient way to INSERT or UPDATE a record. This is a non-standard extension to the SQL syntax, which is supported by jOOQ and emulated in other RDBMS, where this is possible (e.g. if they support the SQL standard MERGE statement). Here is an example how to use the ON DUPLICATE KEY UPDATE clause:
// Add a new author called "Koontz" with ID 3. // If that ID is already present, update the author's name create.insertInto(AUTHOR, AUTHOR.ID, AUTHOR.LAST_NAME) .values(3, "Koontz") .onDuplicateKeyUpdate() .set(AUTHOR.LAST_NAME, "Koontz") .execute();
The synthetic ON DUPLICATE KEY IGNORE clause
The MySQL database also supports an INSERT IGNORE INTO clause. This is supported by jOOQ using the more convenient SQL syntax variant of ON DUPLICATE KEY IGNORE, which can be equally emulated in other databases using a MERGE statement:
// Add a new author called "Koontz" with ID 3. // If that ID is already present, ignore the INSERT statement create.insertInto(AUTHOR, AUTHOR.ID, AUTHOR.LAST_NAME) .values(3, "Koontz") .onDuplicateKeyIgnore() .execute();
Postgres's INSERT .. RETURNING
The Postgres database has native support for an INSERT .. RETURNING clause. This is a very powerful concept that is emulated for all other dialects using JDBC's getGeneratedKeys() method. Take this example:
// Add another author, with a generated ID Record<?> record = create.insertInto(AUTHOR, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .values("Charlotte", "Roche") .returning(AUTHOR.ID) .fetchOne(); System.out.println(record.getValue(AUTHOR.ID)); // For some RDBMS, this also works when inserting several values // The following should return a 2x2 table Result<?> result = create.insertInto(AUTHOR, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .values("Johann Wolfgang", "von Goethe") .values("Friedrich", "Schiller") // You can request any field. Also trigger-generated values .returning(AUTHOR.ID, AUTHOR.CREATION_DATE) .fetch();
Some databases have poor support for returning generated keys after INSERTs. In those cases, jOOQ might need to issue another SELECT statement in order to fetch an @@identity value. Be aware, that this can lead to race-conditions in those databases that cannot properly return generated ID values. For more information, please consider the jOOQ Javadoc for the returning() clause.
The INSERT SELECT statement
In some occasions, you may prefer the INSERT SELECT syntax, for instance, when you copy records from one table to another:
create.insertInto(AUTHOR_ARCHIVE) .select(create.selectFrom(AUTHOR).where(AUTHOR.DECEASED.isTrue())) .execute();
4.3.5. The UPDATE statement
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The UPDATE statement is used to modify one or several pre-existing records in a database table. UPDATE statements are only possible on single tables. Support for multi-table updates will be implemented in the near future. An example update query is given here:
UPDATE AUTHOR SET FIRST_NAME = 'Hermann', LAST_NAME = 'Hesse' WHERE ID = 3;
create.update(AUTHOR) .set(AUTHOR.FIRST_NAME, "Hermann") .set(AUTHOR.LAST_NAME, "Hesse") .where(AUTHOR.ID.eq(3)) .execute();
Most databases allow for using scalar subselects in UPDATE statements in one way or another. jOOQ models this through a set(Field<T>, Select<? extends Record1<T>>)
method in the UPDATE
DSL API:
UPDATE AUTHOR SET FIRST_NAME = ( SELECT FIRST_NAME FROM PERSON WHERE PERSON.ID = AUTHOR.ID ), WHERE ID = 3;
create.update(AUTHOR) .set(AUTHOR.FIRST_NAME, select(PERSON.FIRST_NAME) .from(PERSON) .where(PERSON.ID.eq(AUTHOR.ID)) ) .where(AUTHOR.ID.eq(3)) .execute();
Using row value expressions in an UPDATE statement
jOOQ supports formal row value expressions in various contexts, among which the UPDATE statement. Only one row value expression can be updated at a time. Here's an example:
UPDATE AUTHOR SET (FIRST_NAME, LAST_NAME) = ('Hermann', 'Hesse') WHERE ID = 3;
create.update(AUTHOR) .set(row(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME), row("Herman", "Hesse")) .where(AUTHOR.ID.eq(3)) .execute();
This can be particularly useful when using subselects:
UPDATE AUTHOR SET (FIRST_NAME, LAST_NAME) = ( SELECT PERSON.FIRST_NAME, PERSON.LAST_NAME FROM PERSON WHERE PERSON.ID = AUTHOR.ID ) WHERE ID = 3;
create.update(AUTHOR) .set(row(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME), select(PERSON.FIRST_NAME, PERSON.LAST_NAME) .from(PERSON) .where(PERSON.ID.eq(AUTHOR.ID)) ) .where(AUTHOR.ID.eq(3)) .execute();
The above row value expressions usages are completely typesafe.
UPDATE .. FROM
Some databases, including PostgreSQL and SQL Server, support joining additional tables to an UPDATE
statement using a vendor-specific FROM
clause. This is supported as well by jOOQ:
UPDATE BOOK_ARCHIVE SET BOOK_ARCHIVE.TITLE = BOOK.TITLE FROM BOOK WHERE BOOK_ARCHIVE.ID = BOOK.ID
create.update(BOOK_ARCHIVE) .set(BOOK_ARCHIVE.TITLE, BOOK.TITLE) .from(BOOK) .where(BOOK_ARCHIVE.ID.eq(BOOK.ID)) .execute();
In many cases, such a joined update statement can be emulated using a correlated subquery, or using updatable views.
UPDATE .. RETURNING
The Firebird and Postgres databases support a RETURNING
clause on their UPDATE
statements, similar as the RETURNING
clause in INSERT statements. This is useful to fetch trigger-generated values in one go. An example is given here:
-- Fetch a trigger-generated value UPDATE BOOK SET TITLE = 'Animal Farm' WHERE ID = 5 RETURNING TITLE
String title = create.update(BOOK) .set(BOOK.TITLE, "Animal Farm") .where(BOOK.ID.eq(5)) .returning(BOOK.TITLE) .fetchOne().getValue(BOOK.TITLE);
The UPDATE .. RETURNING
clause is currently not emulated for other databases. Future versions might execute an additional SELECT statement to fetch results.
4.3.6. The DELETE statement
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The DELETE statement removes records from a database table. DELETE statements are only possible on single tables. Support for multi-table deletes will be implemented in the near future. An example delete query is given here:
DELETE AUTHOR WHERE ID = 100;
create.delete(AUTHOR) .where(AUTHOR.ID.eq(100)) .execute();
4.3.7. The MERGE statement
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The MERGE statement is one of the most advanced standardised SQL constructs, which is supported by DB2, HSQLDB, Oracle, SQL Server and Sybase (MySQL has the similar INSERT .. ON DUPLICATE KEY UPDATE construct)
The point of the standard MERGE statement is to take a TARGET table, and merge (INSERT, UPDATE) data from a SOURCE table into it. DB2, Oracle, SQL Server and Sybase also allow for DELETING some data and for adding many additional clauses. With jOOQ 3.5.4, only Oracle's MERGE extensions are supported. Here is an example:
-- Check if there is already an author called 'Hitchcock' -- If there is, rename him to John. If there isn't add him. MERGE INTO AUTHOR USING (SELECT 1 FROM DUAL) ON (LAST_NAME = 'Hitchcock') WHEN MATCHED THEN UPDATE SET FIRST_NAME = 'John' WHEN NOT MATCHED THEN INSERT (LAST_NAME) VALUES ('Hitchcock');
create.mergeInto(AUTHOR) .using(create.selectOne()) .on(AUTHOR.LAST_NAME.eq("Hitchcock")) .whenMatchedThenUpdate() .set(AUTHOR.FIRST_NAME, "John") .whenNotMatchedThenInsert(AUTHOR.LAST_NAME) .values("Hitchcock") .execute();
MERGE Statement (H2-specific syntax)
The H2 database ships with a somewhat less powerful but a little more intuitive syntax for its own version of the MERGE statement. An example more or less equivalent to the previous one can be seen here:
-- Check if there is already an author called 'Hitchcock' -- If there is, rename him to John. If there isn't add him. MERGE INTO AUTHOR (FIRST_NAME, LAST_NAME) KEY (LAST_NAME) VALUES ('John', 'Hitchcock')
create.mergeInto(AUTHOR, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .key(AUTHOR.LAST_NAME) .values("John", "Hitchcock") .execute();
This syntax can be fully emulated by jOOQ for all other databases that support the SQL standard MERGE statement. For more information about the H2 MERGE syntax, see the documentation here:
http://www.h2database.com/html/grammar.html#merge
Typesafety of VALUES() for degrees up to 22
Much like the INSERT statement, the MERGE
statement's VALUES()
clause provides typesafety for degrees up to 22, in both the standard syntax variant as well as the H2 variant.
4.4. SQL Statements (DDL)
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The Data Definition Language (DDL) is used to CREATE, ALTER, and DROP various object types in the database catalog. jOOQ supports an increasing number of these operations natively, and also adds synthetic operation support for convenience.
While many DDL statements are supported natively, and have a 1:1 correspondence to the jOOQ API's representation, dialects differ in many subtle ways when it comes to DDL statement support. These differences may include:
- Different keywords to mean the same thing. For example, the keywords
ALTER
,CHANGE
, orMODIFY
may be used when altering columns or other attributes in a table. - Different statements instead of subclauses. For example, some dialects may choose to support
RENAME [object type] .. TO ..
statements instead of making the rename operation a subclause ofALTER [object type] .. RENAME TO ..
- Some syntax may not be supported, or not be supported consistently, such as the various
IF EXISTS
andIF NOT EXISTS
clauses. Emulations are possible using the dialect's procedural language
Because of these many differences, the jOOQ manual will not list each individual native SQL representation of each jOOQ API call. Also, some optional clauses may exist, such as the IF EXISTS
or OR REPLACE
clauses, which can easily be discovered from the API. The manual will omit documenting these clauses in every example.
4.4.1. The ALTER statement
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
ALTER
statements are used to alter properties of existing objects in the database catalog.
4.4.1.1. ALTER SEQUENCE
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The following types of statements are supported when altering a sequence:
Alter sequence properties
jOOQ supports a variety of sequence properties through meta data and DDL.
// Let the sequence restart with MINVALUE or with a specific value create.alterSequence(S_AUTHOR_ID).restart().execute();
4.4.1.2. ALTER TABLE
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The ALTER TABLE
statement is certainly the most powerful among DDL statements, as tables are the most important object type in a database catalog. The following types of statements are supported when altering a table:
ADD
Columns can be added to a table using the following API:
// Adding a single column to a table create.alterTable("table").add("column", INTEGER).execute();
There exists alternative API representing optional keywords, such as e.g. addColumn()
, which have been omitted from the examples.
Note that some dialects also consider indexes to be a part of a table, but jOOQ does not yet support ALTER TABLE
subclauses modifying indexes. Consider CREATE INDEX or DROP INDEX, instead.
ALTER
Both of the above objects can be altered in a table using the following API:
// Specify a new default value for a column create.alterTable("table").alter("column").default_(1).execute(); // Set a new data type on the column create.alterTable("table").alter("column").set(VARCHAR(50)).execute();
There exists alternative API representing optional keywords, such as e.g. alterColumn()
, which have been omitted from the examples.
DROP
Both columns and constraints can also be dropped from tables using this API:
// Drop a single column create.alterTable("table").drop("column").execute(); // Add CASCADE or RESTRICT clauses when dropping columns (or constraints) create.alterTable("table").drop("column").cascade().execute(); create.alterTable("table").drop("column").restrict().execute(); // Drop a constraint create.alterTable("table").dropConstraint("uk").execute();
4.4.2. The CREATE statement
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The CREATE
statement is the most important DDL statement. It allows for creating new objects in the database catalog.
4.4.2.1. CREATE INDEX
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The CREATE INDEX
statement allows for creating indexes on table columns.
CREATE INDEX
In its simplest form, the statement can be used like this:
// Create an index on a single column create.createIndex("index").on("table", "column").execute(); // Create an index on several columns create.createIndex("index").on("table", "column1", "column2").execute();
4.4.2.2. CREATE SEQUENCE
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The CREATE SEQUENCE
statement is used to create a new sequence in the database catalog.
// Create a sequence with default flags create.createSequence("sequence").execute();
4.4.2.3. CREATE TABLE
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Arguably the most used DDL statement is the CREATE TABLE
statement.
Create a table with columns
There are a few overloads to achieve the same thing with different types of parameters, as always. To keep things simple, only one example is given.
// Create a new table with a column create.createTable("table") .column("column1", INTEGER) .execute();
CREATE TABLE AS SELECT
Occasionally, creating a table from a SELECT statement is very useful, copying the source table's data types and data.
// Create a new table from a source SELECT statement create.createTable("book_archive") .as(select(BOOK.ID, BOOK.TITLE).from(BOOK)) .execute();
4.4.2.4. CREATE VIEW
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
This statement allows for creating a VIEW
in the database catalog:
// Create a new view create.createView("books_and_authors", "author_id", "first_name", "last_name", "book_id", "title") .as(select(AUTHOR.ID, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, BOOK.ID, BOOK.TITLE) .from(AUTHOR) .join(BOOK).on(AUTHOR.ID.eq(BOOK.AUTHOR_ID))) .execute();
4.4.3. The DROP statement
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The DROP
statement is used to drop objects from the database catalog.
4.4.3.1. DROP INDEX
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
This statement is used to drop an INDEX
from the database catalog.
// Drop an index (for indexes stored in the schema namespace, i.e. most dialects) create.dropIndex("index").execute(); // Drop an index (for indexes stored in the table namespace, e.g. MySQL, SQL Server) create.dropIndex("index").on("table").execute();
CASCADE
It is possible to supply a CASCADE
or RESTRICT
clause, explicitly
// Specify the CASCADE / RESTRICT clauses explicitly create.dropIndex("index").cascade().execute(); create.dropIndex("index").restrict().execute();
4.4.3.2. DROP SEQUENCE
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
This statement is used to drop an SEQUENCE
from the database catalog.
// Drop a sequence create.dropSequence("sequence").execute();
4.4.3.3. DROP TABLE
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
This statement is used to drop an TABLE
from the database catalog.
// Drop a table create.dropTable("table").execute();
CASCADE
It is possible to supply a CASCADE
or RESTRICT
clause, explicitly
// Specify the CASCADE / RESTRICT clauses explicitly create.dropTable("table").cascade().execute(); create.dropTable("table").restrict().execute();
4.4.3.4. DROP VIEW
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
This statement is used to drop an VIEW
from the database catalog.
// Drop a view create.dropView("view").execute();
4.4.4. The TRUNCATE statement
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Even if the TRUNCATE
statement mainly modifies data, it is generally considered to be a DDL statement. It is popular in many databases when you want to bypass constraints for table truncation. Databases may behave differently, when a truncated table is referenced by other tables. For instance, they may fail if records from a truncated table are referenced, even with ON DELETE CASCADE
clauses in place. Please, consider your database manual to learn more about its TRUNCATE
implementation.
The TRUNCATE
syntax is trivial:
create.truncate(AUTHOR).execute();
TRUNCATE
is not supported by Ingres and SQLite. jOOQ will execute a DELETE FROM AUTHOR
statement instead.
4.5. Table expressions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The following sections explain the various types of table expressions supported by jOOQ
4.5.1. Generated Tables
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Most of the times, when thinking about a table expression you're probably thinking about an actual table in your database schema. If you're using jOOQ's code generator, you will have all tables from your database schema available to you as type safe Java objects. You can then use these tables in SQL FROM clauses, JOIN clauses or in other SQL statements, just like any other table expression. An example is given here:
SELECT * FROM AUTHOR -- Table expression AUTHOR JOIN BOOK -- Table expression BOOK ON (AUTHOR.ID = BOOK.AUTHOR_ID)
create.select() .from(AUTHOR) // Table expression AUTHOR .join(BOOK) // Table expression BOOK .on(AUTHOR.ID.eq(BOOK.AUTHOR_ID)) .fetch();
The above example shows how AUTHOR and BOOK tables are joined in a SELECT statement. It also shows how you can access table columns by dereferencing the relevant Java attributes of their tables.
See the manual's section about generated tables for more information about what is really generated by the code generator
4.5.2. Aliased Tables
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The strength of jOOQ's code generator becomes more obvious when you perform table aliasing and dereference fields from generated aliased tables. This can best be shown by example:
-- Select all books by authors born after 1920, -- named "Paulo" from a catalogue: SELECT * FROM author a JOIN book b ON a.id = b.author_id WHERE a.year_of_birth > 1920 AND a.first_name = 'Paulo' ORDER BY b.title
// Declare your aliases before using them in SQL: Author a = AUTHOR.as("a"); Book b = BOOK.as("b"); // Use aliased tables in your statement create.select() .from(a) .join(b).on(a.ID.eq(b.AUTHOR_ID)) .where(a.YEAR_OF_BIRTH.gt(1920) .and(a.FIRST_NAME.eq("Paulo"))) .orderBy(b.TITLE) .fetch();
As you can see in the above example, calling as()
on generated tables returns an object of the same type as the table. This means that the resulting object can be used to dereference fields from the aliased table. This is quite powerful in terms of having your Java compiler check the syntax of your SQL statements. If you remove a column from a table, dereferencing that column from that table alias will cause compilation errors.
Dereferencing columns from other table expressions
Only few table expressions provide the SQL syntax typesafety as shown above, where generated tables are used. Most tables, however, expose their fields through field()
methods:
// "Type-unsafe" aliased table: Table<?> a = AUTHOR.as("a"); // Get fields from a: Field<?> id = a.field("ID"); Field<?> firstName = a.field("FIRST_NAME");
Derived column lists
The SQL standard specifies how a table can be renamed / aliased in one go along with its columns. It references the term "derived column list" for the following syntax (as supported by Postgres, for instance):
SELECT t.a, t.b FROM ( SELECT 1, 2 ) t(a, b)
This feature is useful in various use-cases where column names are not known in advance (but the table's degree is!). An example for this are unnested tables, or the VALUES() table constructor:
-- Unnested tables SELECT t.a, t.b FROM unnest(my_table_function()) t(a, b) -- VALUES() constructor SELECT t.a, t.b FROM VALUES(1, 2),(3, 4) t(a, b)
Only few databases really support such a syntax, but fortunately, jOOQ can emulate it easily using UNION ALL
and an empty dummy record specifying the new column names. The two statements are equivalent:
-- Using derived column lists SELECT t.a, t.b FROM ( SELECT 1, 2 ) t(a, b) -- Using UNION ALL and a dummy record SELECT t.a, t.b FROM ( SELECT null a, null b FROM DUAL WHERE 1 = 0 UNION ALL SELECT 1, 2 FROM DUAL ) t
In jOOQ, you would simply specify a varargs list of column aliases as such:
// Unnested tables create.select().from(unnest(myTableFunction()).as("t", "a", "b")).fetch(); // VALUES() constructor create.select().from(values( row(1, 2), row(3, 4) ).as("t", "a", "b")) .fetch();
Unnamed derived tables
The org.jooq.Table type can reference a derived table:
-- Derived table (SELECT 1 AS a)
// Derived table table(select(inline(1).as("a")));
Most databases do not support unnamed derived tables, they require an explicit alias. If you do not provide jOOQ with such an explicit alias, an alias will be generated based on the derived table's content, to make sure the generated SQL will be syntactically correct. The generated alias is not specified and should not be referenced explicitly.
4.5.3. Joined tables
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The JOIN operators that can be used in SQL SELECT statements are the most powerful and best supported means of creating new table expressions in SQL. Informally, the following can be said:
A(colA1, ..., colAn) "join" B(colB1, ..., colBm) "produces" C(colA1, ..., colAn, colB1, ..., colBm)
SQL and relational algebra distinguish between at least the following JOIN types (upper-case: SQL, lower-case: relational algebra):
- CROSS JOIN or cartesian product: The basic JOIN in SQL, producing a relational cross product, combining every record of table A with every record of table B. Note that cartesian products can also be produced by listing comma-separated table expressions in the FROM clause of a SELECT statement
- NATURAL JOIN: The basic JOIN in relational algebra, yet a rarely used JOIN in databases with everyday degree of normalisation. This JOIN type unconditionally equi-joins two tables by all columns with the same name (requiring foreign keys and primary keys to share the same name). Note that the JOIN columns will only figure once in the resulting table expression.
- INNER JOIN or equi-join: This JOIN operation performs a cartesian product (CROSS JOIN) with a filtering predicate being applied to the resulting table expression. Most often, a equal comparison predicate comparing foreign keys and primary keys will be applied as a filter, but any other predicate will work, too.
- OUTER JOIN: This JOIN operation performs a cartesian product (CROSS JOIN) with a filtering predicate being applied to the resulting table expression. Most often, a equal comparison predicate comparing foreign keys and primary keys will be applied as a filter, but any other predicate will work, too. Unlike the INNER JOIN, an OUTER JOIN will add "empty records" to the left (table A) or right (table B) or both tables, in case the conditional expression fails to produce a .
- semi-join: In SQL, this JOIN operation can only be expressed implicitly using IN predicates or EXISTS predicates. The table expression resulting from a semi-join will only contain the left-hand side table A
- anti-join: In SQL, this JOIN operation can only be expressed implicitly using NOT IN predicates or NOT EXISTS predicates. The table expression resulting from a semi-join will only contain the left-hand side table A
- division: This JOIN operation is hard to express at all, in SQL. See the manual's chapter about relational division for details on how jOOQ emulates this operation.
jOOQ supports all of these JOIN types (except semi-join and anti-join) directly on any table expression:
// jOOQ's relational division convenience syntax DivideByOnStep divideBy(Table<?> table) // Various overloaded INNER JOINs TableOnStep join(TableLike<?>) TableOnStep join(String) TableOnStep join(String, Object...) TableOnStep join(String, QueryPart...) // Various overloaded OUTER JOINs (supporting Oracle's partitioned OUTER JOIN) // Overloading is similar to that of INNER JOIN TablePartitionByStep leftOuterJoin(TableLike<?>) TablePartitionByStep rightOuterJoin(TableLike<?>) // Various overloaded FULL OUTER JOINs TableOnStep fullOuterJoin(TableLike<?>) // Various overloaded CROSS JOINs Table<Record> crossJoin(TableLike<?>) // Various overloaded NATURAL JOINs Table<Record> naturalJoin(TableLike<?>) Table<Record> naturalLeftOuterJoin(TableLike<?>) Table<Record> naturalRightOuterJoin(TableLike<?>)
Note that most of jOOQ's JOIN operations give way to a similar DSL API hierarchy as previously seen in the manual's section about the JOIN clause
4.5.4. The VALUES() table constructor
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Some databases allow for expressing in-memory temporary tables using a VALUES()
constructor. This constructor usually works the same way as the VALUES()
clause known from the INSERT statement or from the MERGE statement. With jOOQ, you can also use the VALUES()
table constructor, to create tables that can be used in a SELECT statement's FROM clause:
SELECT a, b FROM VALUES(1, 'a'), (2, 'b') t(a, b)
create.select() .from(values(row(1, "a"), row(2, "b")).as("t", "a", "b")) .fetch();
Note, that it is usually quite useful to provide column aliases ("derived column lists") along with the table alias for the VALUES()
constructor.
The above statement is emulated by jOOQ for those databases that do not support the VALUES()
constructor, natively (actual emulations may vary):
-- If derived column expressions are supported: SELECT a, b FROM ( SELECT 1, 'a' FROM DUAL UNION ALL SELECT 2, 'b' FROM DUAL ) t(a, b) -- If derived column expressions are not supported: SELECT a, b FROM ( -- An empty dummy record is added to provide column names for the emulated derived column expression SELECT NULL a, NULL b FROM DUAL WHERE 1 = 0 UNION ALL -- Then, the actual VALUES() constructor is emulated SELECT 1, 'a' FROM DUAL UNION ALL SELECT 2, 'b' FROM DUAL ) t
4.5.5. Nested SELECTs
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
A SELECT statement can appear almost anywhere a table expression can. Such a "nested SELECT" is often called a "derived table". Apart from many convenience methods accepting org.jooq.Select objects directly, a SELECT statement can always be transformed into a org.jooq.Table object using the asTable() method.
Example: Scalar subquery
SELECT * FROM BOOK WHERE BOOK.AUTHOR_ID = ( SELECT ID FROM AUTHOR WHERE LAST_NAME = 'Orwell')
create.select() .from(BOOK) .where(BOOK.AUTHOR_ID.eq(create .select(AUTHOR.ID) .from(AUTHOR) .where(AUTHOR.LAST_NAME.eq("Orwell")))) .fetch();
Example: Derived table
SELECT nested.* FROM ( SELECT AUTHOR_ID, count(*) books FROM BOOK GROUP BY AUTHOR_ID ) nested ORDER BY nested.books DESC
Table<?> nested = create.select(BOOK.AUTHOR_ID, count().as("books")) .from(BOOK) .groupBy(BOOK.AUTHOR_ID).asTable("nested"); create.select(nested.fields()) .from(nested) .orderBy(nested.field("books")) .fetch();
Example: Correlated subquery
SELECT LAST_NAME, ( SELECT COUNT(*) FROM BOOK WHERE BOOK.AUTHOR_ID = AUTHOR.ID) books FROM AUTHOR ORDER BY books DESC
// The type of books cannot be inferred from the Select<?> Field<?> books = create.selectCount() .from(BOOK) .where(BOOK.AUTHOR_ID.eq(AUTHOR.ID)) .asField("books"); create.select(AUTHOR.ID, books) .from(AUTHOR) .orderBy(books, AUTHOR.ID)) .fetch();
4.5.6. The Oracle 11g PIVOT clause
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
If you are closely coupling your application to an Oracle database, you can take advantage of some Oracle-specific features, such as the PIVOT clause, used for statistical analyses. The formal syntax definition is as follows:
-- SELECT .. FROM table PIVOT (aggregateFunction [, aggregateFunction] FOR column IN (expression [, expression])) -- WHERE ..
The PIVOT clause is available from the org.jooq.Table type, as pivoting is done directly on a table. Currently, only Oracle's PIVOT clause is supported. Support for SQL Server's slightly different PIVOT clause will be added later. Also, jOOQ may emulate PIVOT for other dialects in the future.
4.5.7. jOOQ's relational division syntax
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
There is one operation in relational algebra that is not given a lot of attention, because it is rarely used in real-world applications. It is the relational division, the opposite operation of the cross product (or, relational multiplication). The following is an approximate definition of a relational division:
Assume the following cross join / cartesian product C = A × B Then it can be said that A = C ÷ B B = C ÷ A
With jOOQ, you can simplify using relational divisions by using the following syntax:
C.divideBy(B).on(C.ID.eq(B.C_ID)).returning(C.TEXT)
The above roughly translates to
SELECT DISTINCT C.TEXT FROM C "c1" WHERE NOT EXISTS ( SELECT 1 FROM B WHERE NOT EXISTS ( SELECT 1 FROM C "c2" WHERE "c2".TEXT = "c1".TEXT AND "c2".ID = B.C_ID ) )
Or in plain text: Find those TEXT values in C whose ID's correspond to all ID's in B. Note that from the above SQL statement, it is immediately clear that proper indexing is of the essence. Be sure to have indexes on all columns referenced from the on(...) and returning(...) clauses.
For more information about relational division and some nice, real-life examples, see
4.5.8. Array and cursor unnesting
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SQL standard specifies how SQL databases should implement ARRAY and TABLE types, as well as CURSOR types. Put simply, a CURSOR is a pointer to any materialised table expression. Depending on the cursor's features, this table expression can be scrolled through in both directions, records can be locked, updated, removed, inserted, etc. Often, CURSOR types contain s, whereas ARRAY and TABLE types contain simple scalar values, although that is not a requirement
ARRAY types in SQL are similar to Java's array types. They contain a "component type" or "element type" and a "dimension". This sort of ARRAY type is implemented in H2, HSQLDB and Postgres and supported by jOOQ as such. Oracle uses strongly-typed arrays, which means that an ARRAY type (VARRAY or TABLE type) has a name and possibly a maximum capacity associated with it.
Unnesting array and cursor types
The real power of these types become more obvious when you fetch them from stored procedures to unnest them as table expressions and use them in your FROM clause. An example is given here, where Oracle's DBMS_XPLAN package is used to fetch a cursor containing data about the most recent execution plan:
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(null, null, 'ALLSTATS'));
create.select() .from(table(DbmsXplan.displayCursor(null, null, "ALLSTATS")) .fetch();
Note, in order to access the DbmsXplan package, you can use the code generator to generate Oracle's SYS schema.
4.5.9. Table-valued functions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Some databases support functions that can produce tables for use in arbitrary SELECT statements. jOOQ supports these functions out-of-the-box for such databases. For instance, in SQL Server, the following function produces a table of (ID, TITLE)
columns containing either all the books or just one book by ID:
CREATE FUNCTION f_books (@id INTEGER) RETURNS @out_table TABLE ( id INTEGER, title VARCHAR(400) ) AS BEGIN INSERT @out_table SELECT id, title FROM book WHERE @id IS NULL OR id = @id ORDER BY id RETURN END
The jOOQ code generator will now produce a generated table from the above, which can be used as a SQL function:
// Fetching all books records Result<FBooksRecord> r1 = create.selectFrom(fBooks(null)).fetch(); // Lateral joining the table-valued function to another table using CROSS APPLY: create.select(BOOK.ID, F_BOOKS.TITLE) .from(BOOK.crossApply(fBooks(BOOK.ID))) .fetch();
4.5.10. The DUAL table
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SQL standard specifies that the FROM clause is optional in a SELECT statement. However, according to the standard, you may then no longer use some other clauses, such as the WHERE clause. In the real world, there exist three types of databases:
- The ones that always require a
FROM
clause (as required by the SQL standard) - The ones that never require a
FROM
clause (and still allow aWHERE
clause) - The ones that require a
FROM
clause only with aWHERE
clause,GROUP BY
clause, orHAVING
clause
With jOOQ, you don't have to worry about the above distinction of SQL dialects. jOOQ never requires a FROM
clause, but renders the necessary "DUAL"
table, if needed. The following program shows how jOOQ renders "DUAL"
tables
SELECT 1 FROM (SELECT COUNT(*) FROM MSysResources) AS dual SELECT 1 SELECT 1 FROM "db_root" SELECT 1 FROM "SYSIBM"."DUAL" SELECT 1 FROM "SYSIBM"."SYSDUMMY1" SELECT 1 FROM "RDB$DATABASE" SELECT 1 FROM dual SELECT 1 FROM "INFORMATION_SCHEMA"."SYSTEM_USERS" SELECT 1 FROM (SELECT 1 AS dual FROM systables WHERE tabid = 1) SELECT 1 FROM (SELECT 1 AS dual) AS dual SELECT 1 FROM dual SELECT 1 FROM dual SELECT 1 FROM dual SELECT 1 SELECT 1 SELECT 1 SELECT 1 FROM [SYS].[DUMMY]
DSL.using(SQLDialect.ACCESS ).selectOne().getSQL(); DSL.using(SQLDialect.ASE ).selectOne().getSQL(); DSL.using(SQLDialect.CUBRID ).selectOne().getSQL(); DSL.using(SQLDialect.DB2 ).selectOne().getSQL(); DSL.using(SQLDialect.DERBY ).selectOne().getSQL(); DSL.using(SQLDialect.FIREBIRD ).selectOne().getSQL(); DSL.using(SQLDialect.H2 ).selectOne().getSQL(); DSL.using(SQLDialect.HSQLDB ).selectOne().getSQL(); DSL.using(SQLDialect.INFORMIX ).selectOne().getSQL(); DSL.using(SQLDialect.INGRES ).selectOne().getSQL(); DSL.using(SQLDialect.MARIADB ).selectOne().getSQL(); DSL.using(SQLDialect.MYSQL ).selectOne().getSQL(); DSL.using(SQLDialect.ORACLE ).selectOne().getSQL(); DSL.using(SQLDialect.POSTGRES ).selectOne().getSQL(); DSL.using(SQLDialect.SQLITE ).selectOne().getSQL(); DSL.using(SQLDialect.SQLSERVER).selectOne().getSQL(); DSL.using(SQLDialect.SYBASE ).selectOne().getSQL();
Note, that some databases (H2, MySQL) can normally do without "DUAL"
. However, there exist some corner-cases with complex nested SELECT
statements, where this will cause syntax errors (or parser bugs). To stay on the safe side, jOOQ will always render "dual" in those dialects.
4.6. Column expressions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Column expressions can be used in various SQL clauses in order to refer to one or several columns. This chapter explains how to form various types of column expressions with jOOQ. A particular type of column expression is given in the section about tuples or row value expressions, where an expression may have a degree of more than one.
Using column expressions in jOOQ
jOOQ allows you to freely create arbitrary column expressions using a fluent expression construction API. Many expressions can be formed as functions from DSL methods, other expressions can be formed based on a pre-existing column expression. For example:
// A regular table column expression Field<String> field1 = BOOK.TITLE; // A function created from the DSL Field<String> field2 = trim(BOOK.TITLE); // More complex function with advanced DSL syntax Field<String> field4 = listAgg(BOOK.TITLE) .withinGroupOrderBy(BOOK.ID.asc()) .over().partitionBy(AUTHOR.ID);
4.6.1. Table columns
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Table columns are the most simple implementations of a column expression. They are mainly produced by jOOQ's code generator and can be dereferenced from the generated tables. This manual is full of examples involving table columns. Another example is given in this query:
SELECT BOOK.ID, BOOK.TITLE FROM BOOK WHERE BOOK.TITLE LIKE '%SQL%' ORDER BY BOOK.TITLE
create.select(BOOK.ID, BOOK.TITLE) .from(BOOK) .where(BOOK.TITLE.like("%SQL%")) .orderBy(BOOK.TITLE) .fetch();
Table columns implement a more specific interface called org.jooq.TableField, which is parameterised with its associated <R extends Record>
record type.
See the manual's section about generated tables for more information about what is really generated by the code generator
4.6.2. Aliased columns
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Just like tables, columns can be renamed using aliases. Here is an example:
SELECT FIRST_NAME || ' ' || LAST_NAME author, COUNT(*) books FROM AUTHOR JOIN BOOK ON AUTHOR.ID = AUTHOR_ID GROUP BY FIRST_NAME, LAST_NAME;
Here is how it's done with jOOQ:
Record record = create.select( concat(AUTHOR.FIRST_NAME, inline(" "), AUTHOR.LAST_NAME).as("author"), count().as("books")) .from(AUTHOR) .join(BOOK).on(AUTHOR.ID.eq(BOOK.AUTHOR_ID)) .groupBy(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .fetchAny();
When you alias Fields like above, you can access those Fields' values using the alias name:
System.out.println("Author : " + record.getValue("author")); System.out.println("Books : " + record.getValue("books"));
Unnamed column expressions
In most SQL databases, aliasing of column expressions in top level selects is optional. The database will generate a column name that is roughly based on the expression for documentation purposes (e.g. when running the query in a tool like SQL Developer), but applications cannot rely on the name explicitly. This is not a problem as columns can still be referenced by index.
In a similar fashion, jOOQ will assume an unspecified, generated column name for column expressions, based on their content.
-- Arithmetic expression 1 + 2 -- Correlated subquery (SELECT 1 AS a)
// Arithmetic expression inline(1).plus(inline(2)); // Correlated subquery field(select(inline(1).as("a")));
These unnamed expressions can be used both in SQL as well as with jOOQ. However, do note that jOOQ will use Field.getName() to extract this column name from the field, when referencing the field or when nesting it in derived tables. In order to stay in full control of any such column names, it is always a good idea to provide explicit aliasing for column expressions, both in SQL as well as in jOOQ.
4.6.3. Cast expressions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
jOOQ's source code generator tries to find the most accurate type mapping between your vendor-specific data types and a matching Java type. For instance, most VARCHAR
, CHAR
, CLOB
types will map to String. Most BINARY
, BYTEA
, BLOB
types will map to byte[]
. NUMERIC
types will default to java.math.BigDecimal, but can also be any of java.math.BigInteger, java.lang.Long, java.lang.Integer, java.lang.Short, java.lang.Byte, java.lang.Double, java.lang.Float.
Sometimes, this automatic mapping might not be what you needed, or jOOQ cannot know the type of a field. In those cases you would write SQL type CAST
like this:
-- Let's say, your Postgres column LAST_NAME was VARCHAR(30) -- Then you could do this: SELECT CAST(AUTHOR.LAST_NAME AS TEXT) FROM DUAL
in jOOQ, you can write something like that:
create.select(AUTHOR.LAST_NAME.cast(VARCHAR(100))).fetch();
The same thing can be achieved by casting a Field directly to String.class, as VARCHAR is the default SQLDataType
to map to Java's String
create.select(AUTHOR.LAST_NAME.cast(String.class)).fetch();
The complete CAST
API in org.jooq.Field consists of these three methods:
public interface Field<T> { // Cast this field to the type of another field <Z> Field<Z> cast(Field<Z> field); // Cast this field to a given DataType <Z> Field<Z> cast(DataType<Z> type); // Cast this field to the default DataType for a given Class <Z> Field<Z> cast(Class<? extends Z> type); } // And additional convenience methods in the DSL: public class DSL { <T> Field<T> cast(Object object, Field<T> field); <T> Field<T> cast(Object object, DataType<T> type); <T> Field<T> cast(Object object, Class<? extends T> type); <T> Field<T> castNull(Field<T> field); <T> Field<T> castNull(DataType<T> type); <T> Field<T> castNull(Class<? extends T> type); }
4.6.4. Datatype coercions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
A slightly different use case than CAST expressions are data type coercions, which are not rendered through to generated SQL. Sometimes, you may want to pretend that a numeric value is really treated as a string value, for instance when binding a numeric bind value:
Field<String> field1 = val(1).coerce(String.class); Field<Integer> field2 = val("1").coerce(Integer.class);
In the above example, field1
will be treated by jOOQ as a Field<String>
, binding the numeric literal 1
as a VARCHAR
value. The same applies to field2
, whose string literal "1"
will be bound as an INTEGER
value.
This technique is better than performing unsafe or rawtype casting in Java, if you cannot access the "right" field type from any given expression.
4.6.5. Arithmetic expressions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Numeric arithmetic expressions
Your database can do the math for you. Arithmetic operations are implemented just like numeric functions, with similar limitations as far as type restrictions are concerned. You can use any of these operators:
+ - * / %
In order to express a SQL query like this one:
SELECT ((1 + 2) * (5 - 3) / 2) % 10 FROM DUAL
You can write something like this in jOOQ:
create.select(val(1).add(2).mul(val(5).sub(3)).div(2).mod(10)).fetch();
Operator precedence
jOOQ does not know any operator precedence (see also boolean operator precedence). All operations are evaluated from left to right, as with any object-oriented API. The two following expressions are the same:
val(1).add(2) .mul(val(5).sub(3)) .div(2) .mod(10); (((val(1).add(2)).mul(val(5).sub(3))).div(2)).mod(10);
Datetime arithmetic expressions
jOOQ also supports the Oracle-style syntax for adding days to a Field<? extends java.util.Date>
SELECT SYSDATE + 3 FROM DUAL;
create.select(currentTimestamp().add(3)).fetch();
For more advanced datetime arithmetic, use the DSL's timestampDiff() and dateDiff() functions, as well as jOOQ's built-in SQL standard INTERVAL
data type support:
-
INTERVAL YEAR TO MONTH
: org.jooq.types.YearToMonth -
INTERVAL DAY TO SECOND
: org.jooq.types.DayToSecond
4.6.6. String concatenation
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SQL standard defines the concatenation operator to be an infix operator, similar to the ones we've seen in the chapter about arithmetic expressions. This operator looks like this: ||
. Some other dialects do not support this operator, but expect a concat()
function, instead. jOOQ renders the right operator / function, depending on your SQL dialect:
SELECT 'A' || 'B' || 'C' FROM DUAL -- Or in MySQL: SELECT concat('A', 'B', 'C') FROM DUAL
// For all RDBMS, including MySQL: create.select(concat("A", "B", "C")).fetch();
4.6.7. Case sensitivity with strings
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Most databases allow for specifying a COLLATION
which allows for re-defining the ordering of string values. By default, ASCII, ISO, or Unicode encodings are applied to character data, and ordering is applied according to the respective encoding.
Sometimes, however, certain queries like to ignore parts of the encoding by treating upper-case and lower-case characters alike, such that ABC = abc
, or such that ABC, jkl, XyZ
are an ordered list of strings (case-insensitively).
For these ad-hoc ordering use-cases, most people resort to using LOWER()
or UPPER()
as follows:
-- Case-insensitive filtering: SELECT * FROM BOOK WHERE upper(TITLE) = 'ANIMAL FARM' -- Case-insensitive ordering: SELECT * FROM AUTHOR ORDER BY upper(FIRST_NAME), upper(LAST_NAME)
// Case-insensitive filtering: create.selectFrom(BOOK) .where(upper(BOOK.TITLE).eq("ANIMAL FARM")).fetch(); // Case-insensitive ordering: create.selectFrom(AUTHOR) .orderBy(upper(AUTHOR.FIRST_NAME), upper(AUTHOR.LAST_NAME)) .fetch();
4.6.8. General functions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
There are a variety of general functions supported by jOOQ. As discussed in the chapter about SQL dialects functions are mostly emulated in your database, in case they are not natively supported.
4.6.8.1. COALESCE
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The COALESCE()
function produces the first non-NULL
value from the variadic list of arguments.
SELECT coalesce(null, null, 1);
create.select(coalesce(null, null, 1)).fetch();
The result being
+----------+ | coalesce | +----------+ | 1 | +----------+
Dialect support
This example using jOOQ:
coalesce(null, null, 1)
Translates to the following dialect specific expressions:
-- ACCESS, ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, FIREBIRD, H2, HANA, HSQLDB, INGRES, MARIADB, -- MEMSQL, MYSQL, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, SQLSERVER, SYBASE, TERADATA, VERTICA coalesce(NULL, NULL, 1) -- INFORMIX CASE WHEN CASE WHEN NULL IS NOT NULL THEN NULL ELSE NULL END IS NOT NULL THEN CASE WHEN NULL IS NOT NULL THEN NULL ELSE NULL END ELSE 1 END
(These are currently generated with jOOQ 3.15, see #10141)
4.6.8.2. DECODE
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Some SQL dialects, including Db2, H2, Oracle know a more succinct, but maybe less readable DECODE()
function with a variable number of arguments. This function works like a NULL
safe CASE expression. jOOQ supports the DECODE()
function and emulates it using CASE
expressions in all dialects that do not have native support:
SELECT -- Oracle: DECODE(FIRST_NAME, 'Paulo', 'brazilian', 'George', 'english', 'unknown'), -- Other SQL dialects CASE WHEN FIRST_NAME IS NOT DISTINCT FROM 'Paulo' THEN 'brazilian' WHEN FIRST_NAME IS NOT DISTINCT FROM 'George' THEN 'english' ELSE 'unknown' END FROM AUTHOR
// Use the Oracle-style DECODE() function with jOOQ. // Note, that you will not be able to rely on type-safety decode( AUTHOR.FIRST_NAME, "Paulo", "brazilian", "George", "english", "unknown" );
See the DISTINCT predicate for details about the NULL
safe semantics.
4.6.8.3. NULLIF
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The NULLIF()
function produces a NULL
value if both its arguments are equal, otherwise it produces the first argument.
SELECT nullif(1, 1), nullif(1, 2);
create.select(nullif(1, 1), nullif(1, 2)).fetch();
The result being
+--------+--------+ | nullif | nullif | +--------+--------+ | | 1 | +--------+--------+
Dialect support
This example using jOOQ:
nullif(1, 2)
Translates to the following dialect specific expressions:
-- ACCESS iif(1 = 2, NULL, 1) -- ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, FIREBIRD, H2, HANA, HSQLDB, INFORMIX, INGRES, MARIADB, -- MEMSQL, MYSQL, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, SQLSERVER, SYBASE, TERADATA, VERTICA nullif(1, 2)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.8.4. NVL
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The NVL()
function (or also the ISNULL()
) produces the first argument if it is NOT NULL
, otherwise the second argument. It is a special case of the COALESCE function, which takes any number of arguments.
SELECT nvl(null, 1);
create.select(nvl(null, 1)).fetch();
The result being
+-----+ | nvl | +-----+ | 1 | +-----+
Dialect support
This example using jOOQ:
nvl(null, 1)
Translates to the following dialect specific expressions:
-- ACCESS iif(NULL IS NULL, 1, NULL) -- ASE, CUBRID, FIREBIRD, HANA, INFORMIX, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLSERVER, SYBASE, TERADATA, VERTICA CASE WHEN NULL IS NOT NULL THEN NULL ELSE 1 END -- AURORA_MYSQL, MARIADB, MEMSQL, MYSQL, SQLITE ifnull(NULL, 1) -- AURORA_POSTGRES, COCKROACHDB, DERBY, POSTGRES COALESCE(NULL, 1) -- DB2, H2, HSQLDB, INGRES, ORACLE nvl(NULL, 1)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.8.5. NVL2
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The NVL2()
function checks if the first argument is NOT NULL
to produce the second argument, or the third argument otherwise. It works in a similar way as the CASE expression
SELECT nvl2(1, 2, 3), nvl2(null, 2, 3);
create.select( nvl2(val(1) , 2, 3), nvl2(val(null), 2, 3)).fetch();
The result being
+------+------+ | nvl2 | nvl2 | +------+------+ | 2 | 3 | +------+------+
Dialect support
This example using jOOQ:
nvl2(val(1), 2, 3)
Translates to the following dialect specific expressions:
-- ACCESS, SQLSERVER iif(1 IS NOT NULL, 2, 3) -- ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, FIREBIRD, HANA, INFORMIX, MARIADB, MEMSQL, MYSQL, -- POSTGRES, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, SYBASE, TERADATA, VERTICA CASE WHEN 1 IS NOT NULL THEN 2 ELSE 3 END -- H2, HSQLDB, INGRES, ORACLE nvl2(1, 2, 3)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9. Numeric functions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In addition to the arithmetic expressions discussed previously, jOOQ also supports a variety of numeric functions. As discussed in the chapter about SQL dialects numeric functions (as any function type) are mostly emulated in your database, in case they are not natively supported.
4.6.9.1. ABS
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The ABS()
function produces the absolute value of a numeric value.
SELECT abs(-5), abs(0), abs(3);
create.select(abs(-5), abs(0), abs(3)).fetch();
The result being
+-----+-----+-----+ | abs | abs | abs | +-----+-----+-----+ | 5 | 0 | 3 | +-----+-----+-----+
Dialect support
This example using jOOQ:
abs(3)
Translates to the following dialect specific expressions:
-- All dialects abs(3)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.2. ACOS
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The ACOS()
function calculates the arc cosine of a numeric value.
SELECT acos(0);
create.select(acos(0)).fetch();
The result being
+------------+ | acos | +------------+ | 1.57079633 | +------------+
Dialect support
This example using jOOQ:
acos(0)
Translates to the following dialect specific expressions:
-- ACCESS (atn((-(0) / sqr(((-(0) * 0) + 1)))) + (2 * atn(1))) -- ASE, CUBRID, DB2, DERBY, FIREBIRD, H2, HSQLDB, INFORMIX, INGRES, MARIADB, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE acos(0) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.3. ACOS
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The ASIN()
function calculates the arc sine of a numeric value.
SELECT asin(1);
create.select(asin(1)).fetch();
The result being
+------------+ | asin | +------------+ | 1.57079633 | +------------+
Dialect support
This example using jOOQ:
asin(1)
Translates to the following dialect specific expressions:
-- ACCESS atn((1 / sqr(((-(1) * 1) + 1)))) -- ASE, CUBRID, DB2, DERBY, FIREBIRD, H2, HSQLDB, INFORMIX, INGRES, MARIADB, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE asin(1) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.4. ATAN
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The ATAN()
function calculates the arc tangent of a numeric value.
SELECT atan(1);
create.select(atan(1)).fetch();
The result being
+-------------+ | atan | +-------------+ | 0.785398163 | +-------------+
Dialect support
This example using jOOQ:
atan(1)
Translates to the following dialect specific expressions:
-- ACCESS atn(1) -- ASE, CUBRID, DB2, DERBY, FIREBIRD, H2, HSQLDB, INFORMIX, INGRES, MARIADB, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE atan(1) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.5. ATAN2
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The ATAN2()
function calculates the ATAN2 of a numeric value.
SELECT atan2(1, 1);
create.select(atan2(1, 1)).fetch();
The result being
+---------------+ | atan2 | +---------------+ | 0.78539816339 | +---------------+
Dialect support
This example using jOOQ:
atan2(1, 1)
Translates to the following dialect specific expressions:
-- ASE, SQLSERVER atn2(1, 1) -- CUBRID, DB2, DERBY, FIREBIRD, H2, HSQLDB, INFORMIX, INGRES, MARIADB, MYSQL, ORACLE, POSTGRES, SYBASE atan2(1, 1) -- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, -- VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.6. CEIL
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The CEIL()
function rounds a numeric value to its nearest higher integer.
SELECT ceil(1.7), ceil(-1.7);
create.select( ceil(1.7), ceil(-1.7)).fetch();
The result being
+-------+-------+ | floor | floor | +-------+-------+ | 2 | -1 | +-------+-------+
Dialect support
This example using jOOQ:
ceil(1.7)
Translates to the following dialect specific expressions:
-- ACCESS (CLNG(1.7) - (1.7 - clng(1.7) > 0)) -- ASE, H2, SQLDATAWAREHOUSE, SQLSERVER ceiling(1.7) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, FIREBIRD, HANA, HSQLDB, INFORMIX, INGRES, MARIADB, MEMSQL, -- MYSQL, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, SYBASE, TERADATA, VERTICA ceil(1.7) -- SQLITE (CAST(1.7 AS int8) + (1.7 > CAST(1.7 AS int8)))
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.7. COS
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The COS()
function calculates the cosine of a numeric value.
SELECT cos(3.14159265359);
create.select(cos(3.14159265359)).fetch();
The result being
+-----+ | cos | +-----+ | -1 | +-----+
Dialect support
This example using jOOQ:
cos(3.14159265359)
Translates to the following dialect specific expressions:
-- ACCESS, ASE, CUBRID, DB2, DERBY, FIREBIRD, H2, HSQLDB, INFORMIX, INGRES, MARIADB, MYSQL, ORACLE, POSTGRES, SQLSERVER, -- SYBASE cos(3.14159265359) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.8. COSH
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The COSH()
function calculates the hyperbolic cosine of a numeric value.
SELECT cosh(1);
create.select(cosh(1)).fetch();
The result being
+---------------+ | cosh | +---------------+ | 1.54308063482 | +---------------+
Dialect support
This example using jOOQ:
cosh(1)
Translates to the following dialect specific expressions:
-- ACCESS, ASE, CUBRID, HSQLDB, INGRES, MARIADB, MYSQL, POSTGRES, SQLSERVER, SYBASE ((exp((1 * 2)) + 1) / (exp(1) * 2)) -- DB2, DERBY, FIREBIRD, H2, INFORMIX, ORACLE cosh(1) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.9. COT
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The COT()
function calculates the cotangent of a numeric value.
SELECT cot(1.5707963268);
create.select(cot(1.5707963268)).fetch();
The result being
+-----+ | cot | +-----+ | 0 | +-----+
Dialect support
This example using jOOQ:
cot(1.5707963268)
Translates to the following dialect specific expressions:
-- ACCESS, INFORMIX, INGRES, ORACLE (cos(1.5707963268) / sin(1.5707963268)) -- ASE, CUBRID, DB2, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLSERVER, SYBASE cot(1.5707963268) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.10. COTH
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The COTH()
function calculates the hyperbolic cotangent of a numeric value.
SELECT coth(1);
create.select(coth(1)).fetch();
The result being
+--------------+ | coth | +--------------+ | 1.3130352855 | +--------------+
Dialect support
This example using jOOQ:
coth(1)
Translates to the following dialect specific expressions:
-- ACCESS, ASE, CUBRID, DB2, DERBY, FIREBIRD, H2, HSQLDB, INFORMIX, INGRES, MARIADB, MYSQL, ORACLE, POSTGRES, SQLSERVER, -- SYBASE ((exp((1 * 2)) + 1) / (exp((1 * 2)) - 1)) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.11. DEG
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The DEG()
function calculates the degrees from a radian value (see also RAD).
SELECT deg(3.14159265359);
create.select(deg(3.14159265359)).fetch();
The result being
+-----+ | deg | +-----+ | 180 | +-----+
Dialect support
This example using jOOQ:
deg(3.14159265359)
Translates to the following dialect specific expressions:
-- ACCESS ((3.14159265359 * 180) / 3.141592653589793) -- ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, H2, HSQLDB, INFORMIX, MARIADB, MEMSQL, MYSQL, -- POSTGRES, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLSERVER, SYBASE, TERADATA, VERTICA degrees(3.14159265359) -- FIREBIRD ((CAST(3.14159265359 AS numeric) * 180) / pi()) -- HANA ((CAST(3.14159265359 AS numeric) * 180) / (asin(1) * 2)) -- INGRES ((CAST(3.14159265359 AS decimal(38, 19)) * 180) / pi()) -- ORACLE ((CAST(3.14159265359 AS number) * 180) / (asin(1) * 2)) -- SQLITE ((CAST(3.14159265359 AS numeric) * 180) / 3.141592653589793)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.12. EXP
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The EXP()
function calculates e^x
SELECT exp(1);
create.select(exp(1)).fetch();
The result being
+---------------+ | exp | +---------------+ | 2.71828182846 | +---------------+
Dialect support
This example using jOOQ:
exp(1)
Translates to the following dialect specific expressions:
-- ACCESS, ASE, CUBRID, DB2, DERBY, FIREBIRD, H2, HSQLDB, INFORMIX, INGRES, MARIADB, MYSQL, ORACLE, POSTGRES, SQLSERVER, -- SYBASE exp(1) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.13. FLOOR
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The FLOOR()
function rounds a numeric value to its nearest lower integer.
SELECT floor(1.7), floor(-1.7);
create.select( floor(1.7), floor(-1.7)).fetch();
The result being
+-------+-------+ | floor | floor | +-------+-------+ | 1 | -2 | +-------+-------+
Dialect support
This example using jOOQ:
floor(1.7)
Translates to the following dialect specific expressions:
-- ACCESS (cdec(1.7) - (1.7 < cdec(1.7))) -- ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, FIREBIRD, H2, HANA, HSQLDB, INFORMIX, INGRES, MARIADB, -- MEMSQL, MYSQL, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLSERVER, SYBASE, TERADATA, VERTICA floor(1.7) -- SQLITE (CAST(1.7 AS int8) - (1.7 < CAST(1.7 AS int8)))
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.14. GREATEST
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The GREATEST()
function produces the greatest value among all the arguments.
SELECT greatest(2, 3);
create.select(greatest(2, 3)).fetch();
The result being
+----------+ | greatest | +----------+ | 3 | +----------+
Dialect support
This example using jOOQ:
greatest(2, 3)
Translates to the following dialect specific expressions:
-- ACCESS SWITCH(2 > 3, 2, TRUE, 3) -- ASE, DERBY, INFORMIX, SQLDATAWAREHOUSE, SQLSERVER, SYBASE CASE WHEN 2 > 3 THEN 2 ELSE 3 END -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, H2, HANA, HSQLDB, INGRES, MARIADB, MEMSQL, MYSQL, ORACLE, -- POSTGRES, REDSHIFT, SNOWFLAKE, TERADATA, VERTICA greatest(2, 3) -- FIREBIRD maxvalue(2, 3) -- SQLITE max(2, 3)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.15. LEAST
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The LEAST()
function produces the least value among all the arguments.
SELECT least(2, 3);
create.select(least(2, 3)).fetch();
The result being
+-------+ | least | +-------+ | 2 | +-------+
Dialect support
This example using jOOQ:
least(2, 3)
Translates to the following dialect specific expressions:
-- ACCESS SWITCH(2 < 3, 2, TRUE, 3) -- ASE, DERBY, INFORMIX, SQLDATAWAREHOUSE, SQLSERVER, SYBASE CASE WHEN 2 < 3 THEN 2 ELSE 3 END -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, H2, HANA, HSQLDB, INGRES, MARIADB, MEMSQL, MYSQL, ORACLE, -- POSTGRES, REDSHIFT, SNOWFLAKE, TERADATA, VERTICA least(2, 3) -- FIREBIRD minvalue(2, 3) -- SQLITE min(2, 3)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.16. LN
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The LN()
function calculates the natural logarithm of a numeric value.
SELECT ln(1);
create.select(ln(1)).fetch();
The result being
+----+ | ln | +----+ | 0 | +----+
Dialect support
This example using jOOQ:
ln(1)
Translates to the following dialect specific expressions:
-- ACCESS, ASE, SQLSERVER log(1) -- CUBRID, DB2, DERBY, FIREBIRD, H2, HSQLDB, INGRES, MARIADB, MYSQL, ORACLE, POSTGRES, SYBASE ln(1) -- INFORMIX logn(1) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.17. LOG
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The LOG()
function calculates the logarithm of a numeric value, given a base.
SELECT log(8, 2);
create.select(log(8, 2)).fetch();
The result being
+-----+ | log | +-----+ | 3 | +-----+
Dialect support
This example using jOOQ:
log(8, 2)
Translates to the following dialect specific expressions:
-- ACCESS, ASE (log(8) / log(2)) -- CUBRID, FIREBIRD, H2, MARIADB, MYSQL, ORACLE, POSTGRES log(2, 8) -- DB2, DERBY, HSQLDB, INGRES, SYBASE (ln(8) / ln(2)) -- INFORMIX (logn(8) / logn(2)) -- SQLSERVER log(8, 2) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.18. NEG
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The NEG()
function produces the negation of its argument.
SELECT neg(2);
create.select(neg(2)).fetch();
The result being
+-----+ | neg | +-----+ | -2 | +-----+
4.6.9.19. POWER
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The POWER()
function calculates the power of two numbers.
SELECT power(2, 3);
create.select(power(2, 3)).fetch();
The result being
+-------+ | power | +-------+ | 8 | +-------+
Dialect support
This example using jOOQ:
power(2, 3)
Translates to the following dialect specific expressions:
-- ACCESS (2 ^ 3) -- ASE, CUBRID, DB2, FIREBIRD, H2, HSQLDB, INFORMIX, INGRES, MARIADB, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE power(2, 3) -- DERBY exp((ln(2) * 3)) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.20. RAD
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The RAD()
function calculates the radian value from degrees (see also DEG).
SELECT rad(180);
create.select(rad(180)).fetch();
The result being
+---------------+ | rad | +---------------+ | 3.14159265359 | +---------------+
Dialect support
This example using jOOQ:
rad(180)
Translates to the following dialect specific expressions:
-- ACCESS ((cdec(180) * 3.141592653589793) / 180) -- ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, H2, HSQLDB, INFORMIX, MARIADB, MEMSQL, MYSQL, -- POSTGRES, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLSERVER, SYBASE, TERADATA, VERTICA radians(180) -- FIREBIRD ((CAST(180 AS numeric) * pi()) / 180) -- HANA ((CAST(180 AS numeric) * (asin(1) * 2)) / 180) -- INGRES ((CAST(180 AS decimal(38, 19)) * pi()) / 180) -- ORACLE ((CAST(180 AS number) * (asin(1) * 2)) / 180) -- SQLITE ((CAST(180 AS numeric) * 3.141592653589793) / 180)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.21. RAND
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The RAND()
function produces a random number.
SELECT rand();
create.select(rand()).fetch();
The result being
+------+ | rand | +------+ | 4 | chosen by fair dice roll +------+
Dialect support
This example using jOOQ:
rand()
Translates to the following dialect specific expressions:
-- ACCESS rnd -- ASE, CUBRID, DB2, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, SQLSERVER, SYBASE rand() -- DERBY, INGRES, POSTGRES, SQLITE random() -- ORACLE DBMS_RANDOM.RANDOM -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, INFORMIX, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, TERADATA, -- VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.22. ROUND
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The ROUND()
function rounds a numeric value to its nearest integer, or optionally, to the nearest decimal precision.
SELECT round(1.7), round(-1.7);
create.select( round(1.7), round(-1.7)).fetch();
The result being
+-------+-------+ | round | round | +-------+-------+ | 2 | -2 | +-------+-------+
Dialect support
This example using jOOQ:
round(1.7)
Translates to the following dialect specific expressions:
-- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, CUBRID, DB2, FIREBIRD, H2, HANA, HSQLDB, INFORMIX, MARIADB, MEMSQL, MYSQL, ORACLE, -- POSTGRES, REDSHIFT, SNOWFLAKE, SQLITE, TERADATA, VERTICA round(1.7) -- ASE, INGRES, SQLDATAWAREHOUSE, SQLSERVER, SYBASE round(1.7, 0) -- COCKROACHDB round(CAST(1.7 AS numeric)) -- DERBY CASE WHEN (1.7 - floor(1.7)) < 0.5 THEN floor(1.7) ELSE ceil(1.7) END
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.23. SIGN
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SIGN()
function produces the sign of a numeric value, being any value of -1, 0, 1
SELECT sign(-5), sign(0), sign(3);
create.select(sign(-5), sign(0), sign(3)).fetch();
The result being
+------+------+------+ | sign | sign | sign | +------+------+------+ | -1 | 0 | 1 | +------+------+------+
Dialect support
This example using jOOQ:
sign(3)
Translates to the following dialect specific expressions:
-- ACCESS sgn(3) -- ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, FIREBIRD, H2, HANA, HSQLDB, INFORMIX, INGRES, MARIADB, -- MEMSQL, MYSQL, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLSERVER, SYBASE, TERADATA, VERTICA sign(3) -- SQLITE CASE WHEN 3 > 0 THEN 1 WHEN 3 < 0 THEN -1 WHEN 3 = 0 THEN 0 END
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.24. SIN
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SIN()
function calculates the sine of a numeric value.
SELECT sin(3.14159265359);
create.select(sin(3.14159265359)).fetch();
The result being
+-----+ | sin | +-----+ | 0 | +-----+
Dialect support
This example using jOOQ:
sin(3.14159265359)
Translates to the following dialect specific expressions:
-- ACCESS, ASE, CUBRID, DB2, DERBY, FIREBIRD, H2, HSQLDB, INFORMIX, INGRES, MARIADB, MYSQL, ORACLE, POSTGRES, SQLSERVER, -- SYBASE sin(3.14159265359) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.25. SINH
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SINH()
function calculates the hyperbolic sine of a numeric value.
SELECT sinh(1);
create.select(sinh(1)).fetch();
The result being
+---------------+ | sinh | +---------------+ | 1.17520119364 | +---------------+
Dialect support
This example using jOOQ:
sinh(1)
Translates to the following dialect specific expressions:
-- ACCESS, ASE, CUBRID, HSQLDB, INGRES, MARIADB, MYSQL, POSTGRES, SQLSERVER, SYBASE ((exp((1 * 2)) - 1) / (exp(1) * 2)) -- DB2, DERBY, FIREBIRD, H2, INFORMIX, ORACLE sinh(1) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.26. SQRT
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SQRT()
function calculates the square root of a numeric value.
SELECT sqrt(4);
create.select(sqrt(4)).fetch();
The result being
+------+ | sqrt | +------+ | 4 | +------+
Dialect support
This example using jOOQ:
sqrt(4)
Translates to the following dialect specific expressions:
-- ACCESS sqr(4) -- ASE, CUBRID, DB2, DERBY, FIREBIRD, H2, HSQLDB, INFORMIX, INGRES, MARIADB, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE sqrt(4) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.27. TAN
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The TAN()
function calculates the tangent of a numeric value.
SELECT tan(3.14159265359);
create.select(tan(3.14159265359)).fetch();
The result being
+-----+ | tan | +-----+ | 0 | +-----+
Dialect support
This example using jOOQ:
tan(3.14159265359)
Translates to the following dialect specific expressions:
-- ACCESS, ASE, CUBRID, DB2, DERBY, FIREBIRD, H2, HSQLDB, INFORMIX, INGRES, MARIADB, MYSQL, ORACLE, POSTGRES, SQLSERVER, -- SYBASE tan(3.14159265359) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.28. TANH
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The TANH()
function calculates the hyperbolic tangent of a numeric value.
SELECT tanh(1);
create.select(tanh(1)).fetch();
The result being
+---------------+ | tanh | +---------------+ | 0.76159415595 | +---------------+
Dialect support
This example using jOOQ:
tanh(1)
Translates to the following dialect specific expressions:
-- ACCESS, ASE, CUBRID, HSQLDB, INGRES, MARIADB, MYSQL, POSTGRES, SQLSERVER, SYBASE ((exp((1 * 2)) - 1) / (exp((1 * 2)) + 1)) -- DB2, DERBY, FIREBIRD, H2, INFORMIX, ORACLE tanh(1) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.9.29. TRUNC
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The TRUNC()
function rounds a numeric value to its nearest integer (or optionally, to a specific decimal precision) that is closer to zero.
SELECT trunc(1.7), trunc(-1.7);
create.select( trunc(1.7), trunc(-1.7)).fetch();
The result being
+-------+-------+ | trunc | trunc | +-------+-------+ | 1 | -1 | +-------+-------+
Dialect support
This example using jOOQ:
trunc(1.7)
Translates to the following dialect specific expressions:
-- CUBRID, DB2, HSQLDB, INFORMIX, ORACLE trunc(1.7, 0) -- POSTGRES CAST(trunc(CAST(1.7 AS numeric), 0) AS double precision) -- ACCESS, ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, FIREBIRD, H2, HANA, INGRES, MARIADB, MEMSQL, MYSQL, -- REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, SQLSERVER, SYBASE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.10. Bitwise functions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Most databases only support a few bitwise operations, while others ship with the full set of operators. jOOQ's API includes most bitwise operations as listed below. In order to avoid ambiguities with conditional operators, most bitwise functions are prefixed with "bit"
4.6.10.1. BIT_COUNT
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The BIT_COUNT()
function counts the number of bits in a value.
SELECT bit_count(5);
create.select(bitCount(5)).fetch();
The result being
+-----------+ | bit_count | +-----------+ | 2 | +-----------+
Dialect support
This example using jOOQ:
bitCount((byte) 5)
Translates to the following dialect specific expressions:
-- CUBRID, POSTGRES, SQLITE CAST(((5 & 1) + ((5 & 2) >> 1) + ((5 & 4) >> 2) + ((5 & 8) >> 3) + ((5 & 16) >> 4) + ((5 & 32) >> 5) + ((5 & 64) >> 6) + ((5 & -128) >> 7)) AS int) -- FIREBIRD CAST((bin_and(5, 1) + bin_shr(bin_and(5, 2), 1) + bin_shr(bin_and(5, 4), 2) + bin_shr(bin_and(5, 8), 3) + bin_shr(bin_and(5, 16), 4) + bin_shr(bin_and(5, 32), 5) + bin_shr(bin_and(5, 64), 6) + bin_shr(bin_and(5, -128), 7)) AS integer) -- H2, HSQLDB CAST((bitand(5, 1) + (bitand(5, 2) / 2) + (bitand(5, 4) / 4) + (bitand(5, 8) / 8) + (bitand(5, 16) / 16) + (bitand(5, 32) / 32) + (bitand(5, 64) / 64) + (bitand(5, -128) / -128)) AS int) -- INFORMIX CAST((bitand(5, 1) + (bitand(5, 2) / 2) + (bitand(5, 4) / 4) + (bitand(5, 8) / 8) + (bitand(5, 16) / 16) + (bitand(5, 32) / 32) + (bitand(5, 64) / 64) + (bitand(5, -128) / -128)) AS integer) -- MARIADB, MYSQL bit_count(5) -- ORACLE CAST((bitand(5, 1) + (bitand(5, 2) / 2) + (bitand(5, 4) / 4) + (bitand(5, 8) / 8) + (bitand(5, 16) / 16) + (bitand(5, 32) / 32) + (bitand(5, 64) / 64) + (bitand(5, -128) / -128)) AS number(10)) -- SYBASE CAST(((5 & 1) + ((5 & 2) / 2) + ((5 & 4) / 4) + ((5 & 8) / 8) + ((5 & 16) / 16) + ((5 & 32) / 32) + ((5 & 64) / 64) + ((5 & -128) / -128)) AS int) -- ACCESS, ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DB2, DERBY, HANA, INGRES, MEMSQL, REDSHIFT, SNOWFLAKE, -- SQLDATAWAREHOUSE, SQLSERVER, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.10.2. BIT_AND
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The BIT_AND()
function produces the bitwise AND
operation.
SELECT bit_and(5, 4);
create.select(bitAnd(5, 4)).fetch();
The result being
+---------+ | bit_and | +---------+ | 4 | +---------+
Dialect support
This example using jOOQ:
bitAnd(5, 4)
Translates to the following dialect specific expressions:
-- ASE, CUBRID, MARIADB, MYSQL, POSTGRES, SQLITE, SQLSERVER, SYBASE (5 & 4) -- DB2, H2, HSQLDB, INFORMIX, ORACLE bitand(5, 4) -- FIREBIRD bin_and(5, 4) -- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, INGRES, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, -- TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.10.3. BIT_NAND
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The BIT_NAND()
function produces the bitwise NAND
operation.
SELECT bit_nand(5, 4);
create.select(bitNand(5, 4)).fetch();
The result being
+----------+ | bit_nand | +----------+ | -5 | +----------+
Dialect support
This example using jOOQ:
bitNand(5, 4)
Translates to the following dialect specific expressions:
-- ASE, CUBRID, MARIADB, MYSQL, POSTGRES, SQLITE, SQLSERVER, SYBASE ~((5 & 4)) -- DB2, H2, INFORMIX bitnot(bitand(5, 4)) -- FIREBIRD bin_not(bin_and(5, 4)) -- HSQLDB, ORACLE (0 - bitand(5, 4) - 1) -- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, INGRES, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, -- TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.10.4. BIT_NOR
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The BIT_NOR()
function produces the bitwise NOR
operation.
SELECT bit_nor(5, 2);
create.select(bitNor(5, 2)).fetch();
The result being
+---------+ | bit_nor | +---------+ | -8 | +---------+
Dialect support
This example using jOOQ:
bitNor(5, 2)
Translates to the following dialect specific expressions:
-- ASE, CUBRID, MARIADB, MYSQL, POSTGRES, SQLITE, SQLSERVER, SYBASE ~((5 | 2)) -- DB2, H2, INFORMIX bitnot(bitor(5, 2)) -- FIREBIRD bin_not(bin_or(5, 2)) -- HSQLDB (0 - bitor(5, 2) - 1) -- ORACLE (0 - ((5 + 2) - bitand(5, 2)) - 1) -- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, INGRES, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, -- TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.10.5. BIT_NOT
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The BIT_NOT()
function inverts the bits in a number, producing the 2's complement of a signed number.
SELECT bit_not(5);
create.select(bitNot(5)).fetch();
The result being
+---------+ | bit_not | +---------+ | -6 | +---------+
Dialect support
This example using jOOQ:
bitNot(5)
Translates to the following dialect specific expressions:
-- ASE, CUBRID, MARIADB, MYSQL, POSTGRES, SQLITE, SQLSERVER, SYBASE ~(5) -- DB2, H2, INFORMIX bitnot(5) -- FIREBIRD bin_not(5) -- HSQLDB, ORACLE (0 - 5 - 1) -- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, INGRES, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, -- TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.10.6. BIT_OR
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The BIT_OR()
function produces the bitwise OR
operation.
SELECT bit_or(5, 2);
create.select(bitOr(5, 2)).fetch();
The result being
+--------+ | bit_or | +--------+ | 7 | +--------+
Dialect support
This example using jOOQ:
bitOr(5, 2)
Translates to the following dialect specific expressions:
-- ASE, CUBRID, MARIADB, MYSQL, POSTGRES, SQLITE, SQLSERVER, SYBASE (5 | 2) -- DB2, H2, HSQLDB, INFORMIX bitor(5, 2) -- FIREBIRD bin_or(5, 2) -- ORACLE ((5 + 2) - bitand(5, 2)) -- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, INGRES, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, -- TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.10.7. SHL
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SHL()
function produces the bitwise shift left operation.
SELECT shl(1, 4);
create.select(shl(1, 4)).fetch();
The result being
+-----+ | shl | +-----+ | 16 | +-----+
Dialect support
This example using jOOQ:
shl(1, 4)
Translates to the following dialect specific expressions:
-- ASE, HSQLDB, SQLSERVER, SYBASE (1 * CAST(power(2, 4) AS int)) -- CUBRID, MARIADB, MYSQL, POSTGRES, SQLITE (1 << 4) -- DB2, INFORMIX (1 * CAST(power(2, 4) AS integer)) -- FIREBIRD bin_shl(1, 4) -- H2 lshift(1, 4) -- ORACLE (1 * CAST(power(2, 4) AS number(10))) -- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, INGRES, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, -- TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.10.8. SHR
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SR()
function produces the bitwise shift right operation.
SELECT shr(16, 4);
create.select(shr(16, 4)).fetch();
The result being
+-----+ | shr | +-----+ | 1 | +-----+
Dialect support
This example using jOOQ:
shr(16, 4)
Translates to the following dialect specific expressions:
-- ASE, HSQLDB, SQLSERVER, SYBASE (16 / CAST(power(2, 4) AS int)) -- CUBRID, MARIADB, MYSQL, POSTGRES, SQLITE (16 >> 4) -- DB2, INFORMIX (16 / CAST(power(2, 4) AS integer)) -- FIREBIRD bin_shr(16, 4) -- H2 rshift(16, 4) -- ORACLE (16 / CAST(power(2, 4) AS number(10))) -- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, INGRES, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, -- TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.10.9. BIT_XNOR
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The BIT_XNOR()
function produces the bitwise XNOR
(exclusive NOR
) operation.
SELECT bit_xnor(5, 3);
create.select(bitXNor(5, 3)).fetch();
The result being
+----------+ | bit_xnor | +----------+ | -7 | +----------+
Dialect support
This example using jOOQ:
bitXNor(5, 3)
Translates to the following dialect specific expressions:
-- ASE, CUBRID, MARIADB, MYSQL, SQLSERVER, SYBASE ~((5 ^ 3)) -- DB2, H2, INFORMIX bitnot(bitxor(5, 3)) -- FIREBIRD bin_not(bin_xor(5, 3)) -- HSQLDB (0 - bitxor(5, 3) - 1) -- ORACLE (0 - bitand((0 - bitand(5, 3) - 1), ((5 + 3) - bitand(5, 3))) - 1) -- POSTGRES ~((5 # 3)) -- SQLITE ~((~((5 & 3)) & (5 | 3))) -- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, INGRES, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, -- TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.10.10. BIT_XOR
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The BIT_XOR()
function produces the bitwise XOR
(exclusive OR
) operation.
SELECT bit_xor(5, 3);
create.select(bitXor(5, 3)).fetch();
The result being
+---------+ | bit_xor | +---------+ | 6 | +---------+
Dialect support
This example using jOOQ:
bitXor(5, 3)
Translates to the following dialect specific expressions:
-- ASE, CUBRID, MARIADB, MYSQL, SQLSERVER, SYBASE (5 ^ 3) -- DB2, H2, HSQLDB, INFORMIX bitxor(5, 3) -- FIREBIRD bin_xor(5, 3) -- ORACLE bitand((0 - bitand(5, 3) - 1), ((5 + 3) - bitand(5, 3))) -- POSTGRES (5 # 3) -- SQLITE (~((5 & 3)) & (5 | 3)) -- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, INGRES, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, -- TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11. String functions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
String formatting can be done efficiently in the database before returning results to your Java application. As discussed in the chapter about SQL dialects string functions (as any function type) are mostly emulated in your database, in case they are not natively supported.
4.6.11.1. ASCII
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The ASCII()
function calculates the ASCII code of a single character.
SELECT ascii('A');
create.select(ascii("A")).fetch();
The result being
+-------+ | ascii | +-------+ | 65 | +-------+
Dialect support
This example using jOOQ:
ascii("A")
Translates to the following dialect specific expressions:
-- ACCESS asc('A') -- ASE, CUBRID, DB2, H2, HSQLDB, INFORMIX, MARIADB, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE ascii('A') -- FIREBIRD ascii_val('A') -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, INGRES, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, -- TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.2. CONCAT
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The CONCAT()
function concatenates several strings
SELECT concat('hello', ' ', 'world');
create.select(concat("hello", " ", "world")).fetch();
The result being
+-------------+ | concat | +-------------+ | hello world | +-------------+
Dialect support
This example using jOOQ:
concat("hello", " ", "world")
Translates to the following dialect specific expressions:
-- ACCESS ('hello' & ' ' & 'world') -- ASE, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, FIREBIRD, H2, HANA, HSQLDB, INFORMIX, INGRES, ORACLE, POSTGRES, -- REDSHIFT, SNOWFLAKE, SQLITE, SYBASE, TERADATA, VERTICA ('hello' || ' ' || 'world') -- AURORA_MYSQL, MARIADB, MEMSQL, MYSQL concat('hello', ' ', 'world') -- SQLDATAWAREHOUSE, SQLSERVER ('hello' + ' ' + 'world')
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.3. LEFT
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The LEFT()
function calculates the substring of a given string starting from the left end. See also SUBSTRING, RIGHT
SELECT left('hello world', 5);
create.select(left("hello world", 5)).fetch();
The result being
+-------+ | left | +-------+ | hello | +-------+
Dialect support
This example using jOOQ:
left("hello world", 5)
Translates to the following dialect specific expressions:
-- ACCESS, ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, FIREBIRD, H2, HANA, HSQLDB, INFORMIX, INGRES, -- MARIADB, MEMSQL, MYSQL, POSTGRES, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLSERVER, SYBASE, TERADATA, VERTICA left('hello world', 5) -- DERBY, ORACLE, SQLITE substr('hello world', 1, 5)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.4. LENGTH
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The LENGTH()
function calculates the length of a given string.
SELECT length('hello');
create.select(length("hello")).fetch();
The result being
+--------+ | length | +--------+ | 5 | +--------+
Dialect support
This example using jOOQ:
length("hello")
Translates to the following dialect specific expressions:
-- ACCESS, SQLDATAWAREHOUSE, SQLSERVER len('hello') -- ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, FIREBIRD, H2, HSQLDB, INFORMIX, MARIADB, MEMSQL, MYSQL, POSTGRES, -- REDSHIFT, VERTICA char_length('hello') -- DB2, DERBY, HANA, INGRES, ORACLE, SNOWFLAKE, SQLITE, SYBASE, TERADATA length('hello')
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.5. LOWER
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The LOWER()
function transforms a string into lower case.
SELECT lower('HELLO');
create.select(lower("HELLO")).fetch();
The result being
+-------+ | lower | +-------+ | hello | +-------+
Dialect support
This example using jOOQ:
lower("HELLO")
Translates to the following dialect specific expressions:
-- ACCESS lcase('HELLO') -- ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, FIREBIRD, H2, HANA, HSQLDB, INFORMIX, INGRES, MARIADB, -- MEMSQL, MYSQL, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, SQLSERVER, SYBASE, TERADATA, VERTICA lower('HELLO')
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.6. LPAD
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The LPAD()
pads a string at the left end. See also RPAD.
SELECT lpad('hello', 10, '.');
create.select(lpad(val("hello"), 10, '.')).fetch();
The result being
+------------+ | lpad | +------------+ | .....hello | +------------+
Dialect support
This example using jOOQ:
lpad(val("hello"), 10, '.')
Translates to the following dialect specific expressions:
-- ACCESS (replace(space(10 - len('hello')), ' ', '.') & 'hello') -- ASE (replicate('.', (10 - char_length('hello'))) || 'hello') -- CUBRID, DB2, FIREBIRD, H2, HSQLDB, INFORMIX, INGRES, MARIADB, MYSQL, ORACLE, POSTGRES lpad('hello', 10, '.') -- SQLITE substr("replace"(hex(zeroblob(10)), '00', '.'), 1, 10 - length('hello')) || 'hello' -- SQLSERVER (replicate('.', (10 - len('hello'))) + 'hello') -- SYBASE (repeat('.', (10 - length('hello'))) || 'hello') -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.7. LTRIM
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The LTRIM()
function trims a string from the left end, stripping it of whitespace. See also RTRIM and TRIM.
SELECT ltrim(' hello ');
create.select(ltrim(" hello ")).fetch();
The result being
+---------+ | ltrim | +---------+ | hello | +---------+
Dialect support
This example using jOOQ:
ltrim(" hello ")
Translates to the following dialect specific expressions:
-- ACCESS, ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, H2, HANA, HSQLDB, INFORMIX, INGRES, MARIADB, -- MEMSQL, MYSQL, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, SQLSERVER, SYBASE, TERADATA, VERTICA ltrim(' hello ') -- FIREBIRD trim(LEADING FROM ' hello ')
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.8. MD5
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The MD5()
function calculates the MD5 hash of a given string.
SELECT md5('hello');
create.select(md5("hello")).fetch();
The result being
+----------------------------------+ | md5 | +----------------------------------+ | 5d41402abc4b2a76b9719d911017c592 | +----------------------------------+
Dialect support
This example using jOOQ:
md5("hello")
Translates to the following dialect specific expressions:
-- MARIADB, MYSQL md5('hello') -- ORACLE lower(standard_hash('hello', 'MD5')) -- ACCESS, ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DB2, DERBY, FIREBIRD, H2, HANA, HSQLDB, INFORMIX, INGRES, MEMSQL, -- POSTGRES, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, SQLSERVER, SYBASE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.9. POSITION
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The POSITION()
function finds the first position of a string within another string, starting with 1.
SELECT position('hello', 'e'), position('hello', 'l', 4);
create.select( position("hello", "e"), position("hello", "e", 4)).fetch();
The result being
+----------+----------+ | position | position | +----------+----------+ | 2 | 4 | +----------+----------+
Dialect support
This example using jOOQ:
position("hello", "e")
Translates to the following dialect specific expressions:
-- ASE, SQLSERVER charindex('e', 'hello') -- CUBRID, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES position('e' IN 'hello') -- DB2, DERBY locate('e', 'hello') -- INGRES, SYBASE locate('hello', 'e') -- ORACLE instr('hello', 'e') -- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, INFORMIX, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, -- TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.10. REPEAT
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The REPEAT()
function repeats a string a number of times.
SELECT repeat('abc', 3);
create.select(repeat("abc", 3)).fetch();
The result being
+-----------+ | repeat | +-----------+ | abcabcabc | +-----------+
Dialect support
This example using jOOQ:
repeat("abc", 3)
Translates to the following dialect specific expressions:
-- ASE, SQLSERVER replicate('abc', 3) -- CUBRID, DB2, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SYBASE repeat('abc', 3) -- FIREBIRD rpad('abc', (char_length('abc') * 3), 'abc') -- INGRES, ORACLE rpad('abc', (length('abc') * 3), 'abc') -- SQLITE "replace"(hex(zeroblob(3)), '00', 'abc') -- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, INFORMIX, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, -- TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.11. REPLACE
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The REPLACE()
function replaces a substring inside of a string by another string.
SELECT replace('hello world', 'llo', 'y');
create.select(replace(val("hello world"), "llo", "y")).fetch();
The result being
+-----------+ | replace | +-----------+ | hey world | +-----------+
Dialect support
This example using jOOQ:
replace(val("hello world"), "llo", "y")
Translates to the following dialect specific expressions:
-- ACCESS, CUBRID, DB2, FIREBIRD, H2, HSQLDB, INFORMIX, INGRES, MARIADB, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE replace('hello world', 'llo', 'y') -- ASE str_replace('hello world', 'llo', 'y') -- SQLITE "replace"('hello world', 'llo', 'y') -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.12. REVERSE
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The REVERSE()
function reverses a string.
SELECT reverse('hello');
create.select(reverse("hello")).fetch();
The result being
+---------+ | reverse | +---------+ | olleh | +---------+
Dialect support
This example using jOOQ:
reverse("hello")
Translates to the following dialect specific expressions:
-- ASE, CUBRID, HSQLDB, MARIADB, MYSQL, ORACLE, POSTGRES, SQLSERVER reverse('hello') -- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DB2, DERBY, FIREBIRD, H2, HANA, INFORMIX, INGRES, MEMSQL, REDSHIFT, -- SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, SYBASE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.13. RIGHT
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The RIGHT()
function calculates the substring of a given string starting from the right end. See also SUBSTRING, LEFT
SELECT right('hello world', 5);
create.select(right("hello world", 5)).fetch();
The result being
+-----------+ | right | +-------+ | world | +-------+
Dialect support
This example using jOOQ:
right("hello world", 5)
Translates to the following dialect specific expressions:
-- ACCESS, ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, FIREBIRD, H2, HANA, HSQLDB, INFORMIX, INGRES, -- MARIADB, MEMSQL, MYSQL, POSTGRES, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLSERVER, SYBASE, TERADATA, VERTICA right('hello world', 5) -- DERBY substr('hello world', (length('hello world') + (1 - 5))) -- ORACLE, SQLITE substr('hello world', -(5))
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.14. RPAD
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The RPAD()
pads a string at the right end. See also LPAD.
SELECT rpad('hello', 10, '.');
create.select(rpad(val("hello"), 10, '.')).fetch();
The result being
+------------+ | rpad | +------------+ | hello..... | +------------+
Dialect support
This example using jOOQ:
rpad(val("hello"), 10, '.')
Translates to the following dialect specific expressions:
-- ACCESS ('hello' & replace(space(10 - len('hello')), ' ', '.')) -- ASE ('hello' || replicate('.', (10 - char_length('hello')))) -- CUBRID, DB2, FIREBIRD, H2, HSQLDB, INFORMIX, INGRES, MARIADB, MYSQL, ORACLE, POSTGRES rpad('hello', 10, '.') -- SQLITE 'hello' || substr("replace"(hex(zeroblob(10)), '00', '.'), 1, 10 - length('hello')) -- SQLSERVER ('hello' + replicate('.', (10 - len('hello')))) -- SYBASE ('hello' || repeat('.', (10 - length('hello')))) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.15. RTRIM
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The RTRIM()
function trims a string from the right end, stripping it of whitespace. See also LTRIM and TRIM.
SELECT rtrim(' hello ');
create.select(rtrim(" hello ")).fetch();
The result being
+---------+ | rtrim | +---------+ | hello | +---------+
Dialect support
This example using jOOQ:
rtrim(" hello ")
Translates to the following dialect specific expressions:
-- ACCESS, ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, H2, HANA, HSQLDB, INFORMIX, INGRES, MARIADB, -- MEMSQL, MYSQL, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, SQLSERVER, SYBASE, TERADATA, VERTICA rtrim(' hello ') -- FIREBIRD trim(TRAILING FROM ' hello ')
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.16. SPACE
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SPACE()
function repeats a space character a number of times. This is convenience for REPEAT, as available natively in SQL Server, for example.
SELECT 'a' || space(3) || 'b';
create.select(val("a").concat(space(3)).concat(val("b")).fetch();
The result being
+-------+ | space | +-------+ | a b | +-------+
Dialect support
This example using jOOQ:
space(3)
Translates to the following dialect specific expressions:
-- ASE, CUBRID, DB2, H2, MARIADB, MYSQL, SQLSERVER, SYBASE space(3) -- FIREBIRD, INFORMIX, INGRES, ORACLE rpad(' ', 3, ' ') -- HSQLDB, POSTGRES repeat(' ', 3) -- SQLITE ' ' || substr("replace"(hex(zeroblob(3)), '00', ' '), 1, 3 - length(' ')) -- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, TERADATA, -- VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.17. SUBSTRING
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SUBSTRING()
function calculates the substring of a string given a starting position and optionally, a length.. See also LEFT, RIGHT
SELECT substring('hello world', 7), substring('hello world', 7, 1);
create.select( substring("hello world", 7), substring("hello world", 7, 1)).fetch();
The result being
+-----------+-----------+ | substring | substring | +-----------+-----------+ | world | w | +-----------+-----------+
Dialect support
This example using jOOQ:
substring(val("hello world"), 7)
Translates to the following dialect specific expressions:
-- ACCESS mid('hello world', 7) -- ASE, SQLDATAWAREHOUSE, SQLSERVER substring('hello world', 7, 2147483647) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, H2, HANA, HSQLDB, MARIADB, MEMSQL, MYSQL, POSTGRES, REDSHIFT, -- SNOWFLAKE, SYBASE, VERTICA substring('hello world', 7) -- DB2, DERBY, INFORMIX, ORACLE, SQLITE substr('hello world', 7) -- FIREBIRD, TERADATA substring('hello world' FROM 7) -- INGRES substring('hello world', CAST(7 AS integer))
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.18. TRANSLATE
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The TRANSLATE()
function translates a set of characters to another set of characters within a string, based on matching positions within the search and replacement string.
SELECT translate('1 * [2 + 3]', '[]', '()');
create.select(translate(val("1 * [2 + 3]"), "[]", "()")).fetch();
The result being
+-------------+ | translate | +-------------+ | 1 * (2 + 3) | +-------------+
4.6.11.19. TRIM
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The TRIM()
function trims a string from both ends, stripping it of whitespace. See also LTRIM and RTRIM.
SELECT trim(' hello ');
create.select(trim(" hello ")).fetch();
The result being
+-------+ | trim | +-------+ | hello | +-------+
Dialect support
This example using jOOQ:
trim(" hello ")
Translates to the following dialect specific expressions:
-- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, FIREBIRD, H2, HANA, HSQLDB, INFORMIX, MARIADB, -- MEMSQL, MYSQL, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, SQLITE, SQLSERVER, SYBASE, TERADATA, VERTICA trim(' hello ') -- ASE, INGRES, SQLDATAWAREHOUSE ltrim(rtrim(' hello '))
(These are currently generated with jOOQ 3.15, see #10141)
4.6.11.20. UPPER
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The UPPER()
function transforms a string into upper case.
SELECT upper('hello');
create.select(upper("hello")).fetch();
The result being
+-------+ | upper | +-------+ | HELLO | +-------+
Dialect support
This example using jOOQ:
upper("hello")
Translates to the following dialect specific expressions:
-- ACCESS ucase('hello') -- ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, FIREBIRD, H2, HANA, HSQLDB, INFORMIX, INGRES, MARIADB, -- MEMSQL, MYSQL, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, SQLSERVER, SYBASE, TERADATA, VERTICA upper('hello')
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12. Datetime functions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Datetime functions are useful to calculate date time arithmetic and formatting.
Many functions in this section come with two flavours supporting both the JDBC datetime data types, and the JSR 310 types. These include:
- SQL
DATE
modelled by java.time.LocalDate and JDBC's java.sql.Date - SQL
TIME
modelled by java.time.LocalTime and JDBC's java.sql.Time - SQL
TIMESTAMP
modelled by java.time.LocalDateTime and JDBC's java.sql.Timestamp
Some temporal SQL data types could not be represented canonically with historic JDBC types, but only with JSR 310 types. These include:
- SQL
TIME WITH TIME ZONE
modelled by java.time.OffsetTime - SQL
TIMESTAMP WITH TIME ZONE
modelled by any of java.time.Instant (e.g. PostgreSQL), java.time.OffsetDateTime (JDBC and standard SQL), as well as java.time.ZonedDateTime (e.g. Oracle)
4.6.12.1. CURRENT_DATE
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Get the current server time as a SQL DATE
type (represented by java.sql.Date).
SELECT current_date;
create.select(currentDate()).fetch();
The result being something like
+--------------+ | current_date | +--------------+ | 2020-02-03 | +--------------+
Dialect support
This example using jOOQ:
currentDate()
Translates to the following dialect specific expressions:
-- ACCESS DATE() -- ASE, AURORA_MYSQL, MARIADB, MEMSQL, MYSQL, SNOWFLAKE current_date() -- AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, FIREBIRD, H2, HANA, HSQLDB, INGRES, POSTGRES, REDSHIFT, SQLITE, TERADATA, -- VERTICA CURRENT_DATE -- INFORMIX CURRENT YEAR TO DAY -- ORACLE trunc(current_date) -- SQLDATAWAREHOUSE, SQLSERVER convert(DATE, current_timestamp) -- SYBASE CURRENT DATE
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12.2. CURRENT_TIME
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Get the current server time as a SQL TIME
type (represented by java.sql.Time).
SELECT current_time;
create.select(currentTime()).fetch();
The result being something like
+--------------+ | current_time | +--------------+ | 15:30:45 | +--------------+
Dialect support
This example using jOOQ:
currentTime()
Translates to the following dialect specific expressions:
-- ACCESS TIME() -- ASE, AURORA_MYSQL, MARIADB, MEMSQL, MYSQL, REDSHIFT, SNOWFLAKE current_time() -- AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, FIREBIRD, H2, HANA, HSQLDB, INGRES, POSTGRES, SQLITE, TERADATA, VERTICA CURRENT_TIME -- INFORMIX CURRENT HOUR TO SECOND -- ORACLE current_timestamp -- SQLDATAWAREHOUSE, SQLSERVER convert(TIME, current_timestamp) -- SYBASE CURRENT TIME
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12.3. CURRENT_TIMESTAMP
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Get the current server time as a SQL TIMESTAMP
type (represented by java.sql.Timestamp).
SELECT current_timestamp;
create.select(currentTimestamp()).fetch();
The result being something like
+-----------------------+ | current_timestamp | +-----------------------+ | 2020-02-03 15:30:45 | +-----------------------+
Dialect support
This example using jOOQ:
currentTimestamp()
Translates to the following dialect specific expressions:
-- ACCESS now() -- ASE current_bigdatetime() -- AURORA_MYSQL, MARIADB, MEMSQL, MYSQL, SNOWFLAKE current_timestamp() -- AURORA_POSTGRES, COCKROACHDB, CUBRID, DB2, DERBY, FIREBIRD, H2, HANA, HSQLDB, INGRES, ORACLE, POSTGRES, REDSHIFT, -- SQLDATAWAREHOUSE, SQLITE, SQLSERVER, TERADATA, VERTICA CURRENT_TIMESTAMP -- INFORMIX CURRENT -- SYBASE CURRENT TIMESTAMP
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12.4. DATE
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Convert an ISO 8601 DATE
string literal into a SQL DATE
type (represented by java.sql.Date).
SELECT CAST('2020-02-03' AS DATE);
create.select(date("2020-02-03")).fetch();
The result being
+------------+ | date | +------------+ | 2020-02-03 | +------------+
Dialect support
This example using jOOQ:
date("2020-02-03")
Translates to the following dialect specific expressions:
-- ACCESS #2020/02/03# -- ASE, SQLITE, SYBASE '2020-02-03' -- CUBRID, DB2, FIREBIRD, H2, HSQLDB, INGRES, MARIADB, ORACLE, POSTGRES DATE '2020-02-03' -- INFORMIX DATETIME(2020-02-03) YEAR TO DAY -- MYSQL {d '2020-02-03'} -- SQLSERVER CAST('2020-02-03' AS date) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12.5. DATEADD
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Add an interval of type java.lang.Number (number of days) or org.jooq.types.Interval (SQL interval type) to a date (represented by java.sql.Date).
SELECT DATE '2020-02-03' + 3;
create.select(dateAdd(Date.valueOf("2020-02-03"), 3)).fetch();
The result being
+------------+ | date_add | +------------+ | 2020-02-06 | +------------+
Dialect support
This example using jOOQ:
dateAdd(Date.valueOf("2020-02-03"), 3)
Translates to the following dialect specific expressions:
-- ACCESS dateadd('d', 3, #2020/02/03#) -- ASE, SYBASE dateadd(DAY, 3, '2020-02-03') -- AURORA_MYSQL, MEMSQL, MYSQL date_add({d '2020-02-03'}, INTERVAL 3 DAY) -- AURORA_POSTGRES, COCKROACHDB, H2, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, VERTICA (DATE '2020-02-03' + 3) -- CUBRID, MARIADB date_add(DATE '2020-02-03', INTERVAL 3 DAY) -- DB2, HSQLDB (DATE '2020-02-03' + (3) day) -- DERBY CAST({fn timestampadd(SQL_TSI_DAY, 3, DATE('2020-02-03')) } AS DATE) -- FIREBIRD dateadd(DAY, 3, DATE '2020-02-03') -- HANA add_days(DATE '2020-02-03', 3) -- INFORMIX (DATETIME(2020-02-03) YEAR TO DAY + 3 UNITS DAY) -- INGRES (DATE '2020-02-03' + DATE(3 || ' days')) -- SQLDATAWAREHOUSE, SQLSERVER dateadd(DAY, 3, CAST('2020-02-03' AS date)) -- SQLITE strftime('%Y-%m-%d %H:%M:%f', '2020-02-03', (CAST(3 AS varchar) || ' day')) -- TERADATA DATE '2020-02-03' + CAST(3 || ' 00:00:00' AS INTERVAL DAY_TO_SECOND)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12.6. DATEDIFF
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Subtract two SQL DATE
types (represented by java.sql.Date).
This function comes in two flavours:
MySQL 2 argument version
In MySQL, there is a 2 argument verison of the DATEDIFF()
function, where the result produces the number of days between the two dates. The argument order is in the order of the difference notation: end_date - start_date
SELECT DATEDIFF( DATE '2020-02-03', DATE '2020-02-01');
create.select(dateDiff( Date.valueOf("2020-02-03"), Date.valueOf("2020-02-01"))).fetch();
The result being
+------------+ | datediff | +------------+ | 2 | +------------+
Dialect support
This example using jOOQ:
dateDiff(Date.valueOf("2020-02-03"), Date.valueOf("2020-02-01"))
Translates to the following dialect specific expressions:
-- ACCESS datediff('d', #2020/02/01#, #2020/02/03#) -- ASE, SYBASE datediff(DAY, '2020-02-01', '2020-02-03') -- AURORA_MYSQL, MEMSQL, MYSQL datediff({d '2020-02-03'}, {d '2020-02-01'}) -- AURORA_POSTGRES, COCKROACHDB, CUBRID, ORACLE, POSTGRES (DATE '2020-02-03' - DATE '2020-02-01') -- DB2 (days(DATE '2020-02-03') - days(DATE '2020-02-01')) -- DERBY {fn timestampdiff(sql_tsi_day, DATE('2020-02-01'), DATE('2020-02-03')) } -- FIREBIRD, H2, HSQLDB, VERTICA datediff(DAY, DATE '2020-02-01', DATE '2020-02-03') -- HANA days_between(DATE '2020-02-01', DATE '2020-02-03') -- INFORMIX CAST((DATETIME(2020-02-03) YEAR TO DAY - DATETIME(2020-02-01) YEAR TO DAY) AS integer) -- INGRES, TERADATA CAST((DATE '2020-02-03' - DATE '2020-02-01') AS integer) -- MARIADB datediff(DATE '2020-02-03', DATE '2020-02-01') -- REDSHIFT datediff('day', DATE '2020-02-01', DATE '2020-02-03') -- SNOWFLAKE CAST((DATE '2020-02-03' - DATE '2020-02-01') AS number(10)) -- SQLDATAWAREHOUSE, SQLSERVER datediff(DAY, CAST('2020-02-01' AS date), CAST('2020-02-03' AS date)) -- SQLITE (strftime('%s', '2020-02-03') - strftime('%s', '2020-02-01')) / 86400
(These are currently generated with jOOQ 3.15, see #10141)
SQL Server 3 argument version
In SQL Server, there is a 3 argument verison of the DATEDIFF()
function, where the result produces the number of date part periods between the two dates, with the dates being TRUNC-ed to the relevant date part. The argument order is in the order of the interval notation: [start_date, end_date]
. This version is supported only in jOOQ 3.14+
4.6.12.7. DAY
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Extract the DAY
value from a datetime value.
The DAY
function is a short version of the EXTRACT, passing a DatePart.DAY value as an argument.
SELECT day(DATE '2020-02-03');
create.select(day(Date.valueOf("2020-02-03"))).fetch();
The result being
+-----+ | day | +-----+ | 3 | +-----+
Dialect support
This example using jOOQ:
day(Date.valueOf("2020-02-03"))
Translates to the following dialect specific expressions:
-- ACCESS datepart('d', #2020/02/03 00:00:00#) -- ASE, SYBASE datepart(dd, '2020-02-03 00:00:00.0') -- AURORA_MYSQL, MEMSQL, MYSQL extract(DAY FROM {ts '2020-02-03 00:00:00.0'}) -- AURORA_POSTGRES, COCKROACHDB, FIREBIRD, H2, HANA, HSQLDB, INGRES, MARIADB, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, TERADATA, -- VERTICA extract(DAY FROM TIMESTAMP '2020-02-03 00:00:00.0') -- CUBRID extract(DAY FROM DATETIME '2020-02-03 00:00:00.0') -- DB2 DAY(TIMESTAMP '2020-02-03 00:00:00.0') -- DERBY DAY(TIMESTAMP('2020-02-03 00:00:00.0')) -- INFORMIX DAY(DATETIME(2020-02-03 00:00:00.0) YEAR TO FRACTION) -- SQLDATAWAREHOUSE, SQLSERVER datepart(dd, CAST('2020-02-03 00:00:00.0' AS DATETIME2)) -- SQLITE strftime('%d', '2020-02-03 00:00:00.0')
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12.8. EXTRACT
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Extract a org.jooq.DatePart from a datetime value.
SELECT EXTRACT(MONTH FROM DATE '2020-02-03');
create.select(extract(Date.valueOf("2020-02-03"), DatePart.MONTH)).fetch();
The result being
+-------+ | month | +-------+ | 2 | +-------+
Dialect support
This example using jOOQ:
extract(Date.valueOf("2020-02-03"), DatePart.MONTH)
Translates to the following dialect specific expressions:
-- ACCESS datepart('m', #2020/02/03 00:00:00#) -- ASE, SYBASE datepart(mm, '2020-02-03 00:00:00.0') -- AURORA_MYSQL, MEMSQL, MYSQL extract(MONTH FROM {ts '2020-02-03 00:00:00.0'}) -- AURORA_POSTGRES, COCKROACHDB, FIREBIRD, H2, HANA, HSQLDB, INGRES, MARIADB, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, TERADATA, -- VERTICA extract(MONTH FROM TIMESTAMP '2020-02-03 00:00:00.0') -- CUBRID extract(MONTH FROM DATETIME '2020-02-03 00:00:00.0') -- DB2 MONTH(TIMESTAMP '2020-02-03 00:00:00.0') -- DERBY MONTH(TIMESTAMP('2020-02-03 00:00:00.0')) -- INFORMIX MONTH(DATETIME(2020-02-03 00:00:00.0) YEAR TO FRACTION) -- SQLDATAWAREHOUSE, SQLSERVER datepart(mm, CAST('2020-02-03 00:00:00.0' AS DATETIME2)) -- SQLITE strftime('%m', '2020-02-03 00:00:00.0')
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12.9. HOUR
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Extract the HOUR
value from a datetime value.
The HOUR
function is a short version of the EXTRACT, passing a DatePart.HOUR value as an argument.
SELECT hour(TIMESTAMP '2020-02-03 15:30:45');
create.select(hour(Timestamp.valueOf("2020-02-03 15:30:45"))).fetch();
The result being
+------+ | hour | +------+ | 15 | +------+
Dialect support
This example using jOOQ:
hour(Timestamp.valueOf("2020-02-03 15:30:45"))
Translates to the following dialect specific expressions:
-- ACCESS datepart('h', #2020/02/03 15:30:45#) -- ASE, SYBASE datepart(hh, '2020-02-03 15:30:45.0') -- AURORA_MYSQL, MEMSQL, MYSQL extract(HOUR FROM {ts '2020-02-03 15:30:45.0'}) -- AURORA_POSTGRES, COCKROACHDB, FIREBIRD, H2, HANA, HSQLDB, INGRES, MARIADB, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, TERADATA, -- VERTICA extract(HOUR FROM TIMESTAMP '2020-02-03 15:30:45.0') -- CUBRID extract(HOUR FROM DATETIME '2020-02-03 15:30:45.0') -- DB2 HOUR(TIMESTAMP '2020-02-03 15:30:45.0') -- DERBY HOUR(TIMESTAMP('2020-02-03 15:30:45.0')) -- INFORMIX DATETIME(2020-02-03 15:30:45.0) YEAR TO FRACTION::DATETIME HOUR TO HOUR::CHAR(2)::INT -- SQLDATAWAREHOUSE, SQLSERVER datepart(hh, CAST('2020-02-03 15:30:45.0' AS DATETIME2)) -- SQLITE strftime('%H', '2020-02-03 15:30:45.0')
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12.10. MINUTE
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Extract the MINUTE
value from a datetime value.
The MINUTE
function is a short version of the EXTRACT, passing a DatePart.MINUTE value as an argument.
SELECT minute(TIMESTAMP '2020-02-03 15:30:45');
create.select(minute(Timestamp.valueOf("2020-02-03 15:30:45"))).fetch();
The result being
+--------+ | minute | +--------+ | 30 | +--------+
Dialect support
This example using jOOQ:
minute(Timestamp.valueOf("2020-02-03 15:30:45"))
Translates to the following dialect specific expressions:
-- ACCESS datepart('n', #2020/02/03 15:30:45#) -- ASE, SYBASE datepart(mi, '2020-02-03 15:30:45.0') -- AURORA_MYSQL, MEMSQL, MYSQL extract(MINUTE FROM {ts '2020-02-03 15:30:45.0'}) -- AURORA_POSTGRES, COCKROACHDB, FIREBIRD, H2, HANA, HSQLDB, INGRES, MARIADB, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, TERADATA, -- VERTICA extract(MINUTE FROM TIMESTAMP '2020-02-03 15:30:45.0') -- CUBRID extract(MINUTE FROM DATETIME '2020-02-03 15:30:45.0') -- DB2 MINUTE(TIMESTAMP '2020-02-03 15:30:45.0') -- DERBY MINUTE(TIMESTAMP('2020-02-03 15:30:45.0')) -- INFORMIX DATETIME(2020-02-03 15:30:45.0) YEAR TO FRACTION::DATETIME MINUTE TO MINUTE::CHAR(2)::INT -- SQLDATAWAREHOUSE, SQLSERVER datepart(mi, CAST('2020-02-03 15:30:45.0' AS DATETIME2)) -- SQLITE strftime('%M', '2020-02-03 15:30:45.0')
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12.11. MONTH
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Extract the MONTH
value from a datetime value.
The MONTH
function is a short version of the EXTRACT, passing a DatePart.MONTH value as an argument.
SELECT month(DATE '2020-02-03');
create.select(month(Date.valueOf("2020-02-03"))).fetch();
The result being
+-------+ | month | +-------+ | 2 | +-------+
Dialect support
This example using jOOQ:
month(Date.valueOf("2020-02-03"))
Translates to the following dialect specific expressions:
-- ACCESS datepart('m', #2020/02/03 00:00:00#) -- ASE, SYBASE datepart(mm, '2020-02-03 00:00:00.0') -- AURORA_MYSQL, MEMSQL, MYSQL extract(MONTH FROM {ts '2020-02-03 00:00:00.0'}) -- AURORA_POSTGRES, COCKROACHDB, FIREBIRD, H2, HANA, HSQLDB, INGRES, MARIADB, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, TERADATA, -- VERTICA extract(MONTH FROM TIMESTAMP '2020-02-03 00:00:00.0') -- CUBRID extract(MONTH FROM DATETIME '2020-02-03 00:00:00.0') -- DB2 MONTH(TIMESTAMP '2020-02-03 00:00:00.0') -- DERBY MONTH(TIMESTAMP('2020-02-03 00:00:00.0')) -- INFORMIX MONTH(DATETIME(2020-02-03 00:00:00.0) YEAR TO FRACTION) -- SQLDATAWAREHOUSE, SQLSERVER datepart(mm, CAST('2020-02-03 00:00:00.0' AS DATETIME2)) -- SQLITE strftime('%m', '2020-02-03 00:00:00.0')
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12.12. SECOND
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Extract the SECOND
value from a datetime value.
The SECOND
function is a short version of the EXTRACT, passing a DatePart.SECOND value as an argument.
SELECT second(TIMESTAMP '2020-02-03 15:30:45');
create.select(second(Timestamp.valueOf("2020-02-03 15:30:45"))).fetch();
The result being
+--------+ | second | +--------+ | 45 | +--------+
Dialect support
This example using jOOQ:
second(Timestamp.valueOf("2020-02-03 15:30:45"))
Translates to the following dialect specific expressions:
-- ACCESS datepart('s', #2020/02/03 15:30:45#) -- ASE, SYBASE datepart(ss, '2020-02-03 15:30:45.0') -- AURORA_MYSQL, MEMSQL, MYSQL extract(SECOND FROM {ts '2020-02-03 15:30:45.0'}) -- AURORA_POSTGRES, COCKROACHDB, FIREBIRD, H2, HANA, HSQLDB, INGRES, MARIADB, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, TERADATA, -- VERTICA extract(SECOND FROM TIMESTAMP '2020-02-03 15:30:45.0') -- CUBRID extract(SECOND FROM DATETIME '2020-02-03 15:30:45.0') -- DB2 SECOND(TIMESTAMP '2020-02-03 15:30:45.0') -- DERBY SECOND(TIMESTAMP('2020-02-03 15:30:45.0')) -- INFORMIX DATETIME(2020-02-03 15:30:45.0) YEAR TO FRACTION::DATETIME SECOND TO SECOND::CHAR(2)::INT -- SQLDATAWAREHOUSE, SQLSERVER datepart(ss, CAST('2020-02-03 15:30:45.0' AS DATETIME2)) -- SQLITE strftime('%S', '2020-02-03 15:30:45.0')
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12.13. TIME
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Convert an ISO 8601 TIME
string literal into a SQL TIME
type (represented by java.sql.Time).
SELECT CAST('15:30:45' AS TIME);
create.select(time("15:30:45")).fetch();
The result being
+----------+ | time | +----------+ | 15:30:45 | +----------+
Dialect support
This example using jOOQ:
time("15:30:45")
Translates to the following dialect specific expressions:
-- ACCESS, MYSQL {t '15:30:45'} -- ASE, SQLITE, SYBASE '15:30:45' -- CUBRID, DB2, FIREBIRD, H2, HSQLDB, INGRES, MARIADB, POSTGRES TIME '15:30:45' -- INFORMIX DATETIME(15:30:45) HOUR TO SECOND -- ORACLE TIMESTAMP '1970-01-01 15:30:45' -- SQLSERVER CAST('15:30:45' AS time) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12.14. TIMESTAMP
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Convert an ISO 8601 TIMESTAMP
string literal into a SQL TIMESTAMP
type (represented by java.sql.Timestamp).
SELECT CAST('2020-02-03 15:30:45' AS TIMESTAMP);
create.select(timestamp("2020-02-03 15:30:45")).fetch();
The result being
+---------------------+ | timestamp | +---------------------+ | 2020-02-03 15:30:45 | +---------------------+
Dialect support
This example using jOOQ:
timestamp("2020-02-03 15:30:45")
Translates to the following dialect specific expressions:
-- ACCESS #2020/02/03 15:30:45# -- ASE, SQLITE, SYBASE '2020-02-03 15:30:45.0' -- CUBRID DATETIME '2020-02-03 15:30:45.0' -- DB2, FIREBIRD, H2, HSQLDB, INGRES, MARIADB, ORACLE, POSTGRES TIMESTAMP '2020-02-03 15:30:45.0' -- INFORMIX DATETIME(2020-02-03 15:30:45.0) YEAR TO FRACTION -- MYSQL {ts '2020-02-03 15:30:45.0'} -- SQLSERVER CAST('2020-02-03 15:30:45.0' AS DATETIME2) -- AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12.15. TIMESTAMPADD
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Add an interval of type java.lang.Number (number of days) or org.jooq.types.Interval (SQL interval type) to a timestamp (represented by java.sql.Timestamp).
SELECT DATE '2020-02-03 15:30:45' + INTERVAL 3 DAYS;
create.select(timestampAdd(Timestamp.valueOf("2020-02-03 15:30:45"), 3)).fetch();
The result being
+---------------------+ | timestamp_add | +---------------------+ | 2020-02-06 15:30:45 | +---------------------+
Dialect support
This example using jOOQ:
timestampAdd(Timestamp.valueOf("2020-02-03 15:30:45"), 3)
Translates to the following dialect specific expressions:
-- ACCESS dateadd('d', 3, #2020/02/03 15:30:45#) -- ASE, SYBASE dateadd(DAY, 3, '2020-02-03 15:30:45.0') -- AURORA_MYSQL, MEMSQL, MYSQL date_add({ts '2020-02-03 15:30:45.0'}, INTERVAL 3 DAY) -- AURORA_POSTGRES, COCKROACHDB, POSTGRES, REDSHIFT (TIMESTAMP '2020-02-03 15:30:45.0' + 3 * INTERVAL '1 day') -- CUBRID date_add(DATETIME '2020-02-03 15:30:45.0', INTERVAL 3 DAY) -- DB2, HSQLDB (TIMESTAMP '2020-02-03 15:30:45.0' + (3) day) -- DERBY {fn timestampadd(SQL_TSI_DAY, 3, TIMESTAMP('2020-02-03 15:30:45.0')) } -- FIREBIRD dateadd(DAY, 3, TIMESTAMP '2020-02-03 15:30:45.0') -- H2, ORACLE, SNOWFLAKE, VERTICA (TIMESTAMP '2020-02-03 15:30:45.0' + 3) -- HANA add_days(TIMESTAMP '2020-02-03 15:30:45.0', 3) -- INFORMIX (DATETIME(2020-02-03 15:30:45.0) YEAR TO FRACTION + 3 UNITS DAY) -- INGRES (TIMESTAMP '2020-02-03 15:30:45.0' + DATE(3 || ' days')) -- MARIADB date_add(TIMESTAMP '2020-02-03 15:30:45.0', INTERVAL 3 DAY) -- SQLDATAWAREHOUSE, SQLSERVER dateadd(DAY, 3, CAST('2020-02-03 15:30:45.0' AS DATETIME2)) -- SQLITE strftime('%Y-%m-%d %H:%M:%f', '2020-02-03 15:30:45.0', (CAST(3 AS varchar) || ' day')) -- TERADATA TIMESTAMP '2020-02-03 15:30:45.0' + CAST(3 || ' 00:00:00' AS INTERVAL DAY_TO_SECOND)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12.16. TRUNC
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Truncate a datetime value to the precision of a certain org.jooq.DatePart, or DatePart.DAY by default.
SELECT TRUNC(DATE '2020-02-03', 'YYYY');
create.select(trunc(Date.valueOf("2020-02-03", DatePart.YEAR))).fetch();
The result being
+------------+ | trunc | +------------+ | 2020-01-01 | +------------+
Dialect support
This example using jOOQ:
trunc(Date.valueOf("2020-02-03"), DatePart.YEAR)
Translates to the following dialect specific expressions:
-- CUBRID, HSQLDB trunc(DATE '2020-02-03', 'YY') -- DB2, ORACLE trunc(DATE '2020-02-03', 'YYYY') -- INFORMIX trunc(DATETIME(2020-02-03) YEAR TO DAY, 'YEAR') -- POSTGRES date_trunc('year', DATE '2020-02-03') -- ACCESS, ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, FIREBIRD, H2, HANA, INGRES, MARIADB, MEMSQL, MYSQL, -- REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, SQLSERVER, SYBASE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.12.17. YEAR
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Extract the YEAR
value from a datetime value.
The YEAR
function is a short version of the EXTRACT, passing a DatePart.YEAR value as an argument.
SELECT year(DATE '2020-02-03');
create.select(year(Date.valueOf("2020-02-03"))).fetch();
The result being
+------+ | year | +------+ | 2020 | +------+
Dialect support
This example using jOOQ:
year(Date.valueOf("2020-02-03"))
Translates to the following dialect specific expressions:
-- ACCESS datepart('yyyy', #2020/02/03 00:00:00#) -- ASE, SYBASE datepart(yy, '2020-02-03 00:00:00.0') -- AURORA_MYSQL, MEMSQL, MYSQL extract(YEAR FROM {ts '2020-02-03 00:00:00.0'}) -- AURORA_POSTGRES, COCKROACHDB, FIREBIRD, H2, HANA, HSQLDB, INGRES, MARIADB, ORACLE, POSTGRES, REDSHIFT, SNOWFLAKE, TERADATA, -- VERTICA extract(YEAR FROM TIMESTAMP '2020-02-03 00:00:00.0') -- CUBRID extract(YEAR FROM DATETIME '2020-02-03 00:00:00.0') -- DB2 YEAR(TIMESTAMP '2020-02-03 00:00:00.0') -- DERBY YEAR(TIMESTAMP('2020-02-03 00:00:00.0')) -- INFORMIX YEAR(DATETIME(2020-02-03 00:00:00.0) YEAR TO FRACTION) -- SQLDATAWAREHOUSE, SQLSERVER datepart(yy, CAST('2020-02-03 00:00:00.0' AS DATETIME2)) -- SQLITE strftime('%Y', '2020-02-03 00:00:00.0')
(These are currently generated with jOOQ 3.15, see #10141)
4.6.13. System functions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Some system functions are supported by jOOQ.
4.6.13.1. CURRENT_USER
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The CURRENT_USER()
function produces the dialect dependent expression to produce the currently connected user for the JDBC connection.
SELECT current_user;
create.select(currentUser()).fetch();
The result being, for example
+--------------+ | current_user | +--------------+ | sa | +--------------+
Dialect support
This example using jOOQ:
currentUser()
Translates to the following dialect specific expressions:
-- ASE, INFORMIX, ORACLE user -- CUBRID, H2, MARIADB, MYSQL current_user() -- DB2, DERBY, FIREBIRD, HSQLDB, INGRES, POSTGRES, SQLSERVER, SYBASE current_user -- ACCESS, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, HANA, MEMSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, -- VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.14. Aggregate functions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Aggregate functions work like Java java.util.stream.Collector, as they aggregate data from a group of data into a new data structure.
This section will first explain concepts common to many aggregate functions, and then proceed to explaining individual aggregate functions supported by jOOQ.
4.6.14.1. Grouping
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Aggregate functions aggregate data from groups of data into individual values. There are three main ways of forming such groups:
- A GROUP BY clause is used to define the groups for which data is aggregated
- No GROUP BY clause is defined, which means that all data from a SELECT statement (or subquery) is aggregated into a single row
- All aggregate functions can be used as window functions, in case of which they will aggregate the data of the specified window
Aggregation with GROUP BY
In the presence of GROUP BY, a SELECT statement transforms the output of its FROM clause into a new "virtual" set of tuples containing:
- The column expressions of the
GROUP BY
clause. In the overall data set, the values of these column expressions is unique. - A set of data corresponding to each row produced by the
GROUP BY
clause. This data set can be aggregated per group using aggregate functions.
Using GROUP BY
means that a new set of rules need to be observed in the rest of the query:
- Clauses that logically precede GROUP BY are not affected. These include, for example, FROM and WHERE
- All other clauses (e.g. HAVING, WINDOW, SELECT, or ORDER BY) may now only reference expressions built from the expressions in the
GROUP BY
clause, or aggregations on any other expression
An example:
SELECT AUTHOR_ID, count(*) FROM BOOK GROUP BY AUTHOR_ID;
create.select(BOOK.AUTHOR_ID, count()) .from(BOOK) .groupBy(BOOK.AUTHOR_ID).fetch();
Producing:
+-----------+-------+ | AUTHOR_ID | count | +-----------+-------+ | 1 | 2 | | 2 | 2 | +-----------+-------+
Per the rules imposed by GROUP BY
, it would not be possible, for example, to project the BOOK.TITLE
column, because it is not defined per author. An author has written many books, so we don't know what a BOOK.TITLE
is supposed to mean. Only an aggregation, such as LISTAGG can reference BOOK.TITLE
as an argument.
Aggregation without GROUP BY
In the absence of GROUP BY, a SELECT statement that contains at least one aggregate function in any of its clauses (e.g. HAVING, WINDOW, SELECT, or ORDER BY) will proceed to aggregating the entire data into a single row. There is an implied "empty grouping", i.e. a grouping that has no GROUP BY
columns. These two are the same things:
SELECT count(*) FROM BOOK; SELECT count(*) FROM BOOK GROUP BY ();
See also GROUPING SETS for more details about this empty GROUP BY
syntax.
For example, using our sample database, which has 4 books with IDs 1-4, we can write:
SELECT count(*), sum(ID) FROM BOOK
create.select(count(), sum(BOOK.ID)) .from(BOOK).fetch();
Producing:
+----------+---------+ | count(*) | sum(ID) | +----------+---------+ | 4 | 10 | +----------+---------+
No other columns from the tables in the FROM clause may be projected by the SELECT clause, because they would not be defined for this single group. For example, no specific BOOK.TITLE
is defined for the aggregated value of all books. Only an aggregation, such as LISTAGG can reference BOOK.TITLE
as an argument.
However, any expression whose components do not depend on content of the group is allowed. For example, it is possible to combine aggregate functions and constant expressions like this:
SELECT count(*) + sum(ID) + 1 FROM BOOK
create.select(count().plus(sum(BOOK.ID)).plus(1)) .from(BOOK).fetch();
Producing:
+------+ | plus | +------+ | 15 | +------+
4.6.14.2. Distinctness
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
A useful thing to do when aggregating data is to remove duplicate input first, prior to aggregation. A few aggregate functions support a DISTINCT
keyword for that purpose. For example, we can query
SELECT count(AUTHOR_ID), count(DISTINCT AUTHOR_ID), group_concat(AUTHOR_ID), group_concat(DISTINCT AUTHOR_ID) FROM BOOK
create.select( count(BOOK.AUTHOR_ID), countDistinct(BOOK.AUTHOR_ID), groupConcat(BOOK.AUTHOR_ID), groupConcatDistinct(BOOK.AUTHOR_ID)) .from(BOOK).fetch();
Producing:
+-------+----------------+--------------+-----------------------+ | count | count_distinct | group_concat | group_concat_distinct | +-------+----------------+--------------+-----------------------+ | 4 | 2 | 1, 1, 2, 2 | 1, 2 | +-------+----------------+--------------+-----------------------+
If DISTINCT
is available through the jOOQ API, it is always appended to the aggregate function name, such as count()
and countDistinct()
. sum()
and sumDistinct()
, etc.
4.6.14.3. Filtering
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SQL standard specifies an optional FILTER
clause, that can be appended to all aggregate functions including aggregated window functions. This is very useful, for example, to implement "pivot" tables, such as the following:
SELECT count(*), count(*) FILTER (WHERE TITLE LIKE 'A%'), count(*) FILTER (WHERE TITLE LIKE '%A%') FROM BOOK
create.select( count(), count().filterWhere(BOOK.TITLE.like("A%")), count().filterWhere(BOOK.TITLE.like("%A%"))) .from(BOOK)
Producing:
+-------+-------+-------+ | count | count | count | +-------+-------+-------+ | 4 | 1 | 2 | +-------+-------+-------+
Or, with GROUP BY:
SELECT AUTHOR_ID, count(*), count(*) FILTER (WHERE TITLE LIKE 'A%'), count(*) FILTER (WHERE TITLE LIKE '%A%') FROM BOOK GROUP BY AUTHOR_ID
create.select( BOOK.AUTHOR_ID, count(), count().filterWhere(BOOK.TITLE.like("A%")), count().filterWhere(BOOK.TITLE.like("%A%"))) .from(BOOK) .groupBy(BOOK.AUTHOR_ID)
Producing:
+-----------+-------+-------+-------+ | AUTHOR_ID | count | count | count | +-----------+-------+-------+-------+ | 1 | 2 | 1 | 1 | | 2 | 2 | 0 | 1 | +-----------+-------+-------+-------+
It is usually a good idea to calculate multiple aggregate functions in a single query, if this is possible, and FILTER
helps here.
Only a few dialects implement native support for the FILTER
clause. In all other databases, jOOQ emulates the clause using a CASE expression. Aggregate functions exclude NULL
values from aggregation.
Dialect support
This example using jOOQ:
count().filterWhere(BOOK.TITLE.like("A%"))
Translates to the following dialect specific expressions:
-- ACCESS count(SWITCH(BOOK.TITLE LIKE 'A%', 1)) -- ASE, AURORA_MYSQL, CUBRID, DB2, DERBY, FIREBIRD, HANA, INFORMIX, INGRES, MARIADB, MEMSQL, MYSQL, ORACLE, REDSHIFT, -- SNOWFLAKE, SQLDATAWAREHOUSE, SQLSERVER, SYBASE, TERADATA, VERTICA count(CASE WHEN BOOK.TITLE LIKE 'A%' THEN 1 END) -- AURORA_POSTGRES, COCKROACHDB, H2, HSQLDB, POSTGRES, SQLITE count(*) FILTER (WHERE BOOK.TITLE LIKE 'A%')
(These are currently generated with jOOQ 3.15, see #10141)
4.6.14.4. Ordering WITHIN GROUP
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Some aggregate functions allow for ordering their inputs to produce an ordered output. These aggregate functions allow for specifying a mandatory WITHIN GROUP (ORDER BY ..)
clause after the function.
Standard SQL talks about "ordered set aggregate functions" which come in three flavours
- Hypothetical set functions: Functions that check for the position of a hypothetical value inside of an ordered set. These include RANK, DENSE_RANK, PERCENT_RANK, CUME_DIST.
-
LISTAGG, which is inconsistently using the
WITHIN GROUP
syntax, as it is used to order the output of the function, and isn't mandatory in all dialects.
4.6.14.5. Keeping
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Oracle allows for restricting other aggregate functions using the KEEP()
clause, which is supported by jOOQ. In Oracle, some aggregate functions (e.g. MIN, MAX, SUM, AVG, COUNT, VARIANCE
, or STDDEV
) can be restricted by this clause, hence org.jooq.AggregateFunction also allows for specifying it. Here are a couple of examples using this clause:
SUM(BOOK.AMOUNT_SOLD) KEEP(DENSE_RANK FIRST ORDER BY BOOK.AUTHOR_ID)
sum(BOOK.AMOUNT_SOLD) .keepDenseRankFirstOrderBy(BOOK.AUTHOR_ID)
4.6.14.6. AVG
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The AVG()
aggregate function calculates the average value of all input values
SELECT avg(ID) FROM BOOK
create.select(avg(BOOK.ID)) .from(BOOK)
Producing:
+-----+ | avg | +-----+ | 2.5 | +-----+
Dialect support
This example using jOOQ:
avg(BOOK.ID)
Translates to the following dialect specific expressions:
-- All dialects avg(BOOK.ID)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.14.7. COUNT
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The COUNT()
aggregate function comes in two flavours:
-
COUNT(*)
: This version counts the number of tuples in a group, regardless of any contents, includingNULL
values. -
COUNT(expression)
: This version counts the number of non-NULL
expression evaluations per group.
The second version can be used to emulate the FILTER clause as the argument expression effectively filters out NULL
values. Alternatively, in the case of a LEFT JOIN, the outer joined rows can be counted using an expression on the primary key, because COUNT(*)
always produces at least one row.
SELECT AUTHOR.ID, count(*), count(BOOK.ID) FROM AUTHOR LEFT JOIN BOOK ON BOOK.AUTHOR_ID = AUTHOR.ID
create.select( AUTHOR.ID, count(), count(BOOK.ID)) .from(AUTHOR) .leftJoin(BOOK) .on(BOOK.AUTHOR_ID.eq(AUTHOR.ID))
Producing (assuming the presence of an author with ID = 3, but without books):
+----+----------+----------------+ | ID | count(*) | count(BOOK.ID) | +----+----------+----------------+ | 1 | 2 | 2 | | 2 | 2 | 2 | | 3 | 1 | 0 | +----+----------+----------------+
Dialect support
This example using jOOQ:
count(BOOK.ID)
Translates to the following dialect specific expressions:
-- All dialects count(BOOK.ID)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.14.8. CUME_DIST
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The CUME_DIST()
hypothetical set function calculates the cumulative distribution of the hypothetical value, i.e. the relative rank from 1/N to 1 (PERCENT_RANK produces values from 0 to 1)
SELECT cume_dist(0) WITHIN GROUP (ORDER BY ID), cume_dist(2) WITHIN GROUP (ORDER BY ID), cume_dist(4) WITHIN GROUP (ORDER BY ID) FROM BOOK
create.select( cumeDist(val(0)).withinGroupOrderBy(BOOK.ID), cumeDist(val(2)).withinGroupOrderBy(BOOK.ID), cumeDist(val(4)).withinGroupOrderBy(BOOK.ID)) .from(BOOK)
Producing:
+--------------+--------------+--------------+ | cume_dist(0) | cume_dist(2) | cume_dist(4) | +--------------+--------------+--------------+ | 0.2 | 0.6 | 1.0 | +--------------+--------------+--------------+
Dialect support
This example using jOOQ:
cumeDist(val(0)).withinGroupOrderBy(BOOK.ID)
Translates to the following dialect specific expressions:
-- CUBRID, INFORMIX, ORACLE, POSTGRES, SQLSERVER, SYBASE cume_dist(0) WITHIN GROUP (ORDER BY BOOK.ID) -- ACCESS, ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DB2, DERBY, FIREBIRD, H2, HANA, HSQLDB, INGRES, MARIADB, MEMSQL, -- MYSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.14.9. DENSE_RANK
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The DENSE_RANK()
hypothetical set function calculates the rank without gaps of the hypothetical value, i.e. dense ranks will be 1, 1, 1, 2, 3, 3, 4 (RANK produces values with gaps, e.g. 1, 1, 1, 4, 5, 5, 7)
SELECT dense_rank(0) WITHIN GROUP (ORDER BY AUTHOR_ID), dense_rank(1) WITHIN GROUP (ORDER BY AUTHOR_ID), dense_rank(2) WITHIN GROUP (ORDER BY AUTHOR_ID) FROM BOOK
create.select( denseRank(val(0)).withinGroupOrderBy(BOOK.AUTHOR_ID), denseRank(val(2)).withinGroupOrderBy(BOOK.AUTHOR_ID), denseRank(val(4)).withinGroupOrderBy(BOOK.AUTHOR_ID)) .from(BOOK)
Producing:
+---------------+---------------+---------------+ | dense_rank(0) | dense_rank(1) | dense_rank(2) | +---------------+---------------+---------------+ | 1 | 1 | 2 | +---------------+---------------+---------------+
Dialect support
This example using jOOQ:
denseRank(val(0)).withinGroupOrderBy(BOOK.ID)
Translates to the following dialect specific expressions:
-- CUBRID, DB2, INFORMIX, ORACLE, POSTGRES, SQLSERVER, SYBASE dense_rank(0) WITHIN GROUP (ORDER BY BOOK.ID) -- ACCESS, ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, FIREBIRD, H2, HANA, HSQLDB, INGRES, MARIADB, MEMSQL, MYSQL, -- REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.14.10. GROUP_CONCAT
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The GROUP_CONCAT()
aggregate function is the MySQL version of the standard SQL LISTAGG function, to concatenate aggregate data into a string.
SELECT group_concat(ID), group_concat(ID ORDER BY ID), group_concat(ID SEPARATOR '; '), group_concat(ID ORDER BY ID SEPARATOR '; '), FROM BOOK
create.select( groupConcat(BOOK.ID), groupConcat(BOOK.ID).orderBy(BOOK.ID), groupConcat(BOOK.ID).separator("; "), groupConcat(BOOK.ID).orderBy(BOOK.ID).separator("; ")) .from(BOOK).fetch();
Producing:
+--------------+--------------+--------------+--------------+ | group_concat | group_concat | group_concat | group_concat | +--------------+--------------+--------------+--------------+ | 1, 3, 4, 2 | 1, 2, 3, 4 | 1; 3; 4; 2 | 1; 2; 3; 4 | +--------------+--------------+--------------+--------------+
Dialect support
This example using jOOQ:
groupConcat(BOOK.ID)
Translates to the following dialect specific expressions:
-- CUBRID, H2, HSQLDB, MARIADB, MYSQL group_concat(BOOK.ID SEPARATOR ',') -- DB2 listagg(BOOK.ID, ',') -- ORACLE listagg(BOOK.ID, ',') WITHIN GROUP (ORDER BY NULL) -- POSTGRES string_agg(CAST(BOOK.ID AS varchar), ',') -- SQLITE group_concat(BOOK.ID, ',') -- SYBASE list(CAST(BOOK.ID AS varchar), ',') -- ACCESS, ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, FIREBIRD, HANA, INFORMIX, INGRES, MEMSQL, REDSHIFT, -- SNOWFLAKE, SQLDATAWAREHOUSE, SQLSERVER, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.14.11. LISTAGG
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The LISTAGG()
aggregate function aggregates data into a string. It uses the WITHIN GROUP syntax.
SELECT listagg(ID) WITHIN GROUP (ORDER BY ID), listagg(ID, '; ') WITHIN GROUP (ORDER BY ID), FROM BOOK
create.select( listagg(BOOK.ID).withinGroupOrderBy(BOOK.ID), listagg(BOOK.ID, "; ").withinGroupOrderBy(BOOK.ID)) .from(BOOK).fetch();
Producing:
+------------+--------------+ | listagg | listagg | +------------+--------------+ | 1, 2, 3, 4 | 1; 2; 3; 4 | +------------+--------------+
4.6.14.12. MAX
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The MAX()
aggregate function calculates the maximum value of all input values
SELECT max(ID) FROM BOOK
create.select(max(BOOK.ID)) .from(BOOK)
Producing:
+-----+ | max | +-----+ | 4 | +-----+
Dialect support
This example using jOOQ:
max(BOOK.ID)
Translates to the following dialect specific expressions:
-- All dialects max(BOOK.ID)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.14.13. MEDIAN
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The MEDIAN()
aggregate function calculates the median value of all input values.
SELECT median(ID) FROM BOOK
create.select(median(BOOK.ID)) .from(BOOK)
Producing:
+--------+ | median | +--------+ | 2.5 | +--------+
Dialect support
This example using jOOQ:
median(BOOK.ID)
Translates to the following dialect specific expressions:
-- CUBRID, HSQLDB, ORACLE, SYBASE median(BOOK.ID) -- ACCESS, ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DB2, DERBY, FIREBIRD, H2, HANA, INFORMIX, INGRES, MARIADB, MEMSQL, -- MYSQL, POSTGRES, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, SQLSERVER, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.14.14. MIN
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The MIN()
aggregate function calculates the minimum value of all input values
SELECT min(ID) FROM BOOK
create.select(min(BOOK.ID)) .from(BOOK)
Producing:
+-----+ | min | +-----+ | 1 | +-----+
Dialect support
This example using jOOQ:
min(BOOK.ID)
Translates to the following dialect specific expressions:
-- All dialects min(BOOK.ID)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.14.15. PERCENT_RANK
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The PERCENT_RANK()
hypothetical set function calculates the percent rank of the hypothetical value, i.e. the relative rank from 0 to 1 (CUME_DIST produces values from 1/N to 1)
SELECT percent_rank(0) WITHIN GROUP (ORDER BY ID), percent_rank(2) WITHIN GROUP (ORDER BY ID), percent_rank(4) WITHIN GROUP (ORDER BY ID) FROM BOOK
create.select( percentRank(val(0)).withinGroupOrderBy(BOOK.ID), percentRank(val(2)).withinGroupOrderBy(BOOK.ID), percentRank(val(4)).withinGroupOrderBy(BOOK.ID)) .from(BOOK)
Producing:
+-----------------+-----------------+-----------------+ | percent_rank(0) | percent_rank(2) | percent_rank(4) | +-----------------+-----------------+-----------------+ | 0.0 | 0.25 | 0.75 | +-----------------+-----------------+-----------------+
Dialect support
This example using jOOQ:
percentRank(val(0)).withinGroupOrderBy(BOOK.ID)
Translates to the following dialect specific expressions:
-- CUBRID, INFORMIX, ORACLE, POSTGRES, SQLSERVER, SYBASE percent_rank(0) WITHIN GROUP (ORDER BY BOOK.ID) -- ACCESS, ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DB2, DERBY, FIREBIRD, H2, HANA, HSQLDB, INGRES, MARIADB, MEMSQL, -- MYSQL, REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.14.16. PRODUCT
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The PRODUCT()
aggregate function is a synthetic aggregate function that calculates the product of all values in the group, similar to how the SUM function calculates the sum.
SELECT product(ID) FROM BOOK
create.select(product(BOOK.ID)) .from(BOOK)
Producing:
+---------+ | product | +---------+ | 24 | +---------+
4.6.14.17. RANK
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The RANK()
hypothetical set function calculates the rank with gaps of the hypothetical value, i.e. ranks will be 1, 1, 1, 4, 5, 5, 7 (DENSE_RANK produces values without gaps, e.g. 1, 1, 1, 2, 3, 3, 4)
SELECT rank(0) WITHIN GROUP (ORDER BY AUTHOR_ID), rank(1) WITHIN GROUP (ORDER BY AUTHOR_ID), rank(2) WITHIN GROUP (ORDER BY AUTHOR_ID) FROM BOOK
create.select( rank(val(0)).withinGroupOrderBy(BOOK.AUTHOR_ID), rank(val(2)).withinGroupOrderBy(BOOK.AUTHOR_ID), rank(val(4)).withinGroupOrderBy(BOOK.AUTHOR_ID)) .from(BOOK)
Producing:
+---------+---------+---------+ | rank(0) | rank(1) | rank(2) | +---------+---------+---------+ | 1 | 1 | 3 | +---------+---------+---------+
Dialect support
This example using jOOQ:
rank(val(0)).withinGroupOrderBy(BOOK.ID)
Translates to the following dialect specific expressions:
-- CUBRID, DB2, INFORMIX, ORACLE, POSTGRES, SQLSERVER, SYBASE rank(0) WITHIN GROUP (ORDER BY BOOK.ID) -- ACCESS, ASE, AURORA_MYSQL, AURORA_POSTGRES, COCKROACHDB, DERBY, FIREBIRD, H2, HANA, HSQLDB, INGRES, MARIADB, MEMSQL, MYSQL, -- REDSHIFT, SNOWFLAKE, SQLDATAWAREHOUSE, SQLITE, TERADATA, VERTICA /* UNSUPPORTED */
(These are currently generated with jOOQ 3.15, see #10141)
4.6.14.18. SUM
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SUM()
aggregate function calculates the sum of all values per group.
SELECT sum(ID) FROM BOOK
create.select(sum(BOOK.ID)) .from(BOOK)
Producing:
+-----+ | sum | +-----+ | 10 | +-----+
Dialect support
This example using jOOQ:
sum(BOOK.ID)
Translates to the following dialect specific expressions:
-- All dialects sum(BOOK.ID)
(These are currently generated with jOOQ 3.15, see #10141)
4.6.15. Window functions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Most major RDBMS support the concept of window functions.
As previously discussed, any org.jooq.AggregateFunction can be transformed into a window function using the over()
method. See the chapter about aggregate functions for details. In addition to those, there are also some more window functions supported by jOOQ, as declared in the DSL:
// Ranking functions WindowOverStep<Integer> rowNumber(); WindowOverStep<Integer> rank(); WindowOverStep<Integer> denseRank(); WindowOverStep<BigDecimal> percentRank(); // Windowing functions <T> WindowIgnoreNullsStep<T> firstValue(Field<T> field); <T> WindowIgnoreNullsStep<T> lastValue(Field<T> field) <T> WindowIgnoreNullsStep<T> lead(Field<T> field); <T> WindowIgnoreNullsStep<T> lead(Field<T> field, int offset); <T> WindowIgnoreNullsStep<T> lead(Field<T> field, int offset, T defaultValue); <T> WindowIgnoreNullsStep<T> lead(Field<T> field, int offset, Field<T> defaultValue); <T> WindowIgnoreNullsStep<T> lag(Field<T> field); <T> WindowIgnoreNullsStep<T> lag(Field<T> field, int offset); <T> WindowIgnoreNullsStep<T> lag(Field<T> field, int offset, T defaultValue); <T> WindowIgnoreNullsStep<T> lag(Field<T> field, int offset, Field<T> defaultValue); // Statistical functions WindowOverStep<BigDecimal> cumeDist(); WindowOverStep<Integer> ntile(int number);
SQL distinguishes between various window function types (e.g. "ranking functions"). Depending on the function, SQL expects mandatory PARTITION BY
or ORDER BY
clauses within the OVER()
clause. jOOQ does not enforce those rules for two reasons:
- Your JDBC driver or database already checks SQL syntax semantics
- Not all databases behave correctly according to the SQL standard
If possible, however, jOOQ tries to render missing clauses for you, if a given SQL dialect is more restrictive.
Some examples
Here are some simple examples of window functions with jOOQ:
SELECT -- Sample uses of ROW_NUMBER() ROW_NUMBER() OVER (), ROW_NUMBER() OVER (ORDER BY BOOK.ID), ROW_NUMBER() OVER (PARTITION BY BOOK.AUTHOR_ID ORDER BY BOOK.ID), -- Sample uses of FIRST_VALUE FIRST_VALUE(BOOK.ID) OVER(), FIRST_VALUE(BOOK.ID IGNORE NULLS) OVER(), FIRST_VALUE(BOOK.ID RESPECT NULLS) OVER() FROM BOOK
create.select( // Sample uses of rowNumber() rowNumber().over(), rowNumber().over().partitionBy(BOOK.AUTHOR_ID), rowNumber().over().partitionBy(BOOK.AUTHOR_ID).orderBy(BOOK.ID), // Sample uses of firstValue() firstValue(BOOK.ID).over(), firstValue(BOOK.ID).ignoreNulls().over(), firstValue(BOOK.ID).respectNulls().over()) .from(BOOK).fetch();
An advanced window function example
Window functions can be used for things like calculating a "running total". The following example fetches transactions and the running total for every transaction going back to the beginning of the transaction table (ordered by booked_at). Window functions are accessible from the previously seen org.jooq.AggregateFunction type using the over()
method:
SELECT booked_at, amount, SUM(amount) OVER (PARTITION BY 1 ORDER BY booked_at ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS total FROM transactions
create.select(t.BOOKED_AT, t.AMOUNT, sum(t.AMOUNT).over().partitionByOne() .orderBy(t.BOOKED_AT) .rowsBetweenUnboundedPreceding() .andCurrentRow().as("total") .from(TRANSACTIONS.as("t")) .fetch();
Window functions created from ordered-set aggregate functions
In the previous chapter about aggregate functions, we have seen the concept of "ordered-set aggregate functions", such as Oracle's LISTAGG()
. These functions have a window function / analytical function variant, as well. For example:
SELECT LISTAGG(TITLE, ', ') WITHIN GROUP (ORDER BY TITLE) OVER (PARTITION BY BOOK.AUTHOR_ID) FROM BOOK
create.select(listAgg(BOOK.TITLE, ", ") .withinGroupOrderBy(BOOK.TITLE) .over().partitionBy(BOOK.AUTHOR_ID)) .from(BOOK) .fetch();
Window functions created from Oracle's FIRST
and LAST
aggregate functions
In the previous chapter about aggregate functions, we have seen the concept of "FIRST
and LAST
aggregate functions". These functions have a window function / analytical function variant, as well. For example:
SUM(BOOK.AMOUNT_SOLD) KEEP(DENSE_RANK FIRST ORDER BY BOOK.AUTHOR_ID) OVER(PARTITION BY 1)
sum(BOOK.AMOUNT_SOLD) .keepDenseRankFirstOrderBy(BOOK.AUTHOR_ID) .over().partitionByOne();
Window functions created from user-defined aggregate functions
User-defined aggregate functions also implement org.jooq.AggregateFunction, hence they can also be transformed into window functions using over()
. This is supported by Oracle in particular. See the manual's section about user-defined aggregate functions for more details.
4.6.16. Grouping functions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
ROLLUP() explained in SQL
The SQL standard defines special functions that can be used in the GROUP BY clause: the grouping functions. These functions can be used to generate several groupings in a single clause. This can best be explained in SQL. Let's take ROLLUP()
for instance:
-- ROLLUP() with one argument SELECT AUTHOR_ID, COUNT(*) FROM BOOK GROUP BY ROLLUP(AUTHOR_ID) -- ROLLUP() with two arguments SELECT AUTHOR_ID, PUBLISHED_IN, COUNT(*) FROM BOOK GROUP BY ROLLUP(AUTHOR_ID, PUBLISHED_IN)
-- The same query using UNION ALL: SELECT AUTHOR_ID, COUNT(*) FROM BOOK GROUP BY AUTHOR_ID UNION ALL SELECT NULL, COUNT(*) FROM BOOK GROUP BY () ORDER BY 1 NULLS LAST -- The same query using UNION ALL: SELECT AUTHOR_ID, PUBLISHED_IN, COUNT(*) FROM BOOK GROUP BY AUTHOR_ID, PUBLISHED_IN UNION ALL SELECT AUTHOR_ID, NULL, COUNT(*) FROM BOOK GROUP BY AUTHOR_ID UNION ALL SELECT NULL, NULL, COUNT(*) FROM BOOK GROUP BY () ORDER BY 1 NULLS LAST, 2 NULLS LAST
In English, the ROLLUP()
grouping function provides N+1
groupings, when N
is the number of arguments to the ROLLUP()
function. Each grouping has an additional group field from the ROLLUP()
argument field list. The results of the second query might look something like this:
+-----------+--------------+----------+ | AUTHOR_ID | PUBLISHED_IN | COUNT(*) | +-----------+--------------+----------+ | 1 | 1945 | 1 | <- GROUP BY (AUTHOR_ID, PUBLISHED_IN) | 1 | 1948 | 1 | <- GROUP BY (AUTHOR_ID, PUBLISHED_IN) | 1 | NULL | 2 | <- GROUP BY (AUTHOR_ID) | 2 | 1988 | 1 | <- GROUP BY (AUTHOR_ID, PUBLISHED_IN) | 2 | 1990 | 1 | <- GROUP BY (AUTHOR_ID, PUBLISHED_IN) | 2 | NULL | 2 | <- GROUP BY (AUTHOR_ID) | NULL | NULL | 4 | <- GROUP BY () +-----------+--------------+----------+
CUBE() explained in SQL
CUBE()
is different from ROLLUP()
in the way that it doesn't just create N+1
groupings, it creates all 2^N
possible combinations between all group fields in the CUBE()
function argument list. Let's re-consider our second query from before:
-- CUBE() with two arguments SELECT AUTHOR_ID, PUBLISHED_IN, COUNT(*) FROM BOOK GROUP BY CUBE(AUTHOR_ID, PUBLISHED_IN)
-- The same query using UNION ALL: SELECT AUTHOR_ID, PUBLISHED_IN, COUNT(*) FROM BOOK GROUP BY AUTHOR_ID, PUBLISHED_IN UNION ALL SELECT AUTHOR_ID, NULL, COUNT(*) FROM BOOK GROUP BY AUTHOR_ID UNION ALL SELECT NULL, PUBLISHED_IN, COUNT(*) FROM BOOK GROUP BY PUBLISHED_IN UNION ALL SELECT NULL, NULL, COUNT(*) FROM BOOK GROUP BY () ORDER BY 1 NULLS FIRST, 2 NULLS FIRST
The results would then hold:
+-----------+--------------+----------+ | AUTHOR_ID | PUBLISHED_IN | COUNT(*) | +-----------+--------------+----------+ | NULL | NULL | 2 | <- GROUP BY () | NULL | 1945 | 1 | <- GROUP BY (PUBLISHED_IN) | NULL | 1948 | 1 | <- GROUP BY (PUBLISHED_IN) | NULL | 1988 | 1 | <- GROUP BY (PUBLISHED_IN) | NULL | 1990 | 1 | <- GROUP BY (PUBLISHED_IN) | 1 | NULL | 2 | <- GROUP BY (AUTHOR_ID) | 1 | 1945 | 1 | <- GROUP BY (AUTHOR_ID, PUBLISHED_IN) | 1 | 1948 | 1 | <- GROUP BY (AUTHOR_ID, PUBLISHED_IN) | 2 | NULL | 2 | <- GROUP BY (AUTHOR_ID) | 2 | 1988 | 1 | <- GROUP BY (AUTHOR_ID, PUBLISHED_IN) | 2 | 1990 | 1 | <- GROUP BY (AUTHOR_ID, PUBLISHED_IN) +-----------+--------------+----------+
GROUPING SETS()
GROUPING SETS()
are the generalised way to create multiple groupings. From our previous examples
-
ROLLUP(AUTHOR_ID, PUBLISHED_IN)
corresponds toGROUPING SETS((AUTHOR_ID, PUBLISHED_IN), (AUTHOR_ID), ())
-
CUBE(AUTHOR_ID, PUBLISHED_IN)
corresponds toGROUPING SETS((AUTHOR_ID, PUBLISHED_IN), (AUTHOR_ID), (PUBLISHED_IN), ())
This is nicely explained in the SQL Server manual pages about GROUPING SETS()
and other grouping functions:
http://msdn.microsoft.com/en-us/library/bb510427(v=sql.105)
jOOQ's support for ROLLUP(), CUBE(), GROUPING SETS()
jOOQ fully supports all of these functions, as well as the utility functions GROUPING()
and GROUPING_ID()
, used for identifying the grouping set ID of a record. The DSL API thus includes:
// The various grouping function constructors GroupField rollup(Field<?>... fields); GroupField cube(Field<?>... fields); GroupField groupingSets(Field<?>... fields); GroupField groupingSets(Field<?>[]... fields); GroupField groupingSets(Collection<? extends Field<?>>... fields); // The utility functions generating IDs per GROUPING SET Field<Integer> grouping(Field<?>); Field<Integer> groupingId(Field<?>...);
MySQL's and CUBRID's WITH ROLLUP syntax
MySQL and CUBRID don't know any grouping functions, but they support a WITH ROLLUP
clause, that is equivalent to simple ROLLUP()
grouping functions. jOOQ emulates ROLLUP()
in MySQL and CUBRID, by rendering this WITH ROLLUP
clause. The following two statements mean the same:
-- Statement 1: SQL standard GROUP BY ROLLUP(A, B, C) -- Statement 2: SQL standard GROUP BY A, ROLLUP(B, C)
-- Statement 1: MySQL GROUP BY A, B, C WITH ROLLUP -- Statement 2: MySQL -- This is not supported in MySQL
4.6.17. User-defined functions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Some databases support user-defined functions, which can be embedded in any SQL statement, if you're using jOOQ's code generator. Let's say you have the following simple function in Oracle SQL:
CREATE OR REPLACE FUNCTION echo (INPUT NUMBER) RETURN NUMBER IS BEGIN RETURN INPUT; END echo;
The above function will be made available from a generated Routines class. You can use it like any other column expression:
SELECT echo(1) FROM DUAL WHERE echo(2) = 2
create.select(echo(1)).where(echo(2).eq(2)).fetch();
Note that user-defined functions returning CURSOR or ARRAY data types can also be used wherever table expressions can be used, if they are unnested
4.6.18. User-defined aggregate functions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Some databases support user-defined aggregate functions, which can then be used along with GROUP BY clauses or as window functions. An example for such a database is Oracle. With Oracle, you can define the following OBJECT
type (the example was taken from the Oracle 11g documentation):
CREATE TYPE U_SECOND_MAX AS OBJECT ( MAX NUMBER, -- highest value seen so far SECMAX NUMBER, -- second highest value seen so far STATIC FUNCTION ODCIAggregateInitialize(sctx IN OUT U_SECOND_MAX) RETURN NUMBER, MEMBER FUNCTION ODCIAggregateIterate(self IN OUT U_SECOND_MAX, value IN NUMBER) RETURN NUMBER, MEMBER FUNCTION ODCIAggregateTerminate(self IN U_SECOND_MAX, returnValue OUT NUMBER, flags IN NUMBER) RETURN NUMBER, MEMBER FUNCTION ODCIAggregateMerge(self IN OUT U_SECOND_MAX, ctx2 IN U_SECOND_MAX) RETURN NUMBER ); CREATE OR REPLACE TYPE BODY U_SECOND_MAX IS STATIC FUNCTION ODCIAggregateInitialize(sctx IN OUT U_SECOND_MAX) RETURN NUMBER IS BEGIN SCTX := U_SECOND_MAX(0, 0); RETURN ODCIConst.Success; END; MEMBER FUNCTION ODCIAggregateIterate(self IN OUT U_SECOND_MAX, value IN NUMBER) RETURN NUMBER IS BEGIN IF VALUE > SELF.MAX THEN SELF.SECMAX := SELF.MAX; SELF.MAX := VALUE; ELSIF VALUE > SELF.SECMAX THEN SELF.SECMAX := VALUE; END IF; RETURN ODCIConst.Success; END; MEMBER FUNCTION ODCIAggregateTerminate(self IN U_SECOND_MAX, returnValue OUT NUMBER, flags IN NUMBER) RETURN NUMBER IS BEGIN RETURNVALUE := SELF.SECMAX; RETURN ODCIConst.Success; END; MEMBER FUNCTION ODCIAggregateMerge(self IN OUT U_SECOND_MAX, ctx2 IN U_SECOND_MAX) RETURN NUMBER IS BEGIN IF CTX2.MAX > SELF.MAX THEN IF CTX2.SECMAX > SELF.SECMAX THEN SELF.SECMAX := CTX2.SECMAX; ELSE SELF.SECMAX := SELF.MAX; END IF; SELF.MAX := CTX2.MAX; ELSIF CTX2.MAX > SELF.SECMAX THEN SELF.SECMAX := CTX2.MAX; END IF; RETURN ODCIConst.Success; END; END;
The above OBJECT
type is then available to function declarations as such:
CREATE FUNCTION SECOND_MAX (input NUMBER) RETURN NUMBER PARALLEL_ENABLE AGGREGATE USING U_SECOND_MAX;
Using the generated aggregate function
jOOQ's code generator will detect such aggregate functions and generate them differently from regular user-defined functions. They implement the org.jooq.AggregateFunction type, as mentioned in the manual's section about aggregate functions. Here's how you can use the SECOND_MAX()
aggregate function with jOOQ:
-- Get the second-latest publishing date by author SELECT SECOND_MAX(PUBLISHED_IN) FROM BOOK GROUP BY AUTHOR_ID
// Routines.secondMax() can be static-imported create.select(secondMax(BOOK.PUBLISHED_IN)) .from(BOOK) .groupBy(BOOK.AUTHOR_ID) .fetch();
4.6.19. The CASE expression
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The CASE
expression is part of the standard SQL syntax. While some RDBMS also offer an IF
expression, or a DECODE
function, you can always rely on the two types of CASE
syntax:
SELECT -- Searched case CASE WHEN AUTHOR.FIRST_NAME = 'Paulo' THEN 'brazilian' WHEN AUTHOR.FIRST_NAME = 'George' THEN 'english' ELSE 'unknown' END, -- Simple case CASE AUTHOR.FIRST_NAME WHEN 'Paulo' THEN 'brazilian' WHEN 'George' THEN 'english' ELSE 'unknown' END FROM AUTHOR
create.select( // Searched case when(AUTHOR.FIRST_NAME.eq("Paulo"), "brazilian") .when(AUTHOR.FIRST_NAME.eq("George"), "english") .otherwise("unknown"); // Simple case choose(AUTHOR.FIRST_NAME) .when("Paulo", "brazilian") .when("George", "english") .otherwise("unknown")) .from(AUTHOR) .fetch();
In jOOQ, both syntaxes are supported (The second one is emulated in Derby, which only knows the first one). Unfortunately, both case and else are reserved words in Java. jOOQ chose to use decode() from the Oracle DECODE
function, and otherwise(), which means the same as else.
A CASE
expression can be used anywhere where you can place a column expression (or Field). For instance, you can SELECT
the above expression, if you're selecting from AUTHOR
:
SELECT AUTHOR.FIRST_NAME, [... CASE EXPR ...] AS nationality FROM AUTHOR
Short forms of the CASE expression
The SQL standard and some vendors support a variety of short forms of the CASE
expression, usually in the form of functions. These include:
Sort indirection is often implemented with a CASE
clause of a SELECT
's ORDER BY
clause. See the manual's section about the ORDER BY clause for more details.
4.6.20. Sequences and serials
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Sequences implement the org.jooq.Sequence interface, providing essentially this functionality:
// Get a field for the CURRVAL sequence property Field<T> currval(); // Get a field for the NEXTVAL sequence property Field<T> nextval();
So if you have a sequence like this in Oracle:
CREATE SEQUENCE s_author_id
You can then use your generated sequence object directly in a SQL statement as such:
// Reference the sequence in a SELECT statement: Field<BigInteger> s = S_AUTHOR_ID.nextval(); BigInteger nextID = create.select(s).fetchOne(s); // Reference the sequence in an INSERT statement: create.insertInto(AUTHOR, AUTHOR.ID, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .values(S_AUTHOR_ID.nextval(), val("William"), val("Shakespeare")) .execute();
- For more information about generated sequences, refer to the manual's section about generated sequences
- For more information about executing standalone calls to sequences, refer to the manual's section about sequence execution
4.6.21. Tuples or row value expressions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
According to the SQL standard, row value expressions can have a degree of more than one. This is commonly used in the INSERT statement, where the VALUES
row value constructor allows for providing a row value expression as a source for INSERT
data. Row value expressions can appear in various other places, though. They are supported by jOOQ as records / rows. jOOQ's DSL allows for the construction of type-safe records up to the degree of 22. Higher-degree Rows are supported as well, but without any type-safety. Row types are modelled as follows:
// The DSL provides overloaded row value expression constructor methods: public static <T1> Row1<T1> row(T1 t1) { ... } public static <T1, T2> Row2<T1, T2> row(T1 t1, T2 t2) { ... } public static <T1, T2, T3> Row3<T1, T2, T3> row(T1 t1, T2 t2, T3 t3) { ... } public static <T1, T2, T3, T4> Row4<T1, T2, T3, T4> row(T1 t1, T2 t2, T3 t3, T4 t4) { ... } // [ ... idem for Row5, Row6, Row7, ..., Row22 ] // Degrees of more than 22 are supported without type-safety public static RowN row(Object... values) { ... }
Using row value expressions in predicates
Row value expressions are incompatible with most other QueryParts, but they can be used as a basis for constructing various conditional expressions, such as:
- comparison predicates
- NULL predicates
- BETWEEN predicates
- IN predicates
- OVERLAPS predicate (for degree 2 row value expressions only)
See the relevant sections for more details about how to use row value expressions in predicates.
Using row value expressions in UPDATE statements
The UPDATE statement also supports a variant where row value expressions are updated, rather than single columns. See the relevant section for more details
Higher-degree row value expressions
jOOQ chose to explicitly support degrees up to 22 to match Scala's typesafe tuple, function and product support. Unlike Scala, however, jOOQ also supports higher degrees without the additional typesafety.
4.7. Conditional expressions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Conditions or conditional expressions are widely used in SQL and in the jOOQ API. They can be used in
- The CASE expression
- The JOIN clause (or
JOIN .. ON
clause, to be precise) of a SELECT statement, UPDATE statement, DELETE statement - The WHERE clause of a SELECT statement, UPDATE statement, DELETE statement
- The CONNECT BY clause of a SELECT statement
- The HAVING clause of a SELECT statement
- The MERGE statement's ON clause
Boolean types in SQL
Before SQL:1999, boolean types did not really exist in SQL. They were modelled by 0 and 1 numeric/char values. With SQL:1999, true booleans were introduced and are now supported by most databases. In short, these are possible boolean values:
-
1
orTRUE
-
0
orFALSE
-
NULL
orUNKNOWN
It is important to know that SQL differs from many other languages in the way it interprets the NULL
boolean value. Most importantly, the following facts are to be remembered:
-
[ANY] = NULL
yieldsNULL
(notFALSE
) -
[ANY] != NULL
yieldsNULL
(notTRUE
) -
NULL = NULL
yieldsNULL
(notTRUE
) -
NULL != NULL
yieldsNULL
(notFALSE
)
For simplified NULL
handling, please refer to the section about the DISTINCT predicate.
Note that jOOQ does not model these values as actual column expression compatible.
4.7.1. Condition building
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
With jOOQ, most conditional expressions are built from column expressions, calling various methods on them. For instance, to build a comparison predicate, you can write the following expression:
TITLE = 'Animal Farm'
BOOK.TITLE.eq("Animal Farm")
Create conditions from the DSL
There are a few types of conditions, that can be created statically from the DSL. These are:
- plain SQL conditions, that allow you to phrase your own SQL string conditional expression
- The EXISTS predicate, a standalone predicate that creates a conditional expression
- Constant
TRUE
andFALSE
conditional expressions - Converting a BOOLEAN column to a condition
Connect conditions using boolean operators
Conditions can also be connected using boolean operators as will be discussed in a subsequent chapter.
4.7.2. BOOLEAN columns
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Some databases support the standard SQL BOOLEAN data type, which produces Field<Boolean>
column types in jOOQ's code generator. But even if your dialect doesn't support the BOOLEAN
type out of the box, you may have applied a data type rewrite to force a TINYINT(1)
or CHAR(1)
or NUMBER(1)
column to act as a BOOLEAN
.
When you have such a column, you will want to use it as a condition, and vice-versa. A org.jooq.Field<Boolean> can be turned into a org.jooq.Condition using DSL.condition(Field). The inverse operation can be achieved using DSL.field(Condition):
Condition condition = BOOK.TITLE.eq("Animal Farm"); Field<Boolean> field = field(condition); // Fetch boolean values from a table create.select(field).from(BOOK).fetch(); // Use a boolean field as a condition create.selectFrom(BOOK).where(field).fetch();
4.7.3. AND, OR, NOT boolean operators
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In SQL, as in most other languages, conditional expressions can be connected using the AND
and OR
binary operators, as well as the NOT
unary operator, to form new conditional expressions. In jOOQ, this is modelled as such:
(TITLE = 'Animal Farm' OR TITLE = '1984') AND NOT (AUTHOR.LAST_NAME = 'Orwell')
BOOK.TITLE.eq("Animal Farm").or(BOOK.TITLE.eq("1984")) .andNot(AUTHOR.LAST_NAME.eq("Orwell"))
The above example shows that the number of parentheses in Java can quickly explode. Proper indentation may become crucial in making such code readable. In order to understand how jOOQ composes combined conditional expressions, let's assign component expressions first:
Condition a = BOOK.TITLE.eq("Animal Farm"); Condition b = BOOK.TITLE.eq("1984"); Condition c = AUTHOR.LAST_NAME.eq("Orwell"); Condition combined1 = a.or(b); // These OR-connected conditions form a new condition, wrapped in parentheses Condition combined2 = combined1.andNot(c); // The left-hand side of the AND NOT () operator is already wrapped in parentheses
The Condition API
Here are all boolean operators on the org.jooq.Condition interface:
and(Condition) // Combine conditions with AND and(String) // Combine conditions with AND. Convenience for adding plain SQL to the right-hand side and(String, Object...) // Combine conditions with AND. Convenience for adding plain SQL to the right-hand side and(String, QueryPart...) // Combine conditions with AND. Convenience for adding plain SQL to the right-hand side andExists(Select<?>) // Combine conditions with AND. Convenience for adding an exists predicate to the rhs andNot(Condition) // Combine conditions with AND. Convenience for adding an inverted condition to the rhs andNotExists(Select<?>) // Combine conditions with AND. Convenience for adding an inverted exists predicate to the rhs or(Condition) // Combine conditions with OR or(String) // Combine conditions with OR. Convenience for adding plain SQL to the right-hand side or(String, Object...) // Combine conditions with OR. Convenience for adding plain SQL to the right-hand side or(String, QueryPart...) // Combine conditions with OR. Convenience for adding plain SQL to the right-hand side orExists(Select<?>) // Combine conditions with OR. Convenience for adding an exists predicate to the rhs orNot(Condition) // Combine conditions with OR. Convenience for adding an inverted condition to the rhs orNotExists(Select<?>) // Combine conditions with OR. Convenience for adding an inverted exists predicate to the rhs not() // Invert a condition (synonym for DSL.not(Condition)
4.7.4. Comparison predicate
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In SQL, comparison predicates are formed using common comparison operators:
- = to test for equality
- <> or != to test for non-equality
- > to test for being strictly greater
- >= to test for being greater or equal
- < to test for being strictly less
- <= to test for being less or equal
Unfortunately, Java does not support operator overloading, hence these operators are also implemented as methods in jOOQ, like any other SQL syntax elements. The relevant parts of the org.jooq.Field interface are these:
eq or equal(T); // = (some bind value) eq or equal(Field<T>); // = (some column expression) eq or equal(Select<? extends Record1<T>>); // = (some scalar SELECT statement) ne or notEqual(T); // <> (some bind value) ne or notEqual(Field<T>); // <> (some column expression) ne or notEqual(Select<? extends Record1<T>>); // <> (some scalar SELECT statement) lt or lessThan(T); // < (some bind value) lt or lessThan(Field<T>); // < (some column expression) lt or lessThan(Select<? extends Record1<T>>); // < (some scalar SELECT statement) le or lessOrEqual(T); // <= (some bind value) le or lessOrEqual(Field<T>); // <= (some column expression) le or lessOrEqual(Select<? extends Record1<T>>); // <= (some scalar SELECT statement) gt or greaterThan(T); // > (some bind value) gt or greaterThan(Field<T>); // > (some column expression) gt or greaterThan(Select<? extends Record1<T>>); // > (some scalar SELECT statement) ge or greaterOrEqual(T); // >= (some bind value) ge or greaterOrEqual(Field<T>); // >= (some column expression) ge or greaterOrEqual(Select<? extends Record1<T>>); // >= (some scalar SELECT statement)
Note that every operator is represented by two methods. A verbose one (such as equal()
) and a two-character one (such as eq()
). Both methods are the same. You may choose either one, depending on your taste. The manual will always use the more verbose one.
jOOQ's convenience methods using comparison operators
In addition to the above, jOOQ provides a few convenience methods for common operations performed on strings using comparison predicates:
LOWER(TITLE) = LOWER('animal farm')
BOOK.TITLE.equalIgnoreCase("animal farm")
4.7.5. Boolean operator precedence
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
As previously mentioned in the manual's section about arithmetic expressions, jOOQ does not implement operator precedence. All operators are evaluated from left to right, as expected in an object-oriented API. This is important to understand when combining boolean operators, such as AND
, OR
, and NOT
. The following expressions are equivalent:
A.and(B) .or(C) .and(D) .or(E) (((A.and(B)).or(C)).and(D)).or(E)
In SQL, the two expressions wouldn't be the same, as SQL natively knows operator precedence.
A AND B OR C AND D OR E -- Precedence is applied (((A AND B) OR C) AND D) OR E -- Precedence is overridden
4.7.6. Comparison predicate (degree > 1)
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
All variants of the comparison predicate that we've seen in the previous chapter also work for row value expressions. If your database does not support row value expression comparison predicates, jOOQ emulates them the way they are defined in the SQL standard:
-- Row value expressions (equal) (A, B) = (X, Y) (A, B, C) = (X, Y, Z) -- greater than (A, B) > (X, Y) (A, B, C) > (X, Y, Z) -- greater or equal (A, B) >= (X, Y) (A, B, C) >= (X, Y, Z) -- Inverse comparisons (A, B) <> (X, Y) (A, B) < (X, Y) (A, B) <= (X, Y)
-- Equivalent factored-out predicates (equal) (A = X) AND (B = Y) (A = X) AND (B = Y) AND (C = Z) -- greater than (A > X) OR ((A = X) AND (B > Y)) (A > X) OR ((A = X) AND (B > Y)) OR ((A = X) AND (B = Y) AND (C > Z)) -- greater or equal (A > X) OR ((A = X) AND (B > Y)) OR ((A = X) AND (B = Y)) (A > X) OR ((A = X) AND (B > Y)) OR ((A = X) AND (B = Y) AND (C > Z)) OR ((A = X) AND (B = Y) AND (C = Z)) -- For simplicity, these predicates are shown in terms -- of their negated counter parts NOT((A, B) = (X, Y)) NOT((A, B) >= (X, Y)) NOT((A, B) > (X, Y))
jOOQ supports all of the above row value expression comparison predicates, both with column expression lists and scalar subselects at the right-hand side:
-- With regular column expressions (BOOK.AUTHOR_ID, BOOK.TITLE) = (1, 'Animal Farm') -- With scalar subselects (BOOK.AUTHOR_ID, BOOK.TITLE) = ( SELECT PERSON.ID, 'Animal Farm' FROM PERSON WHERE PERSON.ID = 1 )
// Column expressions row(BOOK.AUTHOR_ID, BOOK.TITLE).eq(1, "Animal Farm"); // Subselects row(BOOK.AUTHOR_ID, BOOK.TITLE).eq( select(PERSON.ID, val("Animal Farm")) .from(PERSON) .where(PERSON.ID.eq(1)) );
4.7.7. Quantified comparison predicate
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
If the right-hand side of a comparison predicate turns out to be a non-scalar table subquery, you can wrap that subquery in a quantifier, such as ALL
, ANY
, or SOME
. Note that the SQL standard defines ANY
and SOME
to be equivalent. jOOQ settled for the more intuitive ANY
and doesn't support SOME
. Here are some examples, supported by jOOQ:
TITLE = ANY('Animal Farm', '1982') PUBLISHED_IN > ALL(1920, 1940)
BOOK.TITLE.eq(any("Animal Farm", "1982")); BOOK.PUBLISHED_IN.gt(all(1920, 1940));
For the example, the right-hand side of the quantified comparison predicates were filled with argument lists. But it is easy to imagine that the source of values results from a subselect.
ANY and the IN predicate
It is interesting to note that the SQL standard defines the IN predicate in terms of the ANY
-quantified predicate. The following two expressions are equivalent:
[ROW VALUE EXPRESSION] IN [IN PREDICATE VALUE]
[ROW VALUE EXPRESSION] = ANY [IN PREDICATE VALUE]
Typically, the IN predicate is more readable than the quantified comparison predicate.
4.7.8. NULL predicate
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In SQL, you cannot compare NULL
with any value using comparison predicates, as the result would yield NULL
again, which is neither TRUE
nor FALSE
(see also the manual's section about conditional expressions). In order to test a column expression for NULL
, use the NULL
predicate as such:
TITLE IS NULL TITLE IS NOT NULL
BOOK.TITLE.isNull() BOOK.TITLE.isNotNull()
4.7.9. NULL predicate (degree > 1)
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SQL NULL
predicate also works well for row value expressions, although it has some subtle, counter-intuitive features when it comes to inversing predicates with the NOT()
operator! Here are some examples:
-- Row value expressions (A, B) IS NULL (A, B) IS NOT NULL -- Inverse of the above NOT((A, B) IS NULL) NOT((A, B) IS NOT NULL)
-- Equivalent factored-out predicates (A IS NULL) AND (B IS NULL) (A IS NOT NULL) AND (B IS NOT NULL) -- Inverse (A IS NOT NULL) OR (B IS NOT NULL) (A IS NULL) OR (B IS NULL)
The SQL standard contains a nice truth table for the above rules:
+-----------------------+-----------+---------------+---------------+-------------------+ | Expression | R IS NULL | R IS NOT NULL | NOT R IS NULL | NOT R IS NOT NULL | +-----------------------+-----------+---------------+---------------+-------------------+ | degree 1: null | true | false | false | true | | degree 1: not null | false | true | true | false | | degree > 1: all null | true | false | false | true | | degree > 1: some null | false | false | true | true | | degree > 1: none null | false | true | true | false | +-----------------------+-----------+---------------+---------------+-------------------+
In jOOQ, you would simply use the isNull()
and isNotNull()
methods on row value expressions. Again, as with the row value expression comparison predicate, the row value expression NULL
predicate is emulated by jOOQ, if your database does not natively support it:
row(BOOK.ID, BOOK.TITLE).isNull(); row(BOOK.ID, BOOK.TITLE).isNotNull();
4.7.10. DISTINCT predicate
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Some databases support the DISTINCT
predicate, which serves as a convenient, NULL
-safe comparison predicate. With the DISTINCT
predicate, the following truth table can be assumed:
-
[ANY] IS DISTINCT FROM NULL
yieldsTRUE
-
[ANY] IS NOT DISTINCT FROM NULL
yieldsFALSE
-
NULL IS DISTINCT FROM NULL
yieldsFALSE
-
NULL IS NOT DISTINCT FROM NULL
yieldsTRUE
For instance, you can compare two fields for distinctness, ignoring the fact that any of the two could be NULL
, which would lead to funny results. This is supported by jOOQ as such:
TITLE IS DISTINCT FROM SUB_TITLE TITLE IS NOT DISTINCT FROM SUB_TITLE
BOOK.TITLE.isDistinctFrom(BOOK.SUB_TITLE) BOOK.TITLE.isNotDistinctFrom(BOOK.SUB_TITLE)
If your database does not natively support the DISTINCT
predicate, jOOQ emulates it with an equivalent CASE expression, modelling the above truth table:
-- A IS DISTINCT FROM B CASE WHEN A IS NULL AND B IS NULL THEN FALSE WHEN A IS NULL AND B IS NOT NULL THEN TRUE WHEN A IS NOT NULL AND B IS NULL THEN TRUE WHEN A = B THEN FALSE ELSE TRUE END
-- A IS NOT DISTINCT FROM B CASE WHEN A IS NULL AND B IS NULL THEN TRUE WHEN A IS NULL AND B IS NOT NULL THEN FALSE WHEN A IS NOT NULL AND B IS NULL THEN FALSE WHEN A = B THEN TRUE ELSE FALSE END
... or better, if the INTERSECT set operation is supported:
-- A IS DISTINCT FROM B NOT EXISTS(SELECT A INTERSECT SELECT B)
-- A IS NOT DISTINCT FROM B EXISTS(SELECT a INTERSECT SELECT b)
4.7.11. BETWEEN predicate
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The BETWEEN
predicate can be seen as syntactic sugar for a pair of comparison predicates. According to the SQL standard, the following two predicates are equivalent:
A BETWEEN B AND C
A >= B AND A <= C
Note the inclusiveness of range boundaries in the definition of the BETWEEN
predicate. Intuitively, this is supported in jOOQ as such:
PUBLISHED_IN BETWEEN 1920 AND 1940 PUBLISHED_IN NOT BETWEEN 1920 AND 1940
BOOK.PUBLISHED_IN.between(1920).and(1940) BOOK.PUBLISHED_IN.notBetween(1920).and(1940)
BETWEEN SYMMETRIC
The SQL standard defines the SYMMETRIC
keyword to be used along with BETWEEN
to indicate that you do not care which bound of the range is larger than the other. A database system should simply swap range bounds, in case the first bound is greater than the second one. jOOQ supports this keyword as well, emulating it if necessary.
PUBLISHED_IN BETWEEN SYMMETRIC 1940 AND 1920 PUBLISHED_IN NOT BETWEEN SYMMETRIC 1940 AND 1920
BOOK.PUBLISHED_IN.betweenSymmetric(1940).and(1920) BOOK.PUBLISHED_IN.notBetweenSymmetric(1940).and(1920)
The emulation is done trivially:
A BETWEEN SYMMETRIC B AND C
(A BETWEEN B AND C) OR (A BETWEEN C AND B)
4.7.12. BETWEEN predicate (degree > 1)
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SQL BETWEEN
predicate also works well for row value expressions. Much like the BETWEEN predicate for degree 1, it is defined in terms of a pair of regular comparison predicates:
A BETWEEN B AND C A BETWEEN SYMMETRIC B AND C
A >= B AND A <= C (A >= B AND A <= C) OR (A >= C AND A <= B)
The above can be factored out according to the rules listed in the manual's section about row value expression comparison predicates.
jOOQ supports the BETWEEN [SYMMETRIC]
predicate and emulates it in all SQL dialects where necessary. An example is given here:
row(BOOK.ID, BOOK.TITLE).between(1, "A").and(10, "Z");
4.7.13. LIKE predicate
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
LIKE
predicates are popular for simple wildcard-enabled pattern matching. Supported wildcards in all SQL databases are:
- _: (single-character wildcard)
- %: (multi-character wildcard)
With jOOQ, the LIKE
predicate can be created from any column expression as such:
TITLE LIKE '%abc%' TITLE NOT LIKE '%abc%'
BOOK.TITLE.like("%abc%") BOOK.TITLE.notLike("%abc%")
Concatenating wildcards
A common practice is to conatenate wildcards to the actual expression. While concatenation is dangerous in plain SQL, it is safe when creating dynamic bind values using the DSL API:
-- Generated SQL is using a bind variable TITLE LIKE '%abc%' TITLE NOT LIKE '%abc%'
// abc might be user input BOOK.TITLE.like("%" + abc "%") BOOK.TITLE.notLike("%" + abc + "%")
Escaping operands with the LIKE predicate
Often, your pattern may contain any of the wildcard characters "_"
and "%"
, in case of which you may want to escape them. jOOQ does not automatically escape patterns in like()
and notLike()
methods. Instead, you can explicitly define an escape character as such:
TITLE LIKE '%The !%-Sign Book%' ESCAPE '!' TITLE NOT LIKE '%The !%-Sign Book%' ESCAPE '!'
BOOK.TITLE.like("%The !%-Sign Book%", '!') BOOK.TITLE.notLike("%The !%-Sign Book%", '!')
In the above predicate expressions, the exclamation mark character is passed as the escape character to escape wildcard characters "!_"
and "!%"
, as well as to escape the escape character itself: "!!"
Please refer to your database manual for more details about escaping patterns with the LIKE
predicate.
jOOQ's convenience methods using the LIKE predicate
In addition to the above, jOOQ provides a few convenience methods for common operations performed on strings using the LIKE
predicate. Typical operations are "contains predicates", "starts with predicates", "ends with predicates", etc. Here is the full convenience API wrapping LIKE
predicates:
-- case insensitivity LOWER(TITLE) LIKE LOWER('%abc%') LOWER(TITLE) NOT LIKE LOWER('%abc%') -- contains and similar methods TITLE LIKE '%' || 'abc' || '%' TITLE LIKE 'abc' || '%' TITLE LIKE '%' || 'abc'
// case insensitivity BOOK.TITLE.likeIgnoreCase("%abc%") BOOK.TITLE.notLikeIgnoreCase("%abc%") // contains and similar methods BOOK.TITLE.contains("abc") BOOK.TITLE.startsWith("abc") BOOK.TITLE.endsWith("abc")
Note, that jOOQ escapes %
and _
characters in value in some of the above predicate implementations. For simplicity, this has been omitted in this manual.
4.7.14. IN predicate
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In SQL, apart from comparing a value against several values, the IN
predicate can be used to create semi-joins or anti-joins. jOOQ knows the following methods on the org.jooq.Field interface, to construct such IN
predicates:
in(Collection<?>) // Construct an IN predicate from a collection of bind values in(T...) // Construct an IN predicate from bind values in(Field<?>...) // Construct an IN predicate from column expressions in(Select<? extends Record1<T>>) // Construct an IN predicate from a subselect notIn(Collection<?>) // Construct a NOT IN predicate from a collection of bind values notIn(T...) // Construct a NOT IN predicate from bind values notIn(Field<?>...) // Construct a NOT IN predicate from column expressions notIn(Select<? extends Record1<T>>) // Construct a NOT IN predicate from a subselect
A sample IN
predicate might look like this:
TITLE IN ('Animal Farm', '1984') TITLE NOT IN ('Animal Farm', '1984')
BOOK.TITLE.in("Animal Farm", "1984") BOOK.TITLE.notIn("Animal Farm", "1984")
NOT IN and NULL values
Beware that you should probably not have any NULL
values in the right hand side of a NOT IN
predicate, as the whole expression would evaluate to NULL
, which is rarely desired. This can be shown informally using the following reasoning:
-- The following conditional expressions are formally or informally equivalent A NOT IN (B, C) A != ANY(B, C) A != B AND A != C -- Substitute C for NULL, you'll get A NOT IN (B, NULL) -- Substitute C for NULL A != B AND A != NULL -- From the above rules A != B AND NULL -- [ANY] != NULL yields NULL NULL -- [ANY] AND NULL yields NULL
A good way to prevent this from happening is to use the EXISTS predicate for anti-joins, which is NULL
-value insensitive. See the manual's section about conditional expressions to see a boolean truth table.
4.7.15. IN predicate (degree > 1)
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The SQL IN
predicate also works well for row value expressions. Much like the IN predicate for degree 1, it is defined in terms of a quantified comparison predicate. The two expressions are equivalent:
R IN [IN predicate value]
R = ANY [IN predicate value]
jOOQ supports the IN
predicate with row value expressions.An example is given here:
-- Using an IN list (BOOK.ID, BOOK.TITLE) IN ((1, 'A'), (2, 'B')) -- Using a subselect (BOOK.ID, BOOK.TITLE) IN ( SELECT T.ID, T.TITLE FROM T )
// Using an IN list row(BOOK.ID, BOOK.TITLE).in(row(1, "A"), row(2, "B")); // Using a subselect row(BOOK.ID, BOOK.TITLE).in( select(T.ID, T.TITLE) .from(T) );
In both cases, i.e. when using an IN
list or when using a subselect, the type of the predicate is checked. Both sides of the predicate must be of equal degree and row type.
Emulation of the IN
predicate where row value expressions aren't well supported is currently only available for IN
predicates that do not take a subselect as an IN
predicate value.
4.7.16. EXISTS predicate
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Slightly less intuitive, yet more powerful than the previously discussed IN predicate is the EXISTS
predicate, that can be used to form semi-joins or anti-joins. With jOOQ, the EXISTS
predicate can be formed in various ways:
- From the DSL, using static methods. This is probably the most used case
- From a conditional expression using convenience methods attached to boolean operators
- From a SELECT statement using convenience methods attached to the where clause, and from other clauses
An example of an EXISTS
predicate can be seen here:
EXISTS (SELECT 1 FROM BOOK WHERE AUTHOR_ID = 3) NOT EXISTS (SELECT 1 FROM BOOK WHERE AUTHOR_ID = 3)
exists(create.selectOne().from(BOOK) .where(BOOK.AUTHOR_ID.eq(3))); notExists(create.selectOne().from(BOOK) .where(BOOK.AUTHOR_ID.eq(3)));
Note that in SQL, the projection of a subselect in an EXISTS
predicate is irrelevant. To help you write queries like the above, you can use jOOQ's selectZero() or selectOne() DSL methods
Performance of IN vs. EXISTS
In theory, the two types of predicates can perform equally well. If your database system ships with a sophisticated cost-based optimiser, it will be able to transform one predicate into the other, if you have all necessary constraints set (e.g. referential constraints, not null constraints). However, in reality, performance between the two might differ substantially. An interesting blog post investigating this topic on the MySQL database can be seen here:
http://blog.jooq.org/2012/07/27/not-in-vs-not-exists-vs-left-join-is-null-mysql/
4.7.17. OVERLAPS predicate
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
When comparing dates, the SQL standard allows for using a special OVERLAPS
predicate, which checks whether two date ranges overlap each other. The following can be said:
-- This yields true (DATE '2010-01-01', DATE '2010-01-03') OVERLAPS (DATE '2010-01-02' DATE '2010-01-04') -- INTERVAL data types are also supported. This is equivalent to the above (DATE '2010-01-01', CAST('+2 00:00:00' AS INTERVAL DAY TO SECOND)) OVERLAPS (DATE '2010-01-02', CAST('+2 00:00:00' AS INTERVAL DAY TO SECOND))
The OVERLAPS predicate in jOOQ
jOOQ supports the OVERLAPS
predicate on row value expressions of degree 2. The following methods are contained in org.jooq.Row2:
Condition overlaps(T1 t1, T2 t2); Condition overlaps(Field<T1> t1, Field<T2> t2); Condition overlaps(Row2<T1, T2> row);
This allows for expressing the above predicates as such:
// The date range tuples version row(Date.valueOf('2010-01-01'), Date.valueOf('2010-01-03')).overlaps(Date.valueOf('2010-01-02'), Date.valueOf('2010-01-04')) // The INTERVAL tuples version row(Date.valueOf('2010-01-01'), new DayToSecond(2)).overlaps(Date.valueOf('2010-01-02'), new DayToSecond(2))
jOOQ's extensions to the standard
Unlike the standard (or any database implementing the standard), jOOQ also supports the OVERLAPS
predicate for comparing arbitrary row vlaue expressions of degree 2. For instance, (1, 3) OVERLAPS (2, 4)
will yield true in jOOQ. This is emulated as such
-- This predicate (A, B) OVERLAPS (C, D) -- can be emulated as such (C <= B) AND (A <= D)
4.8. Synthetic SQL clauses
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Most of the previously mentioned SQL clauses have a native representation in at least one of jOOQ's supported SQL dialects. For example, when a function like LPAD()
is unavailable, jOOQ produces an equivalent expression for it:
-- MySQL (native support) lpad('a', 10, ' ') -- SQL Server (emulation) (replicate(' ', (10 - len('a'))) + 'a')
// In Java lpad("a", 10, " ")
However, since a lot of SQL is emulated for dialect compatibility, nothing prevents jOOQ from supporting synthetic SQL clauses that do not have any native representation anywhere. An example for this is the quantified like predicate, introduced in jOOQ 3.12 (yes, you should upgrade!), which would be really useful in any database:
(TITLE LIKE '%abc%' OR TITLE LIKE '%def%') (TITLE NOT LIKE '%abc%' OR TITLE NOT LIKE '%def%') (TITLE LIKE '%abc%' AND TITLE LIKE '%def%') (TITLE NOT LIKE '%abc%' AND TITLE NOT LIKE '%def%')
BOOK.TITLE.like(any("%abc%", "%def%")) BOOK.TITLE.notLike(any("%abc%", "%def%")) BOOK.TITLE.like(all("%abc%", "%def%")) BOOK.TITLE.notLike(all("%abc%", "%def%"))
In this section, we briefly list most such synthetic SQL clauses, which are available both through the jOOQ API, yet they do not have a native representation in any dialect.
- Relational Division: Relational algebra supports a divison operator, which is the inverse operator of the cross product.
- SEEK clause: The SEEK clause is a synthetic clause of the SELECT statement, which provides an alternative way of paginating other than the OFFSET clause. From a performance perspective, it is generally the preferred way to paginate.
- Sort indirection: When sorting, sometimes, we want to sort by a derived value, not the actual value of a column. Sort indirection makes this very easy with jOOQ.
4.9. Dynamic SQL
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In most cases, table expressions, column expressions, and conditional expressions as introduced in the previous chapters will be embedded into different SQL statement clauses as if the statement were a static SQL statement (e.g. in a view or stored procedure):
create.select( AUTHOR.FIRST_NAME.concat(AUTHOR.LAST_NAME), count() .from(AUTHOR) .join(BOOK).on(AUTHOR.ID.eq(BOOK.AUTHOR_ID)) .groupBy(AUTHOR.ID, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .orderBy(count().desc()) .fetch();
It is, however, interesting to think of all of the above expressions as what they are: expressions. And as such, nothing keeps users from extracting expressions and referencing them from outside the statement. The following statement is exactly equivalent:
SelectField<?>[] select = { AUTHOR.FIRST_NAME.concat(AUTHOR.LAST_NAME), count() }; Table<?> from = AUTHOR.join(BOOK).on(AUTHOR.ID.eq(BOOK.AUTHOR_ID)); GroupField[] groupBy = { AUTHOR.ID, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME }; SortField<?>[] orderBy = { count().desc() }; create.select(select) .from(from) .groupBy(groupBy) .orderBy(orderBy) .fetch();
Each individual expression, and collection of expressions can be seen as an independent entity that can be
- Constructed dynamically
- Reused across queries
Dynamic construction is particularly useful in the case of the WHERE clause, for dynamic predicate building. For instance:
public Condition condition(HttpServletRequest request) { Condition result = trueCondition(); if (request.getParameter("title") != null) result = result.and(BOOK.TITLE.like("%" + request.getParameter("title") + "%")); if (request.getParameter("author") != null) result = result.and(BOOK.AUTHOR_ID.in( selectOne().from(AUTHOR).where( AUTHOR.FIRST_NAME.like("%" + request.getParameter("author") + "%") .or(AUTHOR.LAST_NAME .like("%" + request.getParameter("author") + "%")) ) )); return result; } // And then: create.select() .from(BOOK) .where(condition(httpRequest)) .fetch();
The dynamic SQL building power may be one of the biggest advantages of using a runtime query model like the one offered by jOOQ. Queries can be created dynamically, of arbitrary complexity. In the above example, we've just constructed a dynamic WHERE clause. The same can be done for any other clauses, including dynamic FROM clauses (dynamic JOINs), or adding additional WITH clauses as needed.
4.10. Plain SQL
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
A DSL is a nice thing to have, it feels "fluent" and "natural", especially if it models a well-known language, such as SQL. But a DSL is always expressed in a host language (Java in this case), which was not made for exactly the same purposes as its hosted DSL. If it were, then jOOQ would be implemented on a compiler-level, similar to LINQ in .NET. But it's not, and so, the DSL is limited by language constraints of its host language. We have seen many functionalities where the DSL becomes a bit verbose. This can be especially true for:
You'll probably find other examples. If verbosity scares you off, don't worry. The verbose use-cases for jOOQ are rather rare, and when they come up, you do have an option. Just write SQL the way you're used to!
jOOQ allows you to embed SQL as a String into any supported statement in these contexts:
- Plain SQL as a conditional expression
- Plain SQL as a column expression
- Plain SQL as a function
- Plain SQL as a table expression
- Plain SQL as a query
The DSL plain SQL API
Plain SQL API methods are usually overloaded in three ways. Let's look at the condition
query part constructor:
// Construct a condition without bind values // Example: condition("a = b") Condition condition(String sql); // Construct a condition with bind values // Example: condition("a = ?", 1); Condition condition(String sql, Object... bindings); // Construct a condition taking other jOOQ object arguments // Example: condition("a = {0}", val(1)); Condition condition(String sql, QueryPart... parts);
Both the bind value and the query part argument overloads make use of jOOQ's plain SQL templating language.
Please refer to the org.jooq.impl.DSL Javadoc for more details. The following is a more complete listing of plain SQL construction methods from the DSL:
// A condition Condition condition(String sql); Condition condition(String sql, Object... bindings); Condition condition(String sql, QueryPart... parts); // A field with an unknown data type Field<Object> field(String sql); Field<Object> field(String sql, Object... bindings); Field<Object> field(String sql, QueryPart... parts); // A field with a known data type <T> Field<T> field(String sql, Class<T> type); <T> Field<T> field(String sql, Class<T> type, Object... bindings); <T> Field<T> field(String sql, Class<T> type, QueryPart... parts); <T> Field<T> field(String sql, DataType<T> type); <T> Field<T> field(String sql, DataType<T> type, Object... bindings); <T> Field<T> field(String sql, DataType<T> type, QueryPart... parts); // A field with a known name (properly escaped) Field<Object> fieldByName(String... fieldName); <T> Field<T> fieldByName(Class<T> type, String... fieldName); <T> Field<T> fieldByName(DataType<T> type, String... fieldName) // A function <T> Field<T> function(String name, Class<T> type, Field<?>... arguments); <T> Field<T> function(String name, DataType<T> type, Field<?>... arguments); // A table Table<?> table(String sql); Table<?> table(String sql, Object... bindings); Table<?> table(String sql, QueryPart... parts); // A table with a known name (properly escaped) Table<Record> tableByName(String... fieldName); // A query without results (update, insert, etc) Query query(String sql); Query query(String sql, Object... bindings); Query query(String sql, QueryPart... parts); // A query with results ResultQuery<Record> resultQuery(String sql); ResultQuery<Record> resultQuery(String sql, Object... bindings); ResultQuery<Record> resultQuery(String sql, QueryPart... parts); // A query with results. This is the same as resultQuery(...).fetch(); Result<Record> fetch(String sql); Result<Record> fetch(String sql, Object... bindings); Result<Record> fetch(String sql, QueryPart... parts);
Apart from the general factory methods, plain SQL is also available in various other contexts. For instance, when adding a .where("a = b")
clause to a query. Hence, there exist several convenience methods where plain SQL can be inserted usefully. This is an example displaying all various use-cases in one single query:
// You can use your table aliases in plain SQL fields // As long as that will produce syntactically correct SQL Field<?> LAST_NAME = field("a.LAST_NAME"); // You can alias your plain SQL fields Field<?> COUNT1 = field("count(*) x"); // If you know a reasonable Java type for your field, you // can also provide jOOQ with that type Field<Integer> COUNT2 = field("count(*) y", Integer.class); // Use plain SQL as select fields create.select(LAST_NAME, COUNT1, COUNT2) // Use plain SQL as aliased tables (be aware of syntax!) .from("author a") .join("book b") // Use plain SQL for conditions both in JOIN and WHERE clauses .on("a.id = b.author_id") // Bind a variable in plain SQL .where("b.title != ?", "Brida") // Use plain SQL again as fields in GROUP BY and ORDER BY clauses .groupBy(LAST_NAME) .orderBy(LAST_NAME) .fetch();
Important things to note about plain SQL!
There are some important things to keep in mind when using plain SQL:
- jOOQ doesn't know what you're doing. You're on your own again!
- You have to provide something that will be syntactically correct. If it's not, then jOOQ won't know. Only your JDBC driver or your RDBMS will detect the syntax error.
- You have to provide consistency when you use variable binding. The number of ? must match the number of variables
- Your SQL is inserted into jOOQ queries without further checks. Hence, jOOQ can't prevent SQL injection.
4.11. Plain SQL Templating Language
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The plain SQL API, as documented in the previous chapter, supports a string templating mini-language that allows for constructing complex SQL string content from smaller parts. A simple example can be seen below, e.g. when looking for support for one of PostgreSQL's various vendor-specific operator types:
ARRAY[1,4,3] && ARRAY[2,1]
condition("{0} && {1}", array1, array2);
Such a plain SQL template always consists of two things:
- The SQL string fragment
- A set of org.jooq.QueryPart arguments, which are expected to be embedded in the SQL string
The SQL string may reference the arguments by 0-based indexing. Each argument may be referenced several times. For instance, SQLite's emulation of the REPEAT(string, count)
function may look like this:
Field<Integer> count = val(3); Field<String> string = val("abc"); field("replace(substr(quote(zeroblob(({0} + 1) / 2)), 3, {0}), '0', {1})", String.class, count, string); // ^ ^ ^ ^^^^^ ^^^^^^ // | | | | | // argument "count" is repeated twice: \------------------+----------|---------------------/ | // argument "string" is used only once: \-----------------------------/
Parsing rules
When processing these plain SQL templates, a mini parser is run that handles things like
- String literals
- Quoted names
- Comments
- JDBC escape sequences
The above are recognised by the templating engine and contents inside of them are ignored when replacing numbered placeholders and/or bind variables. For instance:
query( "SELECT /* In a comment, this is not a placeholder: {0}. And this is not a bind variable: ? */ title AS `title {1} ?` " + "-- Another comment without placeholders: {2} nor bind variables: ?" + "FROM book " + "WHERE title = 'In a string literal, this is not a placeholder: {3}. And this is not a bind variable: ?'" );
The above query does not contain any numbered placeholders nor bind variables, because the tokens that would otherwise be searched for are contained inside of comments, string literals, or quoted names.
4.12. Names and identifiers
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Various SQL objects columns or tables can be referenced using names (often also called identifiers). SQL dialects differ in the way they understand names, syntactically. The differences include:
- The permitted characters to be used in "unquoted" names
- The permitted characters to be used in "quoted" names
- The name quoting characters (e.g.
"double quotes"
,`backticks`
, or[brackets]
) - The standard case for case-insensitive ("unquoted") names
For the above reasons, and also to prevent an additional SQL injection risk where names might contain SQL code, jOOQ by default quotes all names in generated SQL to be sure they match what is really contained in your database. This means that the following names will be rendered
-- Unquoted name AUTHOR.TITLE -- MariaDB, MySQL `AUTHOR`.`TITLE` -- MS Access, SQL Server, Sybase ASE, Sybase SQL Anywhere [AUTHOR].[TITLE] -- All the others, including the SQL standard "AUTHOR"."TITLE"
Note that you can influence jOOQ's name rendering behaviour through custom settings, if you prefer another name style to be applied.
Creating custom names
Custom, qualified or unqualified names can be created very easily using the DSL.name() constructor:
// Unqualified name Name name = name("TITLE"); // Qualified name Name name = name("AUTHOR", "TITLE");
Such names can be used as standalone QueryParts, or as DSL entry point for SQL expressions, like
- Common table expressions to be used with the WITH clause
- Window specifications to be used with the WINDOW clause
More details about how to use names / identifiers to construct such expressions can be found in the relevant sections of the manual.
4.13. Bind values and parameters
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Bind values are used in SQL / JDBC for various reasons. Among the most obvious ones are:
- Protection against SQL injection. Instead of inlining values possibly originating from user input, you bind those values to your prepared statement and let the JDBC driver / database take care of handling security aspects.
- Increased speed. Advanced databases such as Oracle can keep execution plans of similar queries in a dedicated cache to prevent hard-parsing your query again and again. In many cases, the actual value of a bind variable does not influence the execution plan, hence it can be reused. Preparing a statement will thus be faster
- On a JDBC level, you can also reuse the SQL string and prepared statement object instead of constructing it again, as you can bind new values to the prepared statement. jOOQ currently does not cache prepared statements, internally.
The following sections explain how you can introduce bind values in jOOQ, and how you can control the way they are rendered and bound to SQL.
4.13.1. Indexed parameters
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
JDBC only knows indexed bind values. A typical example for using bind values with JDBC is this:
try (PreparedStatement stmt = connection.prepareStatement("SELECT * FROM BOOK WHERE ID = ? AND TITLE = ?")) { // bind values to the above statement for appropriate indexes stmt.setInt(1, 5); stmt.setString(2, "Animal Farm"); stmt.executeQuery(); }
With dynamic SQL, keeping track of the number of question marks and their corresponding index may turn out to be hard. jOOQ abstracts this and lets you provide the bind value right where it is needed. A trivial example is this:
create.select().from(BOOK).where(BOOK.ID.eq(5)).and(BOOK.TITLE.eq("Animal Farm")).fetch(); // This notation is in fact a short form for the equivalent: create.select().from(BOOK).where(BOOK.ID.eq(val(5))).and(BOOK.TITLE.eq(val("Animal Farm"))).fetch();
Note the using of DSL.val() to explicitly create an indexed bind value. You don't have to worry about that index. When the query is rendered, each bind value will render a question mark. When the query binds its variables, each bind value will generate the appropriate bind value index.
Extract bind values from a query
Should you decide to run the above query outside of jOOQ, using your own java.sql.PreparedStatement, you can do so as follows:
Select<?> select = create.select().from(BOOK).where(BOOK.ID.eq(5)).and(BOOK.TITLE.eq("Animal Farm")); // Render the SQL statement: String sql = select.getSQL(); assertEquals("SELECT * FROM BOOK WHERE ID = ? AND TITLE = ?", sql); // Get the bind values: List<Object> values = select.getBindValues(); assertEquals(2, values.size()); assertEquals(5, values.get(0)); assertEquals("Animal Farm", values.get(1));
For more details about jOOQ's internals, see the manual's section about QueryParts.
4.13.2. Named parameters
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Some SQL access abstractions that are built on top of JDBC, or some that bypass JDBC may support named parameters. jOOQ allows you to give names to your parameters as well, although those names are not rendered to SQL strings by default. Here is an example of how to create named parameters using the org.jooq.Param type:
// Create a query with a named parameter. You can then use that name for accessing the parameter again Query query1 = create.select().from(AUTHOR).where(LAST_NAME.eq(param("lastName", "Poe"))); Param<?> param1 = query.getParam("lastName"); // Or, keep a reference to the typed parameter in order not to lose the <T> type information: Param<String> param2 = param("lastName", "Poe"); Query query2 = create.select().from(AUTHOR).where(LAST_NAME.eq(param2));
The org.jooq.Query interface also allows for setting new bind values directly, without accessing the Param type:
Query query1 = create.select().from(AUTHOR).where(LAST_NAME.eq("Poe")); query1.bind(1, "Orwell"); // Or, with named parameters Query query2 = create.select().from(AUTHOR).where(LAST_NAME.eq(param("lastName", "Poe"))); query2.bind("lastName", "Orwell");
In order to actually render named parameter names in generated SQL, use the DSLContext.renderNamedParams() method:
-- The named bind variable can be rendered SELECT * FROM AUTHOR WHERE LAST_NAME = :lastName
create.renderNamedParams( create.select() .from(AUTHOR) .where(LAST_NAME.eq( param("lastName", "Poe"))));
4.13.3. Inlined parameters
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Sometimes, you may wish to avoid rendering bind variables while still using custom values in SQL. jOOQ refers to that as "inlined" bind values. When bind values are inlined, they render the actual value in SQL rather than a JDBC question mark. Bind value inlining can be achieved in several ways:
- Globally, by using the Settings and setting the org.jooq.conf.StatementType to STATIC_STATEMENT. This will inline all bind values for SQL statements rendered from such a Configuration.
- Per query locally, by using the Query.getSQL(ParamType) method.
- Per value locally, by using DSL.inline() methods.
In all cases, your inlined bind values will be properly escaped to avoid SQL syntax errors and SQL injection. Some examples:
// Use dedicated calls to inline() in order to specify // single bind values to be rendered as inline values // -------------------------------------------------- create.select() .from(AUTHOR) .where(LAST_NAME.eq(inline("Poe"))) .fetch(); // Or render the whole query with inlined values // -------------------------------------------------- Settings settings = new Settings() .withStatementType(StatementType.STATIC_STATEMENT); // Add the settings to the Configuration DSLContext create = DSL.using(connection, SQLDialect.ORACLE, settings); // Run queries that omit rendering schema names create.select() .from(AUTHOR) .where(LAST_NAME.eq("Poe")) .fetch();
4.13.4. SQL injection and plain SQL QueryParts
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Special care needs to be taken when using plain SQL QueryParts. While jOOQ's API allows you to specify bind values for use with plain SQL, you're not forced to do that. For instance, both of the following queries will lead to the same, valid result:
// This query will use bind values, internally. create.fetch("SELECT * FROM BOOK WHERE ID = ? AND TITLE = ?", 5, "Animal Farm"); // This query will not use bind values, internally. create.fetch("SELECT * FROM BOOK WHERE ID = 5 AND TITLE = 'Animal Farm'");
All methods in the jOOQ API that allow for plain (unescaped, untreated) SQL contain a warning message in their relevant Javadoc, to remind you of the risk of SQL injection in what is otherwise a SQL-injection-safe API.
4.14. QueryParts
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
A org.jooq.Query and all its contained objects is a org.jooq.QueryPart. QueryParts essentially provide this functionality:
- they can render SQL using the accept(Context) method
- they can bind variables using the accept(Context) method
Both of these methods are contained in jOOQ's internal API's org.jooq.QueryPartInternal, which is internally implemented by every QueryPart.
The following sections explain some more details about SQL rendering and variable binding, as well as other implementation details about QueryParts in general.
4.14.1. SQL rendering
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Every org.jooq.QueryPart must implement the accept(Context) method to render its SQL string to a org.jooq.RenderContext. This RenderContext has two purposes:
- It provides some information about the "state" of SQL rendering.
- It provides a common API for constructing SQL strings on the context's internal java.lang.StringBuilder
An overview of the org.jooq.RenderContext API is given here:
// These methods are useful for generating unique aliases within a RenderContext (and thus within a Query) String peekAlias(); String nextAlias(); // These methods return rendered SQL String render(); String render(QueryPart part); // These methods allow for fluent appending of SQL to the RenderContext's internal StringBuilder RenderContext keyword(String keyword); RenderContext literal(String literal); RenderContext sql(String sql); RenderContext sql(char sql); RenderContext sql(int sql); RenderContext sql(QueryPart part); // These methods allow for controlling formatting of SQL, if the relevant Setting is active RenderContext formatNewLine(); RenderContext formatSeparator(); RenderContext formatIndentStart(); RenderContext formatIndentStart(int indent); RenderContext formatIndentLockStart(); RenderContext formatIndentEnd(); RenderContext formatIndentEnd(int indent); RenderContext formatIndentLockEnd(); // These methods control the RenderContext's internal state boolean inline(); RenderContext inline(boolean inline); boolean qualify(); RenderContext qualify(boolean qualify); boolean namedParams(); RenderContext namedParams(boolean renderNamedParams); CastMode castMode(); RenderContext castMode(CastMode mode); Boolean cast(); RenderContext castModeSome(SQLDialect... dialects);
The following additional methods are inherited from a common org.jooq.Context, which is shared among org.jooq.RenderContext and org.jooq.BindContext:
// These methods indicate whether fields or tables are being declared (MY_TABLE AS MY_ALIAS) or referenced (MY_ALIAS) boolean declareFields(); Context declareFields(boolean declareFields); boolean declareTables(); Context declareTables(boolean declareTables); // These methods indicate whether a top-level query is being rendered, or a subquery boolean subquery(); Context subquery(boolean subquery); // These methods provide the bind value indices within the scope of the whole Context (and thus of the whole Query) int nextIndex(); int peekIndex();
An example of rendering SQL
A simple example can be provided by checking out jOOQ's internal representation of a (simplified) CompareCondition. It is used for any org.jooq.Condition comparing two fields as for example the AUTHOR.ID = BOOK.AUTHOR_ID
condition here:
-- [...] FROM AUTHOR JOIN BOOK ON AUTHOR.ID = BOOK.AUTHOR_ID -- [...]
This is how jOOQ renders such a condition (simplified example):
@Override public final void accept(Context<?> context) { // The CompareCondition delegates rendering of the Fields to the Fields // themselves and connects them using the Condition's comparator operator: context.visit(field1) .sql(" ") .keyword(comparator.toSQL()) .sql(" ") .visit(field2); }
See the manual's sections about custom QueryParts and plain SQL QueryParts to learn about how to write your own query parts in order to extend jOOQ.
4.14.2. Pretty printing SQL
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
As mentioned in the previous chapter about SQL rendering, there are some elements in the org.jooq.RenderContext that are used for formatting / pretty-printing rendered SQL. In order to obtain pretty-printed SQL, just use the following custom settings:
// Create a DSLContext that will render "formatted" SQL DSLContext pretty = DSL.using(dialect, new Settings().withRenderFormatted(true));
And then, use the above DSLContext to render pretty-printed SQL:
select "TEST"."AUTHOR"."LAST_NAME", count(*) "c" from "TEST"."BOOK" join "TEST"."AUTHOR" on "TEST"."BOOK"."AUTHOR_ID" = "TEST"."AUTHOR"."ID" where "TEST"."BOOK"."TITLE" <> '1984' group by "TEST"."AUTHOR"."LAST_NAME" having count(*) = 2
String sql = pretty.select( AUTHOR.LAST_NAME, count().as("c")) .from(BOOK) .join(AUTHOR) .on(BOOK.AUTHOR_ID.eq(AUTHOR.ID)) .where(BOOK.TITLE.ne("1984")) .groupBy(AUTHOR.LAST_NAME) .having(count().eq(2)) .getSQL();
The section about ExecuteListeners shows an example of how such pretty printing can be used to log readable SQL to the stdout.
4.14.3. Variable binding
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Every org.jooq.QueryPart must implement the accept(Context<?>) method. This Context has two purposes (among many others):
- It provides some information about the "state" of the variable binding in process.
- It provides a common API for binding values to the context's internal java.sql.PreparedStatement
An overview of the org.jooq.BindContext API is given here:
// This method provides access to the PreparedStatement to which bind values are bound PreparedStatement statement(); // These methods provide convenience to delegate variable binding BindContext bind(QueryPart part) throws DataAccessException; BindContext bind(Collection<? extends QueryPart> parts) throws DataAccessException; BindContext bind(QueryPart[] parts) throws DataAccessException; // These methods perform the actual variable binding BindContext bindValue(Object value, Class<?> type) throws DataAccessException; BindContext bindValues(Object... values) throws DataAccessException;
Some additional methods are inherited from a common org.jooq.Context, which is shared among org.jooq.RenderContext and org.jooq.BindContext. Details are documented in the previous chapter about SQL rendering
An example of binding values to SQL
A simple example can be provided by checking out jOOQ's internal representation of a (simplified) CompareCondition. It is used for any org.jooq.Condition comparing two fields as for example the AUTHOR.ID = BOOK.AUTHOR_ID
condition here:
-- [...] WHERE AUTHOR.ID = ? -- [...]
This is how jOOQ binds values on such a condition:
@Override public final void bind(BindContext context) throws DataAccessException { // The CompareCondition itself does not bind any variables. // But the two fields involved in the condition might do so... context.bind(field1).bind(field2); }
See the manual's sections about custom QueryParts and plain SQL QueryParts to learn about how to write your own query parts in order to extend jOOQ.
4.14.4. Extend jOOQ with custom types
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
To support simple vendor specific SQL syntax extensions, jOOQ offers the plain SQL templating API. If a SQL clause is too complex to express with jOOQ or with this templating API, or you have a requirement to support different dialects, you can extend either one of the following types for use directly in a jOOQ query:
// Simplified API description: public abstract class CustomField<T> implements Field<T> {} public abstract class CustomCondition implements Condition {} public abstract class CustomTable<R extends TableRecord<R>> implements Table<R> {} public abstract class CustomRecord<R extends TableRecord<R>> implements TableRecord<R> {}
An example for implementing a custom table and its record.
Here's an example org.jooq.impl.CustomTable showing how to create a custom table with its field definitions, similar to what the code generator is doing.
public class BookTable extends CustomTable<BookRecord> { public static final BookTable BOOK = new BookTable(); public final TableField<BookRecord, String> FIRST_NAME = createField(name("FIRST_NAME"), VARCHAR); public final TableField<BookRecord, String> UNMATCHED = createField(name("UNMATCHED"), VARCHAR); public final TableField<BookRecord, String> LAST_NAME = createField(name("LAST_NAME"), VARCHAR); public final TableField<BookRecord, Short> ID = createField(name("ID"), SMALLINT); public final TableField<BookRecord, String> TITLE = createField(name("TITLE"), VARCHAR); protected BookTable() { super(name("BOOK")); } @Override public Class<? extends BookRecord> getRecordType() { return BookRecord.class; } } public class BookRecord extends CustomRecord<BookRecord> { protected BookRecord() { super(BookTable.BOOK); } }
An example for implementing custom multiplication.
Here's an example org.jooq.impl.CustomField showing how to create a field multiplying another field by 2
// Create an anonymous CustomField, initialised with BOOK.ID arguments final Field<Integer> IDx2 = new CustomField<Integer>(BOOK.ID.getName(), BOOK.ID.getDataType()) { @Override public void accept(Context<?> context) { context.visit(BOOK.ID).sql(" * ").visit(DSL.val(2)); } }; // Use the above field in a SQL statement: create.select(IDx2).from(BOOK);
An example for implementing vendor-specific functions.
Many vendor-specific functions are not officially supported by jOOQ, but you can implement such support yourself using CustomField
, for instance. Here's an example showing how to implement Oracle's TO_CHAR()
function, emulating it in SQL Server using CONVERT()
:
// Create a CustomField implementation taking two arguments in its constructor class ToChar extends CustomField<String> { final Field<?> arg0; final Field<?> arg1; ToChar(Field<?> arg0, Field<?> arg1) { super("to_char", VARCHAR); this.arg0 = arg0; this.arg1 = arg1; } @Override public void accept(RenderContext context) { context.visit(delegate(context.configuration())); } private QueryPart delegate(Configuration configuration) { switch (configuration.dialect().family()) { case ORACLE: return DSL.field("TO_CHAR({0}, {1})", String.class, arg0, arg1); case SQLSERVER: return DSL.field("CONVERT(VARCHAR(8), {0}, {1})", String.class, arg0, arg1); default: throw new UnsupportedOperationException("Dialect not supported"); } } }
The above CustomField
implementation can be exposed from your own custom DSL class:
public class MyDSL { public static Field<String> toChar(Field<?> field, String format) { return new ToChar(field, DSL.inline(format)); } }
4.14.5. Plain SQL QueryParts
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
If you don't need the integration of rather complex QueryParts into jOOQ, then you might be safer using simple Plain SQL functionality, where you can provide jOOQ with a simple String representation of your embedded SQL. Plain SQL methods in jOOQ's API come in two flavours.
- method(String, Object...): This is a method that accepts a SQL string and a list of bind values that are to be bound to the variables contained in the SQL string
- method(String, QueryPart...): This is a method that accepts a SQL string and a list of QueryParts that are "injected" at the position of their respective placeholders in the SQL string
The above distinction is best explained using an example:
// Plain SQL using bind values. The value 5 is bound to the first variable, "Animal Farm" to the second variable: create.selectFrom(BOOK).where("BOOK.ID = ? AND TITLE = ?", 5, "Animal Farm"); // Plain SQL using placeholders (counting from zero). // The QueryPart "id" is substituted for the placeholder {0}, the QueryPart "title" for {1} Field<Integer> id = val(5); Field<String> title = val("Animal Farm"); create.selectFrom(BOOK).where("BOOK.ID = {0} AND TITLE = {1}", id, title).fetch();
The above technique allows for creating rather complex SQL clauses that are currently not supported by jOOQ, without extending any of the custom QueryParts as indicated in the previous chapter.
4.14.6. Serializability
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
A lot of jOOQ types extend and implement the java.io.Serializable interface for your convenience. Beware, however, that jOOQ will make no guarantees related to the serialisation format, and its backwards compatible evolution. This means that while it is generally safe to rely on jOOQ types being serialisable when two processes using the exact same jOOQ version transfer jOOQ state over some network, it is not safe to rely on persisting serialised jOOQ state to be deserialised again at a later time - even after a patch release upgrade!
As always with Java's serialisation, if you want reliable serialisation of Java objects, please use your own serialisation protocol, or use one of the official export formats.
What types are serializable?
The only transient, non-serializable element in any jOOQ object is the Configuration's underlying java.sql.Connection. When you want to execute queries after de-serialisation, or when you want to store/refresh/delete Updatable Records, you may have to "re-attach" them to a Configuration
// Deserialise a SELECT statement ObjectInputStream in = new ObjectInputStream(...); Select<?> select = (Select<?>) in.readObject(); // This will throw a DetachedException: select.execute(); // In order to execute the above select, attach it first DSLContext create = DSL.using(connection, SQLDialect.ORACLE); create.attach(select);
Automatically attaching QueryParts
Another way of attaching QueryParts automatically, or rather providing them with a new java.sql.Connection at will, is to hook into the Execute Listener support. More details about this can be found in the manual's chapter about ExecuteListeners
4.14.7. Custom SQL transformation
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
With jOOQ 3.2's org.jooq.VisitListener SPI, it is possible to perform custom SQL transformation to implement things like shared-schema multi-tenancy, or a security layer centrally preventing access to certain data. This SPI is extremely powerful, as you can make ad-hoc decisions at runtime regarding local or global transformation of your SQL statement. The following sections show a couple of simple, yet real-world use-cases.
4.14.7.1. Logging abbreviated bind values
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
When implementing a logger, one needs to carefully assess how much information should really be disclosed on what logger level. In log4j and similar frameworks, we distinguish between FATAL
, ERROR
, WARN
, INFO
, DEBUG
, and TRACE
. In DEBUG
level, jOOQ's internal default logger logs all executed statements including inlined bind values as such:
Executing query : select * from "BOOK" where "BOOK"."TITLE" like ? -> with bind values : select * from "BOOK" where "BOOK"."TITLE" like 'How I stopped worrying%'
But textual or binary bind values can get quite long, quickly filling your log files with irrelevant information. It would be good to be able to abbreviate such long values (and possibly add a remark to the logged statement). Instead of patching jOOQ's internals, we can just transform the SQL statements in the logger implementation, cleanly separating concerns. This can be done with the following VisitListener
:
// This listener is inserted into a Configuration through a VisitListenerProvider that creates a // new listener instance for every rendering lifecycle public class BindValueAbbreviator extends DefaultVisitListener { private boolean anyAbbreviations = false; @Override public void visitStart(VisitContext context) { // Transform only when rendering values if (context.renderContext() != null) { QueryPart part = context.queryPart(); // Consider only bind variables, leave other QueryParts untouched if (part instanceof Param<?>) { Param<?> param = (Param<?>) part; Object value = param.getValue(); // If the bind value is a String (or Clob) of a given length, abbreviate it // e.g. using commons-lang's StringUtils.abbreviate() if (value instanceof String && ((String) value).length() > maxLength) { anyAbbreviations = true; // ... and replace it in the current rendering context (not in the Query) context.queryPart(val(abbreviate((String) value, maxLength))); } // If the bind value is a byte[] (or Blob) of a given length, abbreviate it // e.g. by removing bytes from the array else if (value instanceof byte[] && ((byte[]) value).length > maxLength) { anyAbbreviations = true; // ... and replace it in the current rendering context (not in the Query) context.queryPart(val(Arrays.copyOf((byte[]) value, maxLength))); } } } } @Override public void visitEnd(VisitContext context) { // If any abbreviations were performed before... if (anyAbbreviations) { // ... and if this is the top-level QueryPart, then append a SQL comment to indicate the abbreviation if (context.queryPartsLength() == 1) { context.renderContext().sql(" -- Bind values may have been abbreviated"); } } } }
If maxLength were set to 5, the above listener would produce the following log output:
Executing query : select * from "BOOK" where "BOOK"."TITLE" like ? -> with bind values : select * from "BOOK" where "BOOK"."TITLE" like 'Ho...' -- Bind values may have been abbreviated
The above VisitListener
is in place since jOOQ 3.3 in the org.jooq.tools.LoggerListener.
4.15. Zero-based vs one-based APIs
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Any API that bridges two languages / mind sets, such as Java / SQL will inevitably face the difficulty of finding a consistent strategy to solving the "based-ness" problem. Should arrays be one-based or zero-based?
Clearly, Java is zero-based and SQL is one-based, and the best strategy for jOOQ is to keep things this way. The following are a set of rules that you should remember if this ever confuses you:
All SQL API is one-based
When using SQL API, such as the index-based ORDER BY clause, or window functions such as in the example below, jOOQ will not interpret indexes but send them directly as-is to the SQL engine. For instance:
SELECT nth_value(title, 3) OVER (ORDER BY id) FROM book ORDER BY 1
create.select(nthValue(BOOK.TITLE, 3).over(orderBy(BOOK.ID))) .from(BOOK) .orderBy(1).fetch();
In the above example, we're looking for the 3rd value of X in T ordered by Y. Clearly, this window function uses one-based indexing. The same is true for the ORDER BY
clause, which orders the result by the 1st column - again one-based counting. There is no column zero in SQL.
All jOOQ API is zero-based
jOOQ is a Java API and as such, one-basedness would be quite surprising despite the fact that JDBC is one-based (see below). For instance, when you access a record by index in a jOOQ org.jooq.Result, given that the result extends java.util.List, you will use zero-based index access:
Result<?> result = create.select(BOOK.ID, BOOK.TITLE) .from(BOOK) .orderBy(1) .fetch(); for (int i = 0; i < result.size(); i++) System.out.println(result.get(i));
Unlike in JDBC, where java.sql.ResultSet#absolute(int) positions the underlying cursor at the one-based index, we Java developers really don't like that way of thinking. As can be seen in the above loop, we iterate over this result as we do over any other Java collection.
All JDBC API is one-based
An exception to the above rule is, obviously, all jOOQ API that is JDBC-interfacing.
4.16. SQL building in Scala
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
jOOQ-Scala is a maven module used for leveraging some advanced Scala features for those users that wish to use jOOQ with Scala.
Using Scala's implicit defs to allow for operator overloading
The most obvious Scala feature to use in jOOQ are implicit defs for implicit conversions in order to enhance the org.jooq.Field type with SQL-esque operators.
The following depicts a trait which wraps all fields:
/** * A Scala-esque representation of {@link org.jooq.Field}, adding overloaded * operators for common jOOQ operations to arbitrary fields */ trait SAnyField[T] extends Field[T] { // String operations // ----------------- def ||(value : String) : Field[String] def ||(value : Field[_]) : Field[String] // Comparison predicates // --------------------- def ===(value : T) : Condition def ===(value : Field[T]) : Condition def !==(value : T) : Condition def !==(value : Field[T]) : Condition def <>(value : T) : Condition def <>(value : Field[T]) : Condition def >(value : T) : Condition def >(value : Field[T]) : Condition def >=(value : T) : Condition def >=(value : Field[T]) : Condition def <(value : T) : Condition def <(value : Field[T]) : Condition def <=(value : T) : Condition def <=(value : Field[T]) : Condition def <=>(value : T) : Condition def <=>(value : Field[T]) : Condition }
The following depicts a trait which wraps numeric fields:
/** * A Scala-esque representation of {@link org.jooq.Field}, adding overloaded * operators for common jOOQ operations to numeric fields */ trait SNumberField[T <: Number] extends SAnyField[T] { // Arithmetic operations // --------------------- def unary_- : Field[T] def +(value : Number) : Field[T] def +(value : Field[_ <: Number]) : Field[T] def -(value : Number) : Field[T] def -(value : Field[_ <: Number]) : Field[T] def *(value : Number) : Field[T] def *(value : Field[_ <: Number]) : Field[T] def /(value : Number) : Field[T] def /(value : Field[_ <: Number]) : Field[T] def %(value : Number) : Field[T] def %(value : Field[_ <: Number]) : Field[T] // Bitwise operations // ------------------ def unary_~ : Field[T] def &(value : T) : Field[T] def &(value : Field[T]) : Field[T] def |(value : T) : Field[T] def |(value : Field[T]) : Field[T] def ^(value : T) : Field[T] def ^(value : Field[T]) : Field[T] def <<(value : T) : Field[T] def <<(value : Field[T]) : Field[T] def >>(value : T) : Field[T] def >>(value : Field[T]) : Field[T] }
An example query using such overloaded operators would then look like this:
select ( BOOK.ID * BOOK.AUTHOR_ID, BOOK.ID + BOOK.AUTHOR_ID * 3 + 4, BOOK.TITLE || " abc" || " xy") from BOOK leftOuterJoin ( select (x.ID, x.YEAR_OF_BIRTH) from x limit 1 asTable x.getName() ) on BOOK.AUTHOR_ID === x.ID where (BOOK.ID <> 2) or (BOOK.TITLE in ("O Alquimista", "Brida")) fetch
Scala 2.10 Macros
This feature is still being experimented with. With Scala Macros, it might be possible to inline a true SQL dialect into the Scala syntax, backed by the jOOQ API. Stay tuned!
5. SQL execution
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In a previous section of the manual, we've seen how jOOQ can be used to build SQL that can be executed with any API including JDBC or ... jOOQ. This section of the manual deals with various means of actually executing SQL with jOOQ.
SQL execution with JDBC
JDBC calls executable objects "java.sql.Statement". It distinguishes between three types of statements:
- java.sql.Statement, or "static statement": This statement type is used for any arbitrary type of SQL statement. It is particularly useful with inlined parameters
- java.sql.PreparedStatement: This statement type is used for any arbitrary type of SQL statement. It is particularly useful with indexed parameters (note that JDBC does not support named parameters)
- java.sql.CallableStatement: This statement type is used for SQL statements that are "called" rather than "executed". In particular, this includes calls to stored procedures. Callable statements can register OUT parameters
Today, the JDBC API may look weird to users being used to object-oriented design. While statements hide a lot of SQL dialect-specific implementation details quite well, they assume a lot of knowledge about the internal state of a statement. For instance, you can use the PreparedStatement.addBatch() method, to add a the prepared statement being created to an "internal list" of batch statements. Instead of returning a new type, this method forces user to reflect on the prepared statement's internal state or "mode".
jOOQ is wrapping JDBC
These things are abstracted away by jOOQ, which exposes such concepts in a more object-oriented way. For more details about jOOQ's batch query execution, see the manual's section about batch execution.
The following sections of this manual will show how jOOQ is wrapping JDBC for SQL execution
5.1. Comparison between jOOQ and JDBC
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Similarities with JDBC
Even if there are two general types of Query, there are a lot of similarities between JDBC and jOOQ. Just to name a few:
- Both APIs return the number of affected records in non-result queries. JDBC: Statement.executeUpdate(), jOOQ: Query.execute()
- Both APIs return a scrollable result set type from result queries. JDBC: java.sql.ResultSet, jOOQ: org.jooq.Result
Differences to JDBC
Some of the most important differences between JDBC and jOOQ are listed here:
- Query vs. ResultQuery: JDBC does not formally distinguish between queries that can return results, and queries that cannot. The same API is used for both. This greatly reduces the possibility for fetching convenience methods
- Exception handling: While SQL uses the checked java.sql.SQLException, jOOQ wraps all exceptions in an unchecked org.jooq.exception.DataAccessException
- org.jooq.Result: Unlike its JDBC counter-part, this type implements java.util.List and is fully loaded into Java memory, freeing resources as early as possible. Just like statements, this means that users don't have to deal with a "weird" internal result set state.
- org.jooq.Cursor: If you want more fine-grained control over how many records are fetched into memory at once, you can still do that using jOOQ's lazy fetching feature
- Statement type: jOOQ does not formally distinguish between static statements and prepared statements. By default, all statements are prepared statements in jOOQ, internally. Executing a statement as a static statement can be done simply using a custom settings flag
- Closing Statements: JDBC keeps open resources even if they are already consumed. With JDBC, there is a lot of verbosity around safely closing resources. In jOOQ, resources are closed after consumption, by default. If you want to keep them open after consumption, you have to explicitly say so.
- JDBC flags: JDBC execution flags and modes are not modified. They can be set fluently on a Query
- Zero-based vs one-based APIs: JDBC is a one-based API, jOOQ is a zero-based API. While this makes sense intuitively (JDBC is the less intuitive API from a Java perspective), it can lead to confusion in certain cases.
5.2. Query vs. ResultQuery
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Unlike JDBC, jOOQ has a lot of knowledge about a SQL query's structure and internals (see the manual's section about SQL building). Hence, jOOQ distinguishes between these two fundamental types of queries. While every org.jooq.Query can be executed, only org.jooq.ResultQuery can return results (see the manual's section about fetching to learn more about fetching results). With plain SQL, the distinction can be made clear most easily:
// Create a Query object and execute it: Query query = create.query("DELETE FROM BOOK"); query.execute(); // Create a ResultQuery object and execute it, fetching results: ResultQuery<Record> resultQuery = create.resultQuery("SELECT * FROM BOOK"); Result<Record> result = resultQuery.fetch();
5.3. Fetching
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Fetching is something that has been completely neglegted by JDBC and also by various other database abstraction libraries. Fetching is much more than just looping or listing records or mapped objects. There are so many ways you may want to fetch data from a database, it should be considered a first-class feature of any database abstraction API. Just to name a few, here are some of jOOQ's fetching modes:
- Untyped vs. typed fetching: Sometimes you care about the returned type of your records, sometimes (with arbitrary projections) you don't.
- Fetching arrays, maps, or lists: Instead of letting you transform your result sets into any more suitable data type, a library should do that work for you.
- Fetching through handler callbacks: This is an entirely different fetching paradigm. With Java 8's lambda expressions, this will become even more powerful.
- Fetching through mapper callbacks: This is an entirely different fetching paradigm. With Java 8's lambda expressions, this will become even more powerful.
- Fetching custom POJOs: This is what made Hibernate and JPA so strong. Automatic mapping of tables to custom POJOs.
- Lazy vs. eager fetching: It should be easy to distinguish these two fetch modes.
- Fetching many results: Some databases allow for returning many result sets from a single query. JDBC can handle this but it's very verbose. A list of results should be returned instead.
- Fetching data asynchronously: Some queries take too long to execute to wait for their results. You should be able to spawn query execution in a separate process.
Convenience and how ResultQuery, Result, and Record share API
The term "fetch" is always reused in jOOQ when you can fetch data from the database. An org.jooq.ResultQuery provides many overloaded means of fetching data:
Various modes of fetching
These modes of fetching are also documented in subsequent sections of the manual
// The "standard" fetch Result<R> fetch(); // The "standard" fetch when you know your query returns only one record R fetchOne(); // The "standard" fetch when you only want to fetch the first record R fetchAny(); // Create a "lazy" Cursor, that keeps an open underlying JDBC ResultSet Cursor<R> fetchLazy(); Cursor<R> fetchLazy(int fetchSize); // Fetch several results at once List<Result<Record>> fetchMany(); // Fetch records into a custom callback <H extends RecordHandler<R>> H fetchInto(H handler); // Map records using a custom callback <E> List<E> fetch(RecordMapper<? super R, E> mapper); // Execute a ResultQuery with jOOQ, but return a JDBC ResultSet, not a jOOQ object ResultSet fetchResultSet();
Fetch convenience
These means of fetching are also available from org.jooq.Result and org.jooq.Record APIs
// These methods are convenience for fetching only a single field, // possibly converting results to another type <T> List<T> fetch(Field<T> field); <T> List<T> fetch(Field<?> field, Class<? extends T> type); <T, U> List<U> fetch(Field<T> field, Converter<? super T, U> converter); List<?> fetch(int fieldIndex); <T> List<T> fetch(int fieldIndex, Class<? extends T> type); <U> List<U> fetch(int fieldIndex, Converter<?, U> converter); List<?> fetch(String fieldName); <T> List<T> fetch(String fieldName, Class<? extends T> type); <U> List<U> fetch(String fieldName, Converter<?, U> converter); // These methods are convenience for fetching only a single field, possibly converting results to another type // Instead of returning lists, these return arrays <T> T[] fetchArray(Field<T> field); <T> T[] fetchArray(Field<?> field, Class<? extends T> type); <T, U> U[] fetchArray(Field<T> field, Converter<? super T, U> converter); Object[] fetchArray(int fieldIndex); <T> T[] fetchArray(int fieldIndex, Class<? extends T> type); <U> U[] fetchArray(int fieldIndex, Converter<?, U> converter); Object[] fetchArray(String fieldName); <T> T[] fetchArray(String fieldName, Class<? extends T> type); <U> U[] fetchArray(String fieldName, Converter<?, U> converter); // These methods are convenience for fetching only a single field from a single record, // possibly converting results to another type <T> T fetchOne(Field<T> field); <T> T fetchOne(Field<?> field, Class<? extends T> type); <T, U> U fetchOne(Field<T> field, Converter<? super T, U> converter); Object fetchOne(int fieldIndex); <T> T fetchOne(int fieldIndex, Class<? extends T> type); <U> U fetchOne(int fieldIndex, Converter<?, U> converter); Object fetchOne(String fieldName); <T> T fetchOne(String fieldName, Class<? extends T> type); <U> U fetchOne(String fieldName, Converter<?, U> converter);
Fetch transformations
These means of fetching are also available from org.jooq.Result and org.jooq.Record APIs
// Transform your Records into arrays, Results into matrices Object[][] fetchArrays(); Object[] fetchOneArray(); // Reduce your Result object into maps <K> Map<K, R> fetchMap(Field<K> key); <K, V> Map<K, V> fetchMap(Field<K> key, Field<V> value); <K, E> Map<K, E> fetchMap(Field<K> key, Class<E> value); Map<Record, R> fetchMap(Field<?>[] key); <E> Map<Record, E> fetchMap(Field<?>[] key, Class<E> value); // Transform your Result object into maps List<Map<String, Object>> fetchMaps(); Map<String, Object> fetchOneMap(); // Transform your Result object into groups <K> Map<K, Result<R>> fetchGroups(Field<K> key); <K, V> Map<K, List<V>> fetchGroups(Field<K> key, Field<V> value); <K, E> Map<K, List<E>> fetchGroups(Field<K> key, Class<E> value); Map<Record, Result<R>> fetchGroups(Field<?>[] key); <E> Map<Record, List<E>> fetchGroups(Field<?>[] key, Class<E> value); // Transform your Records into custom POJOs <E> List<E> fetchInto(Class<? extends E> type); // Transform your records into another table type <Z extends Record> Result<Z> fetchInto(Table<Z> table);
Note, that apart from the fetchLazy() methods, all fetch() methods will immediately close underlying JDBC result sets.
5.3.1. Record vs. TableRecord
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
jOOQ understands that SQL is much more expressive than Java, when it comes to the declarative typing of table expressions. As a declarative language, SQL allows for creating ad-hoc row value expressions (records with indexed columns, or tuples) and records (records with named columns). In Java, this is not possible to the same extent.
Yet, still, sometimes you wish to use strongly typed records, when you know that you're selecting only from a single table:
Fetching strongly or weakly typed records
When fetching data only from a single table, the table expression's type is known to jOOQ if you use jOOQ's code generator to generate TableRecords for your database tables. In order to fetch such strongly typed records, you will have to use the simple select API:
// Use the selectFrom() method: BookRecord book = create.selectFrom(BOOK).where(BOOK.ID.eq(1)).fetchOne(); // Typesafe field access is now possible: System.out.println("Title : " + book.getTitle()); System.out.println("Published in: " + book.getPublishedIn());
When you use the DSLContext.selectFrom() method, jOOQ will return the record type supplied with the argument table. Beware though, that you will no longer be able to use any clause that modifies the type of your table expression. This includes:
Mapping custom row types to strongly typed records
Sometimes, you may want to explicitly select only a subset of your columns, but still use strongly typed records. Alternatively, you may want to join a one-to-one relationship and receive the two individual strongly typed records after the join.
In both of the above cases, you can map your org.jooq.Record "into" a org.jooq.TableRecord type by using Record.into(Table).
// Join two tables Record record = create.select() .from(BOOK) .join(AUTHOR).on(BOOK.AUTHOR_ID.eq(AUTHOR.ID)) .where(BOOK.ID.eq(1)) .fetchOne(); // "extract" the two individual strongly typed TableRecord types from the denormalised Record: BookRecord book = record.into(BOOK); AuthorRecord author = record.into(AUTHOR); // Typesafe field access is now possible: System.out.println("Title : " + book.getTitle()); System.out.println("Published in: " + book.getPublishedIn()); System.out.println("Author : " + author.getFirstName() + " " + author.getLastName();
5.3.2. Record1 to Record22
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
jOOQ's row value expression (or tuple) support has been explained earlier in this manual. It is useful for constructing row value expressions where they can be used in SQL. The same typesafety is also applied to records for degrees up to 22. To express this fact, org.jooq.Record is extended by org.jooq.Record1 to org.jooq.Record22. Apart from the fact that these extensions of the R type can be used throughout the jOOQ DSL, they also provide a useful API. Here is org.jooq.Record2, for instance:
public interface Record2<T1, T2> extends Record { // Access fields and values as row value expressions Row2<T1, T2> fieldsRow(); Row2<T1, T2> valuesRow(); // Access fields by index Field<T1> field1(); Field<T2> field2(); // Access values by index T1 value1(); T2 value2(); }
Higher-degree records
jOOQ chose to explicitly support degrees up to 22 to match Scala's typesafe tuple, function and product support. Unlike Scala, however, jOOQ also supports higher degrees without the additional typesafety.
5.3.3. Arrays, Maps and Lists
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
By default, jOOQ returns an org.jooq.Result object, which is essentially a java.util.List of org.jooq.Record. Often, you will find yourself wanting to transform this result object into a type that corresponds more to your specific needs. Or you just want to list all values of one specific column. Here are some examples to illustrate those use cases:
// Fetching only book titles (the two calls are equivalent): List<String> titles1 = create.select().from(BOOK).fetch().getValues(BOOK.TITLE); List<String> titles2 = create.select().from(BOOK).fetch(BOOK.TITLE); String[] titles3 = create.select().from(BOOK).fetchArray(BOOK.TITLE); // Fetching only book IDs, converted to Long List<Long> ids1 = create.select().from(BOOK).fetch().getValues(BOOK.ID, Long.class); List<Long> ids2 = create.select().from(BOOK).fetch(BOOK.ID, Long.class); Long[] ids3 = create.select().from(BOOK).fetchArray(BOOK.ID, Long.class); // Fetching book IDs and mapping each ID to their records or titles Map<Integer, BookRecord> map1 = create.selectFrom(BOOK).fetch().intoMap(BOOK.ID); Map<Integer, BookRecord> map2 = create.selectFrom(BOOK).fetchMap(BOOK.ID); Map<Integer, String> map3 = create.selectFrom(BOOK).fetch().intoMap(BOOK.ID, BOOK.TITLE); Map<Integer, String> map4 = create.selectFrom(BOOK).fetchMap(BOOK.ID, BOOK.TITLE); // Group by AUTHOR_ID and list all books written by any author: Map<Integer, Result<BookRecord>> group1 = create.selectFrom(BOOK).fetch().intoGroups(BOOK.AUTHOR_ID); Map<Integer, Result<BookRecord>> group2 = create.selectFrom(BOOK).fetchGroups(BOOK.AUTHOR_ID); Map<Integer, List<String>> group3 = create.selectFrom(BOOK).fetch().intoGroups(BOOK.AUTHOR_ID, BOOK.TITLE); Map<Integer, List<String>> group4 = create.selectFrom(BOOK).fetchGroups(BOOK.AUTHOR_ID, BOOK.TITLE);
Note that most of these convenience methods are available both through org.jooq.ResultQuery and org.jooq.Result, some are even available through org.jooq.Record as well.
5.3.4. RecordHandler
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In a more functional operating mode, you might want to write callbacks that receive records from your select statement results in order to do some processing. This is a common data access pattern in Spring's JdbcTemplate, and it is also available in jOOQ. With jOOQ, you can implement your own org.jooq.RecordHandler classes and plug them into jOOQ's org.jooq.ResultQuery:
// Write callbacks to receive records from select statements create.selectFrom(BOOK) .orderBy(BOOK.ID) .fetch() .into(new RecordHandler<BookRecord>() { @Override public void next(BookRecord book) { Util.doThingsWithBook(book); } }); // Or more concisely create.selectFrom(BOOK) .orderBy(BOOK.ID) .fetchInto(new RecordHandler<BookRecord>() {...}); // Or even more concisely with Java 8's lambda expressions: create.selectFrom(BOOK) .orderBy(BOOK.ID) .fetchInto(book -> { Util.doThingsWithBook(book); }; );
See also the manual's section about the RecordMapper, which provides similar features
5.3.5. RecordMapper
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In a more functional operating mode, you might want to write callbacks that map records from your select statement results in order to do some processing. This is a common data access pattern in Spring's JdbcTemplate, and it is also available in jOOQ. With jOOQ, you can implement your own org.jooq.RecordMapper classes and plug them into jOOQ's org.jooq.ResultQuery:
// Write callbacks to receive records from select statements List<Integer> ids = create.selectFrom(BOOK) .orderBy(BOOK.ID) .fetch() .map(BookRecord::getId); // Or more concisely, as fetch().map(mapper) can be written as fetch(mapper): create.selectFrom(BOOK) .orderBy(BOOK.ID) .fetch(BookRecord::getId); // Or using a lambda expression: create.selectFrom(BOOK) .orderBy(BOOK.ID) .fetch(book -> book.getId()); // Of course, the lambda could be expanded into the following anonymous RecordMapper: create.selectFrom(BOOK) .orderBy(BOOK.ID) .fetch(new RecordMapper<BookRecord, Integer>() { @Override public Integer map(BookRecord book) { return book.getId(); } });
Your custom RecordMapper
types can be used automatically through jOOQ's POJO mapping APIs, by injecting a RecordMapperProvider into your Configuration.
See also the manual's section about the RecordHandler, which provides similar features
5.3.6. POJOs
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Fetching data in records is fine as long as your application is not really layered, or as long as you're still writing code in the DAO layer. But if you have a more advanced application architecture, you may not want to allow for jOOQ artefacts to leak into other layers. You may choose to write POJOs (Plain Old Java Objects) as your primary DTOs (Data Transfer Objects), without any dependencies on jOOQ's org.jooq.Record types, which may even potentially hold a reference to a Configuration, and thus a JDBC java.sql.Connection. Like Hibernate/JPA, jOOQ allows you to operate with POJOs. Unlike Hibernate/JPA, jOOQ does not "attach" those POJOs or create proxies with any magic in them.
If you're using jOOQ's code generator, you can configure it to generate POJOs for you, but you're not required to use those generated POJOs. You can use your own. See the manual's section about POJOs with custom RecordMappers to see how to modify jOOQ's standard POJO mapping behaviour.
Using JPA-annotated POJOs
jOOQ tries to find JPA annotations on your POJO types. If it finds any, they are used as the primary source for mapping meta-information. Only the javax.persistence.Column annotation is used and understood by jOOQ. An example:
// A JPA-annotated POJO class public class MyBook { @Column(name = "ID") public int myId; @Column(name = "TITLE") public String myTitle; } // The various "into()" methods allow for fetching records into your custom POJOs: MyBook myBook = create.select().from(BOOK).fetchAny().into(MyBook.class); List<MyBook> myBooks = create.select().from(BOOK).fetch().into(MyBook.class); List<MyBook> myBooks = create.select().from(BOOK).fetchInto(MyBook.class);
Just as with any other JPA implementation, you can put the javax.persistence.Column annotation on any class member, including attributes, setters and getters. Please refer to the Record.into() Javadoc for more details.
Using simple POJOs
If jOOQ does not find any JPA-annotations, columns are mapped to the "best-matching" constructor, attribute or setter. An example illustrates this:
// A "mutable" POJO class public class MyBook1 { public int id; public String title; } // The various "into()" methods allow for fetching records into your custom POJOs: MyBook1 myBook = create.select().from(BOOK).fetchAny().into(MyBook1.class); List<MyBook1> myBooks = create.select().from(BOOK).fetch().into(MyBook1.class); List<MyBook1> myBooks = create.select().from(BOOK).fetchInto(MyBook1.class);
Please refer to the Record.into() Javadoc for more details.
Using "immutable" POJOs
If jOOQ does not find any default constructor, columns are mapped to the "best-matching" constructor. This allows for using "immutable" POJOs with jOOQ. An example illustrates this:
// An "immutable" POJO class public class MyBook2 { public final int id; public final String title; public MyBook2(int id, String title) { this.id = id; this.title = title; } } // With "immutable" POJO classes, there must be an exact match between projected fields and available constructors: MyBook2 myBook = create.select(BOOK.ID, BOOK.TITLE).from(BOOK).fetchAny().into(MyBook2.class); List<MyBook2> myBooks = create.select(BOOK.ID, BOOK.TITLE).from(BOOK).fetch().into(MyBook2.class); List<MyBook2> myBooks = create.select(BOOK.ID, BOOK.TITLE).from(BOOK).fetchInto(MyBook2.class); // An "immutable" POJO class with a java.beans.ConstructorProperties annotation public class MyBook3 { public final String title; public final int id; @ConstructorProperties({ "title", "id" }) public MyBook3(String title, int id) { this.title = title; this.id = id; } } // With annotated "immutable" POJO classes, there doesn't need to be an exact match between fields and constructor arguments. // In the below cases, only BOOK.ID is really set onto the POJO, BOOK.TITLE remains null and BOOK.AUTHOR_ID is ignored MyBook3 myBook = create.select(BOOK.ID, BOOK.AUTHOR_ID).from(BOOK).fetchAny().into(MyBook3.class); List<MyBook3> myBooks = create.select(BOOK.ID, BOOK.AUTHOR_ID).from(BOOK).fetch().into(MyBook3.class); List<MyBook3> myBooks = create.select(BOOK.ID, BOOK.AUTHOR_ID).from(BOOK).fetchInto(MyBook3.class);
Please refer to the Record.into() Javadoc for more details.
Using proxyable types
jOOQ also allows for fetching data into abstract classes or interfaces, or in other words, "proxyable" types. This means that jOOQ will return a java.util.HashMap wrapped in a java.lang.reflect.Proxy implementing your custom type. An example of this is given here:
// A "proxyable" type public interface MyBook3 { int getId(); void setId(int id); String getTitle(); void setTitle(String title); } // The various "into()" methods allow for fetching records into your custom POJOs: MyBook3 myBook = create.select(BOOK.ID, BOOK.TITLE).from(BOOK).fetchAny().into(MyBook3.class); List<MyBook3> myBooks = create.select(BOOK.ID, BOOK.TITLE).from(BOOK).fetch().into(MyBook3.class); List<MyBook3> myBooks = create.select(BOOK.ID, BOOK.TITLE).from(BOOK).fetchInto(MyBook3.class);
Please refer to the Record.into() Javadoc for more details.
Loading POJOs back into Records to store them
The above examples show how to fetch data into your own custom POJOs / DTOs. When you have modified the data contained in POJOs, you probably want to store those modifications back to the database. An example of this is given here:
// A "mutable" POJO class public class MyBook { public int id; public String title; } // Create a new POJO instance MyBook myBook = new MyBook(); myBook.id = 10; myBook.title = "Animal Farm"; // Load a jOOQ-generated BookRecord from your POJO BookRecord book = create.newRecord(BOOK, myBook); // Insert it (implicitly) book.store(); // Insert it (explicitly) create.executeInsert(book); // or update it (ID = 10) create.executeUpdate(book);
Note: Because of your manual setting of ID = 10, jOOQ's store() method will asume that you want to insert a new record. See the manual's section about CRUD with UpdatableRecords for more details on this.
Interaction with DAOs
If you're using jOOQ's code generator, you can configure it to generate DAOs for you. Those DAOs operate on generated POJOs. An example of using such a DAO is given here:
// Initialise a Configuration Configuration configuration = new DefaultConfiguration().set(connection).set(SQLDialect.ORACLE); // Initialise the DAO with the Configuration BookDao bookDao = new BookDao(configuration); // Start using the DAO Book book = bookDao.findById(5); // Modify and update the POJO book.setTitle("1984"); book.setPublishedIn(1948); bookDao.update(book); // Delete it again bookDao.delete(book);
More complex data structures
jOOQ currently doesn't support more complex data structures, the way Hibernate/JPA attempt to map relational data onto POJOs. While future developments in this direction are not excluded, jOOQ claims that generic mapping strategies lead to an enormous additional complexity that only serves very few use cases. You are likely to find a solution using any of jOOQ's various fetching modes, with only little boiler-plate code on the client side.
5.3.7. POJOs with RecordMappers
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
In the previous sections we have seen how to create RecordMapper types to map jOOQ records onto arbitrary objects. We have also seen how jOOQ provides default algorithms to map jOOQ records onto POJOs. Your own custom domain model might be much more complex, but you want to avoid looking up the most appropriate RecordMapper
every time you need one. For this, you can provide jOOQ's Configuration with your own implementation of the org.jooq.RecordMapperProvider interface. An example is given here:
DSL.using(new DefaultConfiguration() .set(connection) .set(SQLDialect.ORACLE) .set( new RecordMapperProvider() { @Override public <R extends Record, E> RecordMapper<R, E> provide(RecordType<R> recordType, Class<? extends E> type) { // UUID mappers will always try to find the ID column if (type == UUID.class) { return new RecordMapper<R, E>() { @Override public E map(R record) { return (E) record.getValue("ID"); } } } // Books might be joined with their authors, create a 1:1 mapping if (type == Book.class) { return new BookMapper(); } // Fall back to jOOQ's DefaultRecordMapper, which maps records onto // POJOs using reflection. return new DefaultRecordMapper(recordType, type); } } )) .selectFrom(BOOK) .orderBy(BOOK.ID) .fetchInto(UUID.class);
The above is a very simple example showing that you will have complete flexibility in how to override jOOQ's record to POJO mapping mechanisms.
Using third party libraries
A couple of useful libraries exist out there, which implement custom, more generic mapping algorithms. Some of them have been specifically made to work with jOOQ. Among them are:
- ModelMapper (with an explicit jOOQ integration)
- SimpleFlatMapper (with an explicit jOOQ integration)
- Orika Mapper (without explicit jOOQ integration)
5.3.8. Lazy fetching
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Unlike JDBC's java.sql.ResultSet, jOOQ's org.jooq.Result does not represent an open database cursor with various fetch modes and scroll modes, that needs to be closed after usage. jOOQ's results are simple in-memory Java java.util.List objects, containing all of the result values. If your result sets are large, or if you have a lot of network latency, you may wish to fetch records one-by-one, or in small chunks. jOOQ supports a org.jooq.Cursor type for that purpose. In order to obtain such a reference, use the ResultQuery.fetchLazy() method. An example is given here:
// Obtain a Cursor reference: Cursor<BookRecord> cursor = null; try { cursor = create.selectFrom(BOOK).fetchLazy(); // Cursor has similar methods as Iterator<R> while (cursor.hasNext()) { BookRecord book = cursor.fetchOne(); Util.doThingsWithBook(book); } } // Close the cursor and the cursor's underlying JDBC ResultSet finally { if (cursor != null) { cursor.close(); } }
As a org.jooq.Cursor holds an internal reference to an open java.sql.ResultSet, it may need to be closed at the end of iteration. If a cursor is completely scrolled through, it will conveniently close the underlying ResultSet. However, you should not rely on that.
Fetch sizes
While using a Cursor
prevents jOOQ from eager fetching all data into memory, your underlying JDBC driver may still do that. To configure a fetch size in your JDBC driver, use ResultQuery.fetchSize(int), which specifies the JDBC Statement.setFetchSize(int) when executing the query. Please refer to your JDBC driver manual to learn about fetch sizes and their possible defaults and limitations.
Cursors ship with all the other fetch features
Like org.jooq.ResultQuery or org.jooq.Result, org.jooq.Cursor gives access to all of the other fetch features that we've seen so far, i.e.
- Strongly or weakly typed records: Cursors are also typed with the <R> type, allowing to fetch custom, generated org.jooq.TableRecord or plain org.jooq.Record types.
- RecordHandler callbacks: You can use your own org.jooq.RecordHandler callbacks to receive lazily fetched records.
- RecordMapper callbacks: You can use your own org.jooq.RecordMapper callbacks to map lazily fetched records.
- POJOs: You can fetch data into your own custom POJO types.
5.3.9. Many fetching
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Many databases support returning several result sets, or cursors, from single queries. An example for this is Sybase ASE's sp_help command:
> sp_help 'author' +--------+-----+-----------+-------------+-------------------+ |Name |Owner|Object_type|Object_status|Create_date | +--------+-----+-----------+-------------+-------------------+ | author|dbo |user table | -- none -- |Sep 22 2011 11:20PM| +--------+-----+-----------+-------------+-------------------+ +-------------+-------+------+----+-----+-----+ |Column_name |Type |Length|Prec|Scale|... | +-------------+-------+------+----+-----+-----+ |id |int | 4|NULL| NULL| 0| |first_name |varchar| 50|NULL| NULL| 1| |last_name |varchar| 50|NULL| NULL| 0| |date_of_birth|date | 4|NULL| NULL| 1| |year_of_birth|int | 4|NULL| NULL| 1| +-------------+-------+------+----+-----+-----+
The correct (and verbose) way to do this with JDBC is as follows:
ResultSet rs = statement.executeQuery(); // Repeat until there are no more result sets for (;;) { // Empty the current result set while (rs.next()) { // [ .. do something with it .. ] } // Get the next result set, if available if (statement.getMoreResults()) { rs = statement.getResultSet(); } else { break; } } // Be sure that all result sets are closed statement.getMoreResults(Statement.CLOSE_ALL_RESULTS); statement.close();
As previously discussed in the chapter about differences between jOOQ and JDBC, jOOQ does not rely on an internal state of any JDBC object, which is "externalised" by Javadoc. Instead, it has a straight-forward API allowing you to do the above in a one-liner:
// Get some information about the author table, its columns, keys, indexes, etc List<Result<Record>> results = create.fetchMany("sp_help 'author'");
Using generics, the resulting structure is immediately clear.
5.3.10. Later fetching
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Using Java 8 CompletableFutures
Java 8 has introduced the new java.util.concurrent.CompletableFuture type, which allows for functional composition of asynchronous execution units. When applying this to SQL and jOOQ, you might be writing code as follows:
// Initiate an asynchronous call chain CompletableFuture // This lambda will supply an int value indicating the number of inserted rows .supplyAsync(() -> DSL.using(configuration) .insertInto(AUTHOR, AUTHOR.ID, AUTHOR.LAST_NAME) .values(3, "Hitchcock") .execute() ) // This will supply an AuthorRecord value for the newly inserted author .handleAsync((rows, throwable) -> DSL.using(configuration) .fetchOne(AUTHOR, AUTHOR.ID.eq(3)) ) // This should supply an int value indicating the number of rows, // but in fact it'll throw a constraint violation exception .handleAsync((record, throwable) -> { record.changed(true); return record.insert(); }) // This will supply an int value indicating the number of deleted rows .handleAsync((rows, throwable) -> DSL.using(configuration) .delete(AUTHOR) .where(AUTHOR.ID.eq(3)) .execute() ) .join();
The above example will execute four actions one after the other, but asynchronously in the JDK's default or common java.util.concurrent.ForkJoinPool.
For more information, please refer to the java.util.concurrent.CompletableFuture Javadoc and official documentation.
Using deprecated API
Some queries take very long to execute, yet they are not crucial for the continuation of the main program. For instance, you could be generating a complicated report in a Swing application, and while this report is being calculated in your database, you want to display a background progress bar, allowing the user to pursue some other work. This can be achived simply with jOOQ, by creating a org.jooq.FutureResult, a type that extends java.util.concurrent.Future. An example is given here:
// Spawn off this query in a separate process: FutureResult<BookRecord> future = create.selectFrom(BOOK).where(... complex predicates ...).fetchLater(); // This example actively waits for the result to be done while (!future.isDone()) { progressBar.increment(1); Thread.sleep(50); } // The result should be ready, now Result<BookRecord> result = future.get();
Note, that instead of letting jOOQ spawn a new thread, you can also provide jOOQ with your own java.util.concurrent.ExecutorService:
// Spawn off this query in a separate process: ExecutorService service = // [...] FutureResult<BookRecord> future = create.selectFrom(BOOK).where(... complex predicates ...).fetchLater(service);
5.3.11. ResultSet fetching
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
When interacting with legacy applications, you may prefer to have jOOQ return a java.sql.ResultSet, rather than jOOQ's own org.jooq.Result types. This can be done simply, in two ways:
// jOOQ's Cursor type exposes the underlying ResultSet: ResultSet rs1 = create.selectFrom(BOOK).fetchLazy().resultSet(); // But you can also directly access that ResultSet from ResultQuery: ResultSet rs2 = create.selectFrom(BOOK).fetchResultSet(); // Don't forget to close these, though! rs1.close(); rs2.close();
Transform jOOQ's Result into a JDBC ResultSet
Instead of operating on a JDBC ResultSet holding an open resource from your database, you can also let jOOQ's org.jooq.Result wrap itself in a java.sql.ResultSet. The advantage of this is that the so-created ResultSet has no open connection to the database. It is a completely in-memory ResultSet:
// Transform a jOOQ Result into a ResultSet Result<BookRecord> result = create.selectFrom(BOOK).fetch(); ResultSet rs = result.intoResultSet();
The inverse: Fetch data from a legacy ResultSet using jOOQ
The inverse of the above is possible too. Maybe, a legacy part of your application produces JDBC java.sql.ResultSet, and you want to turn them into a org.jooq.Result:
// Transform a JDBC ResultSet into a jOOQ Result ResultSet rs = connection.createStatement().executeQuery("SELECT * FROM BOOK"); // As a Result: Result<Record> result = create.fetch(rs); // As a Cursor Cursor<Record> cursor = create.fetchLazy(rs);
You can also tighten the interaction with jOOQ's data type system and data type conversion features, by passing the record type to the above fetch methods:
// Pass an array of types: Result<Record> result = create.fetch (rs, Integer.class, String.class); Cursor<Record> result = create.fetchLazy(rs, Integer.class, String.class); // Pass an array of data types: Result<Record> result = create.fetch (rs, INTEGER, VARCHAR); Cursor<Record> result = create.fetchLazy(rs, INTEGER, VARCHAR); // Pass an array of fields: Result<Record> result = create.fetch (rs, BOOK.ID, BOOK.TITLE); Cursor<Record> result = create.fetchLazy(rs, BOOK.ID, BOOK.TITLE);
If supplied, the additional information is used to override the information obtained from the ResultSet's java.sql.ResultSetMetaData information.
5.3.12. Auto data type conversion
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Many native SQL data types can be automatically converted from one another, such as VARCHAR
to INTEGER
and vice versa.
The jOOQ API also supports a variety of such auto conversions through the org.jooq.tools.Convert utility API, which implements the following rules:
-
null
is always converted tonull
, or the primitive default value, orOptional.empty()
, regardless of the target type. - Identity conversion (converting a value to its own type) is always possible.
- Primitive types can be converted to their wrapper types and vice versa
- All types can be converted to String
- All types can be converted to Object
- All Number types can be converted to other Number types
- All
Number
orString
types can be converte toBoolean
. Possible (case-insensitive) values fortrue
:-
1
-
1.0
-
y
-
yes
-
true
-
on
-
enabled
Possible (case-insensitive) values forfalse
:-
0
-
0.0
-
n
-
no
-
false
-
off
-
disabled
All other values evaluate tonull
-
- All java.util.Date subtypes (java.sql.Date, java.sql.Time, java.sql.Timestamp), as well as most java.time.temporal.Temporal subtypes (java.time.LocalDate, java.time.LocalTime, java.time.LocalDateTime, java.time.OffsetTime, java.time.OffsetDateTime, as well as java.time.Instant) can be converted into each other.
-
byte[]
can be converted intoString
, using the platform's default charset -
Object[]
can be converted into any other array type, if array elements can be converted, too
This auto conversion can be applied explicitly, but is also available through a variety of API, in particular anywhere a java.lang.Class reference can be provided, such as:
Record record = ... int i = record.get(0, int.class); String s = record.get(1, String.class);
5.3.13. Custom data type conversion
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Apart from a few extra features (user-defined types), jOOQ only supports basic types as supported by the JDBC API. In your application, you may choose to transform these data types into your own ones, without writing too much boiler-plate code. This can be done using jOOQ's org.jooq.Converter types. A converter essentially allows for two-way conversion between two Java data types <T> and <U>. By convention, the <T> type corresponds to the type in your database whereas the <U> type corresponds to your own user type. The Converter API is given here:
public interface Converter<T, U> extends Serializable { /** * Convert a database object to a user object */ U from(T databaseObject); /** * Convert a user object to a database object */ T to(U userObject); /** * The database type */ Class<T> fromType(); /** * The user type */ Class<U> toType(); }
Such a converter can be used in many parts of the jOOQ API. Some examples have been illustrated in the manual's section about fetching.
A Converter for GregorianCalendar
Here is a some more elaborate example involving a Converter for java.util.GregorianCalendar:
// You may prefer Java Calendars over JDBC Timestamps public class CalendarConverter implements Converter<Timestamp, GregorianCalendar> { @Override public GregorianCalendar from(Timestamp databaseObject) { GregorianCalendar calendar = (GregorianCalendar) Calendar.getInstance(); calendar.setTimeInMillis(databaseObject.getTime()); return calendar; } @Override public Timestamp to(GregorianCalendar userObject) { return new Timestamp(userObject.getTime().getTime()); } @Override public Class<Timestamp> fromType() { return Timestamp.class; } @Override public Class<GregorianCalendar> toType() { return GregorianCalendar.class; } } // Now you can fetch calendar values from jOOQ's API: List<GregorianCalendar> dates1 = create.selectFrom(BOOK).fetch().getValues(BOOK.PUBLISHING_DATE, new CalendarConverter()); List<GregorianCalendar> dates2 = create.selectFrom(BOOK).fetch(BOOK.PUBLISHING_DATE, new CalendarConverter());
Enum Converters
jOOQ ships with a built-in default org.jooq.impl.EnumConverter, that you can use to map VARCHAR values to enum literals or NUMBER values to enum ordinals (both modes are supported). Let's say, you want to map a YES / NO / MAYBE column to a custom Enum:
// Define your Enum public enum YNM { YES, NO, MAYBE } // Define your converter public class YNMConverter extends EnumConverter<String, YNM> { public YNMConverter() { super(String.class, YNM.class); } } // And you're all set for converting records to your custom Enum: for (BookRecord book : create.selectFrom(BOOK).fetch()) { switch (book.getValue(BOOK.I_LIKE, new YNMConverter())) { case YES: System.out.println("I like this book : " + book.getTitle()); break; case NO: System.out.println("I didn't like this book : " + book.getTitle()); break; case MAYBE: System.out.println("I'm not sure about this book : " + book.getTitle()); break; } }
Using Converters in generated source code
jOOQ also allows for generated source code to reference your own custom converters, in order to permanently replace a table column's <T> type by your own, custom <U> type. See the manual's section about custom data types for details.
5.3.14. Interning data
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
SQL result tables are not optimal in terms of used memory as they are not designed to represent hierarchical data as produced by JOIN
operations. Specifically, FOREIGN KEY
values may repeat themselves unnecessarily:
+----+-----------+--------------+ | ID | AUTHOR_ID | TITLE | +----+-----------+--------------+ | 1 | 1 | 1984 | | 2 | 1 | Animal Farm | | 3 | 2 | O Alquimista | | 4 | 2 | Brida | +----+-----------+--------------+
Now, if you have millions of records with only few distinct values for AUTHOR_ID
, you may not want to hold references to distinct (but equal) java.lang.Integer objects. This is specifically true for IDs of type java.util.UUID or string representations thereof. jOOQ allows you to "intern" those values:
// Interning data after fetching Result<?> r1 = create.select(BOOK.ID, BOOK.AUTHOR_ID, BOOK.TITLE) .from(BOOK) .join(AUTHOR).on(BOOK.AUTHOR_ID.eq(AUTHOR.ID)) .fetch() .intern(BOOK.AUTHOR_ID); // Interning data while fetching Result<?> r1 = create.select(BOOK.ID, BOOK.AUTHOR_ID, BOOK.TITLE) .from(BOOK) .join(AUTHOR).on(BOOK.AUTHOR_ID.eq(AUTHOR.ID)) .intern(BOOK.AUTHOR_ID) .fetch();
You can specify as many fields as you want for interning. The above has the following effect:
- If the interned Field is of type java.lang.String, then String.intern() is called upon each string
- If the interned Field is of any other type, then the call is ignored
Future versions of jOOQ will implement interning of data for non-String data types by collecting values in java.util.Set, removing duplicate instances.
Note, that jOOQ will not use interned data for identity comparisons: string1 == string2
. Interning is used only to reduce the memory footprint of org.jooq.Result objects.
5.4. Static statements vs. Prepared Statements
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
With JDBC, you have full control over your SQL statements. You can decide yourself, if you want to execute a static java.sql.Statement without bind values, or a java.sql.PreparedStatement with (or without) bind values. But you have to decide early, which way to go. And you'll have to prevent SQL injection and syntax errors manually, when inlining your bind variables.
With jOOQ, this is easier. As a matter of fact, it is plain simple. With jOOQ, you can just set a flag in your Configuration's Settings, and all queries produced by that configuration will be executed as static statements, with all bind values inlined. An example is given here:
-- These statements are rendered by the two factories: SELECT ? FROM DUAL WHERE ? = ? SELECT 1 FROM DUAL WHERE 1 = 1
// This DSLContext executes PreparedStatements DSLContext prepare = DSL.using(connection, SQLDialect.ORACLE); // This DSLContext executes static Statements DSLContext inlined = DSL.using(connection, SQLDialect.ORACLE, new Settings().withStatementType(StatementType.STATIC_STATEMENT)); prepare.select(val(1)).where(val(1).eq(1)).fetch(); inlined.select(val(1)).where(val(1).eq(1)).fetch();
Reasons for choosing one or the other
Not all databases are equal. Some databases show improved performance if you use java.sql.PreparedStatement, as the database will then be able to re-use execution plans for identical SQL statements, regardless of actual bind values. This heavily improves the time it takes for soft-parsing a SQL statement. In other situations, assuming that bind values are irrelevant for SQL execution plans may be a bad idea, as you might run into "bind value peeking" issues. You may be better off spending the extra cost for a new hard-parse of your SQL statement and instead having the database fine-tune the new plan to the concrete bind values.
Whichever aproach is more optimal for you cannot be decided by jOOQ. In most cases, prepared statements are probably better. But you always have the option of forcing jOOQ to render inlined bind values.
Inlining bind values on a per-bind-value basis
Note that you don't have to inline all your bind values at once. If you know that a bind value is not really a variable and should be inlined explicitly, you can do so by using DSL.inline(), as documented in the manual's section about inlined parameters
5.5. Reusing a Query's PreparedStatement
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
As previously discussed in the chapter about differences between jOOQ and JDBC, reusing PreparedStatements is handled a bit differently in jOOQ from how it is handled in JDBC
Keeping open PreparedStatements with JDBC
With JDBC, you can easily reuse a java.sql.PreparedStatement by not closing it between subsequent executions. An example is given here:
// Execute the statement try (PreparedStatement stmt = connection.prepareStatement("SELECT 1 FROM DUAL")) { // Fetch a first ResultSet try (ResultSet rs1 = stmt.executeQuery()) { ... } // Without closing the statement, execute it again to fetch another ResultSet try (ResultSet rs2 = stmt.executeQuery()) { ... } }
The above technique can be quite useful when you want to reuse expensive database resources. This can be the case when your statement is executed very frequently and your database would take non-negligible time to soft-parse the prepared statement and generate a new statement / cursor resource.
Keeping open PreparedStatements with jOOQ
This is also modeled in jOOQ. However, the difference to JDBC is that closing a statement is the default action, whereas keeping it open has to be configured explicitly. This is better than JDBC, because the default action should be the one that is used most often. Keeping open statements is rarely done in average applications. Here's an example of how to keep open PreparedStatements with jOOQ:
// Create a query which is configured to keep its underlying PreparedStatement open ResultQuery<Record> query = create.selectOne().keepStatement(true); // Execute the query twice, against the same underlying PreparedStatement: try { Result<Record> result1 = query.fetch(); // This will lazily create a new PreparedStatement Result<Record> result2 = query.fetch(); // This will reuse the previous PreparedStatement } // ... but now, you must not forget to close the query finally { query.close(); }
The above example shows how a query can be executed twice against the same underlying PreparedStatement. Unlike in other execution scenarios, you must not forget to close this query now
Beware of resource leaks
While jOOQ allows for explicitly keeping open PreparedStatement
references in Query
instances, the JDBC Connection
may still be closed independently without jOOQ or the PreparedStatement
noticing. It is the user's responsibility to close all resources according to the specification and behaviour of the concrete JDBC driver and the underlying database.
5.6. JDBC flags
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
JDBC knows a couple of execution flags and modes, which can be set through the jOOQ API as well. jOOQ essentially supports these flags and execution modes:
public interface Query extends QueryPart, Attachable { // [...] // The query execution timeout. // ----------------------------------------------------------- Query queryTimeout(int timeout); }
public interface ResultQuery<R extends Record> extends Query { // [...] // The query execution timeout. // ----------------------------------------------------------- @Override ResultQuery<R> queryTimeout(int timeout); // Flags allowing to specify the resulting ResultSet modes // ----------------------------------------------------------- ResultQuery<R> resultSetConcurrency(int resultSetConcurrency); ResultQuery<R> resultSetType(int resultSetType); ResultQuery<R> resultSetHoldability(int resultSetHoldability); // The buffer size for JDBC cursors // ----------------------------------------------------------- ResultQuery<R> fetchSize(int size); // The maximum number of rows to be fetched by JDBC // ----------------------------------------------------------- ResultQuery<R> maxRows(int rows); }
Using ResultSet concurrency with ExecuteListeners
An example of why you might want to manually set a ResultSet's concurrency flag to something non-default is given here:
DSL.using(new DefaultConfiguration() .set(connection) .set(SQLDialect.ORACLE) .set(DefaultExecuteListenerProvider.providers( new DefaultExecuteListener() { @Override public void recordStart(ExecuteContext ctx) { try { // Change values in the cursor before reading a record ctx.resultSet().updateString(BOOK.TITLE.getName(), "New Title"); ctx.resultSet().updateRow(); } catch (SQLException e) { throw new DataAccessException("Exception", e); } } } ) )) .select(BOOK.ID, BOOK.TITLE) .from(BOOK) .orderBy(BOOK.ID) .resultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE) .resultSetConcurrency(ResultSet.CONCUR_UPDATABLE) .fetch(BOOK.TITLE);
In the above example, your custom ExecuteListener callback is triggered before jOOQ loads a new Record
from the JDBC ResultSet
. With the concurrency being set to ResultSet.CONCUR_UPDATABLE
, you can now modify the database cursor through the standard JDBC ResultSet
API.
5.7. Using JDBC batch operations
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
With JDBC, you can easily execute several statements at once using the addBatch() method. Essentially, there are two modes in JDBC
- Execute several queries without bind values
- Execute one query several times with bind values
Using JDBC
In code, this looks like the following snippet:
// 1. several queries // ------------------ try (Statement stmt = connection.createStatement()) { stmt.addBatch("INSERT INTO author(id, first_name, last_name) VALUES (1, 'Erich', 'Gamma')"); stmt.addBatch("INSERT INTO author(id, first_name, last_name) VALUES (2, 'Richard', 'Helm')"); stmt.addBatch("INSERT INTO author(id, first_name, last_name) VALUES (3, 'Ralph', 'Johnson')"); stmt.addBatch("INSERT INTO author(id, first_name, last_name) VALUES (4, 'John', 'Vlissides')"); int[] result = stmt.executeBatch(); } // 2. a single query // ----------------- try (PreparedStatement stmt = connection.prepareStatement("INSERT INTO author(id, first_name, last_name) VALUES (?, ?, ?)")) { stmt.setInt(1, 1); stmt.setString(2, "Erich"); stmt.setString(3, "Gamma"); stmt.addBatch(); stmt.setInt(1, 2); stmt.setString(2, "Richard"); stmt.setString(3, "Helm"); stmt.addBatch(); stmt.setInt(1, 3); stmt.setString(2, "Ralph"); stmt.setString(3, "Johnson"); stmt.addBatch(); stmt.setInt(1, 4); stmt.setString(2, "John"); stmt.setString(3, "Vlissides"); stmt.addBatch(); int[] result = stmt.executeBatch(); }
Using jOOQ
jOOQ supports executing queries in batch mode as follows:
// 1. several queries // ------------------ create.batch( create.insertInto(AUTHOR, ID, FIRST_NAME, LAST_NAME).values(1, "Erich" , "Gamma" ), create.insertInto(AUTHOR, ID, FIRST_NAME, LAST_NAME).values(2, "Richard", "Helm" ), create.insertInto(AUTHOR, ID, FIRST_NAME, LAST_NAME).values(3, "Ralph" , "Johnson" ), create.insertInto(AUTHOR, ID, FIRST_NAME, LAST_NAME).values(4, "John" , "Vlissides")) .execute(); // 2. a single query // ----------------- create.batch(create.insertInto(AUTHOR, ID, FIRST_NAME, LAST_NAME ).values((Integer) null, null, null)) .bind( 1 , "Erich" , "Gamma" ) .bind( 2 , "Richard" , "Helm" ) .bind( 3 , "Ralph" , "Johnson" ) .bind( 4 , "John" , "Vlissides") .execute();
When creating a batch execution with a single query and multiple bind values, you will still have to provide jOOQ with dummy bind values for the original query. In the above example, these are set to null
. For subsequent calls to bind()
, there will be no type safety provided by jOOQ.
5.8. Sequence execution
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Most databases support sequences of some sort, to provide you with unique values to be used for primary keys and other enumerations. If you're using jOOQ's code generator, it will generate a sequence object per sequence for you. There are two ways of using such a sequence object:
Standalone calls to sequences
Instead of actually phrasing a select statement, you can also use the DSLContext's convenience methods:
// Fetch the next value from a sequence BigInteger nextID = create.nextval(S_AUTHOR_ID); // Fetch the current value from a sequence BigInteger currID = create.currval(S_AUTHOR_ID);
Inlining sequence references in SQL
You can inline sequence references in jOOQ SQL statements. The following are examples of how to do that:
// Reference the sequence in a SELECT statement: Field<BigInteger> s = S_AUTHOR_ID.nextval(); BigInteger nextID = create.select(s).fetchOne(s); // Reference the sequence in an INSERT statement: create.insertInto(AUTHOR, AUTHOR.ID, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .values(S_AUTHOR_ID.nextval(), val("William"), val("Shakespeare")) .execute();
For more info about inlining sequence references in SQL statements, please refer to the manual's section about sequences and serials.
5.9. Stored procedures and functions
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Many RDBMS support the concept of "routines", usually calling them procedures and/or functions. These concepts have been around in programming languages for a while, also outside of databases. Famous languages distinguishing procedures from functions are:
- Ada
- BASIC
- Pascal
- etc...
The general distinction between (stored) procedures and (stored) functions can be summarised like this:
Procedures
- Are called using JDBC CallableStatement
- Have no return value
- Usually support OUT parameters
Functions
- Can be used in SQL statements
- Have a return value
- Usually don't support OUT parameters
Exceptions to these rules
- DB2, H2, and HSQLDB don't allow for JDBC escape syntax when calling functions. Functions must be used in a SELECT statement
- H2 only knows functions (without OUT parameters)
- Oracle functions may have OUT parameters
- Oracle knows functions that must not be used in SQL statements for transactional reasons
- Postgres only knows functions (with all features combined). OUT parameters can also be interpreted as return values, which is quite elegant/surprising, depending on your taste
- The Sybase jconn3 JDBC driver doesn't handle null values correctly when using the JDBC escape syntax on functions
In general, it can be said that the field of routines (procedures / functions) is far from being standardised in modern RDBMS even if the SQL:2008 standard specifies things quite well. Every database has its ways and JDBC only provides little abstraction over the great variety of procedures / functions implementations, especially when advanced data types such as cursors / UDT's / arrays are involved.
To simplify things a little bit, jOOQ handles both procedures and functions the same way, using a more general org.jooq.Routine type.
Using jOOQ for standalone calls to stored procedures and functions
If you're using jOOQ's code generator, it will generate org.jooq.Routine objects for you. Let's consider the following example:
-- Check whether there is an author in AUTHOR by that name and get his ID CREATE OR REPLACE PROCEDURE author_exists (author_name VARCHAR2, result OUT NUMBER, id OUT NUMBER);
The generated artefacts can then be used as follows:
// Make an explicit call to the generated procedure object: AuthorExists procedure = new AuthorExists(); // All IN and IN OUT parameters generate setters procedure.setAuthorName("Paulo"); procedure.execute(configuration); // All OUT and IN OUT parameters generate getters assertEquals(new BigDecimal("1"), procedure.getResult()); assertEquals(new BigDecimal("2"), procedure.getId();
But you can also call the procedure using a generated convenience method in a global Routines class:
// The generated Routines class contains static methods for every procedure. // Results are also returned in a generated object, holding getters for every OUT or IN OUT parameter. AuthorExists procedure = Routines.authorExists(configuration, "Paulo"); // All OUT and IN OUT parameters generate getters assertEquals(new BigDecimal("1"), procedure.getResult()); assertEquals(new BigDecimal("2"), procedure.getId();
For more details about code generation for procedures, see the manual's section about procedures and code generation.
Inlining stored function references in SQL
Unlike procedures, functions can be inlined in SQL statements to generate column expressions or table expressions, if you're using unnesting operators. Assume you have a function like this:
-- Check whether there is an author in AUTHOR by that name and get his ID CREATE OR REPLACE FUNCTION author_exists (author_name VARCHAR2) RETURN NUMBER;
The generated artefacts can then be used as follows:
-- This is the rendered SQL SELECT AUTHOR_EXISTS('Paulo') FROM DUAL
// Use the static-imported method from Routines: boolean exists = create.select(authorExists("Paulo")).fetchOne(0, boolean.class);
For more info about inlining stored function references in SQL statements, please refer to the manual's section about user-defined functions.
5.9.1. Oracle Packages
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Oracle uses the concept of a PACKAGE to group several procedures/functions into a sort of namespace. The SQL 92 standard talks about "modules", to represent this concept, even if this is rarely implemented as such. This is reflected in jOOQ by the use of Java sub-packages in the source code generation destination package. Every Oracle package will be reflected by
- A Java package holding classes for formal Java representations of the procedure/function in that package
- A Java class holding convenience methods to facilitate calling those procedures/functions
Apart from this, the generated source code looks exactly like the one for standalone procedures/functions.
For more details about code generation for procedures and packages see the manual's section about procedures and code generation.
5.9.2. Oracle member procedures
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Oracle UDTs can have object-oriented structures including member functions and procedures. With Oracle, you can do things like this:
CREATE OR REPLACE TYPE u_author_type AS OBJECT ( id NUMBER(7), first_name VARCHAR2(50), last_name VARCHAR2(50), MEMBER PROCEDURE LOAD, MEMBER FUNCTION counBOOKs RETURN NUMBER ) -- The type body is omitted for the example
These member functions and procedures can simply be mapped to Java methods:
// Create an empty, attached UDT record from the DSLContext UAuthorType author = create.newRecord(U_AUTHOR_TYPE); // Set the author ID and load the record using the LOAD procedure author.setId(1); author.load(); // The record is now updated with the LOAD implementation's content assertNotNull(author.getFirstName()); assertNotNull(author.getLastName());
For more details about code generation for UDTs see the manual's section about user-defined types and code generation.
5.10. Exporting to XML, CSV, JSON, HTML, Text
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
If you are using jOOQ for scripting purposes or in a slim, unlayered application server, you might be interested in using jOOQ's exporting functionality (see also the importing functionality). You can export any Result<Record> into the formats discussed in the subsequent chapters of the manual
5.10.1. Exporting XML
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
// Fetch books and format them as XML String xml = create.selectFrom(BOOK).fetch().formatXML();
The above query will result in an XML document looking like the following one:
<result xmlns="http://www.jooq.org/xsd/jooq-export-3.10.0.xsd"> <fields> <field name="ID" type="INTEGER"/> <field name="AUTHOR_ID" type="INTEGER"/> <field name="TITLE" type="VARCHAR"/> </fields> <records> <record> <value field="ID">1</value> <value field="AUTHOR_ID">1</value> <value field="TITLE">1984</value> </record> <record> <value field="ID">2</value> <value field="AUTHOR_ID">1</value> <value field="TITLE">Animal Farm</value> </record> </records> </result>
The same result as an org.w3c.dom.Document can be obtained using the Result.intoXML() method:
// Fetch books and format them as XML Document xml = create.selectFrom(BOOK).fetch().intoXML();
See the XSD schema definition here, for a formal definition of the XML export format:
http://www.jooq.org/xsd/jooq-export-3.10.0.xsd
5.10.2. Exporting CSV
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
// Fetch books and format them as CSV String csv = create.selectFrom(BOOK).fetch().formatCSV();
The above query will result in a CSV document looking like the following one:
ID,AUTHOR_ID,TITLE 1,1,1984 2,1,Animal Farm
In addition to the standard behaviour, you can also specify a separator character, as well as a special string to represent NULL values (which cannot be represented in standard CSV):
// Use ";" as the separator character String csv = create.selectFrom(BOOK).fetch().formatCSV(';'); // Specify "{null}" as a representation for NULL values String csv = create.selectFrom(BOOK).fetch().formatCSV(';', "{null}");
5.10.3. Exporting JSON
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
// Fetch books and format them as JSON String json = create.selectFrom(BOOK).fetch().formatJSON();
The above query will result in a JSON document looking like the following one:
{"fields":[{"name":"field-1","type":"type-1"}, {"name":"field-2","type":"type-2"}, ..., {"name":"field-n","type":"type-n"}], "records":[[value-1-1,value-1-2,...,value-1-n], [value-2-1,value-2-2,...,value-2-n]]}
Note: This format has changed in jOOQ 2.6.0
5.10.4. Exporting HTML
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
// Fetch books and format them as HTML String html = create.selectFrom(BOOK).fetch().formatHTML();
The above query will result in an HTML document looking like the following one
<table> <thead> <tr> <th>ID</th> <th>AUTHOR_ID</th> <th>TITLE</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>1</td> <td>1984</td> </tr> <tr> <td>2</td> <td>1</td> <td>Animal Farm</td> </tr> </tbody> </table>
5.10.5. Exporting Text
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
// Fetch books and format them as text String text = create.selectFrom(BOOK).fetch().format();
The above query will result in a text document looking like the following one
+---+---------+-----------+ | ID|AUTHOR_ID|TITLE | +---+---------+-----------+ | 1| 1|1984 | | 2| 1|Animal Farm| +---+---------+-----------+
A simple text representation can also be obtained by calling toString() on a Result object. See also the manual's section about DEBUG logging
5.11. Importing data
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
jOOQ's loader API can be used to import tabular data into a table from a variety of data sources. It offers a simplified API to solve common data import challenges such as:
- Mapping different data sources, like CSV, JSON, XML, records to SQL tables
- Specifying behaviour when duplicate keys are encountered
- Fine tuning batch, bulk, and commit sizes
- Error handling
5.11.1. The Loader API
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The loader API is implemented like any other DSL statement in jOOQ, following a few steps:
create.loadInto(TARGET_TABLE) .[options] .[source and source to target mapping] .[listeners] .[execution and error handling]
For example:
create.loadInto(BOOK) // Options .onDuplicateKeyError() .bulkAll() .batchAll() .commitAll() // Source and source to target mapping .loadCSV(inputStream) .fields(BOOK.ID, BOOK.AUTHOR_ID, BOOK.TITLE) // Listeners .onRow(ctx -> { /* ... */ }) // Execution and error handling .execute() .errors() .forEach(e -> { /* ... */ });
See the following sections for details about each step:
5.11.2. Import options
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Prior to specifing data sources, data source independent loading options can be specified. These include throttling, duplicate handling, and error handling:
5.11.2.1. Throttling
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Not all RDBMS offer the same optimisation capabilities. Please refer to your database manual to learn how these tuning capabilities may affect your data import performance. Also, actual measurements may help improve these numbers. Do not optimise prematurely, or based on assumptions. Always measure if your optimisation has the desired effect!
The commit size
Committing a transaction can be a costly operation if done too often, or not often enough. If there are too many commits, this can lead to a lot of logging overhead on the server. If a too many changes are left uncommitted for too long, there may be too much locking in 2PL transaction models, or log contention in MVCC transaction models. An empirically discovered, optimal commit size that leads to committing e.g. 1000 rows (or 10000, or 100, please measure what works best for you) may produce best results.
There are 3 possible, mutually exclusive configurations of specifying the batch size:
create.loadInto(BOOK) // Commit all statements (batch, bulk, or not) in a single large transaction. .commitAll() // Put up to 32 statements (batch, bulk, or not) in a transaction. .commitAfter(32) // Commit each statement (batch, bulk, or not) in a transaction, just like commitAfter(1) .commitEach() // Do not commit any statement, leave committing to client code .commitNone() .loadCSV(inputstream) .fields(BOOK.ID, BOOK.AUTHOR_ID, BOOK.TITLE) .execute();
5.11.2.2. Duplicate handling
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
When importing data, some data may already be present and needs to be updated. jOOQ supports a variety of UPSERT style statements.
create.loadInto(BOOK) // Insert each row using INSERT .. ON DUPLICATE KEY UPDATE .onDuplicateKeyUpdate() // Insert each row using INSERT .. ON DUPLICATE KEY IGNORE .onDuplicateKeyIgnore() // Use ordinary INSERT statements, which will produce errors on duplicate keys .onDuplicateKeyError() .loadCSV(inputstream) .fields(BOOK.ID, BOOK.AUTHOR_ID, BOOK.TITLE) .execute();
5.11.2.3. Error handling
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
When importing large amounts of data, errors may be inevitable and may need to be processed after the import, without impacting the entire import. In these cases, it may be useful to specify error handling. In all cases, errors will be reported after the execution of the import process:
create.loadInto(BOOK) // Ignore any errors and continue inserting. Errors will be reported nonetheless. .onErrorIgnore() // Abort the import upon encountering the first error. .onErrorAbort() .loadCSV(inputstream) .fields(BOOK.ID, BOOK.AUTHOR_ID, BOOK.TITLE) .execute();
5.11.3. Import data sources
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Different types of data sources are supported by jOOQ in the same formats as the export API. These include:
5.11.3.1. Importing CSV
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The below CSV data represents two author records that may have been exported previously, by jOOQ's exporting functionality, and then modified in Microsoft Excel or any other spreadsheet tool:
ID,AUTHOR_ID,TITLE <-- Note the CSV header. By default, the first line is ignored 1,1,1984 2,1,Animal Farm
The following examples show how to map source and target tables.
// Specify fields from the target table to be matched with fields from the source CSV by position. // Positional matching is independent of the presence of a header row in the CSV content. create.loadInto(BOOK) .loadCSV(inputstream, encoding) .fields(BOOK.ID, BOOK.AUTHOR_ID, BOOK.TITLE) .execute(); // Use "null" field placeholders to ignore source columns by position. create.loadInto(BOOK) .loadCSV(inputstream, encoding) .fields(BOOK.ID, null, BOOK.TITLE) .execute();
CSV specific options
You may pass one of the following flags to specify how the CSV content should be parsed:
create.loadInto(BOOK) .loadCSV(inputstream, encoding) .fields(BOOK.ID, BOOK.AUTHOR_ID, BOOK.TITLE) // Ignore a certain number of header rows. By default, this is 1. .ignoreRows(1) // The quote character for use with string content containing quotes or separators. By default, this is " .quote('"') // The separator character that separates columns. By default, this is , .separator(',') // The null string encoding, which allows for distinguishing between empty strings and null. By default, there is no null string. .nullString("{null}") .execute();
5.11.3.2. Importing JSON
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
The below JSON data represents two author records that may have been exported previously, by jOOQ's exporting functionality:
{"fields" :[{"name":"ID","type":"INTEGER"}, {"name":"AUTHOR_ID","type":"INTEGER"}, {"name":"TITLE","type":"VARCHAR"}], "records":[[1,1,"1984"], [2,1,"Animal Farm"]]}
The following examples show how to map source data and target table.
// Specify fields from the target table to be matched with fields from the source JSON array by position. // Positional matching is independent of the presence of a header information in the JSON content. create.loadInto(BOOK) .loadJSON(inputstream, encoding) .fields(BOOK.ID, BOOK.AUTHOR_ID, BOOK.TITLE) .execute(); // Use "null" field placeholders to ignore source columns by position. create.loadInto(BOOK) .loadJSON(inputstream, encoding) .fields(BOOK.ID, null, BOOK.TITLE) .execute();
No other, JSON-specific options are currently available.
5.11.3.3. Importing XML
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
This is not yet supported
5.11.4. Import result and error handling
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
After completed execution, a number of diagnostics are available to implement error handling:
Loader<?> loader = create.loadInto(BOOK) .loadCSV(inputstream, encoding) .fields(BOOK.ID, BOOK.AUTHOR_ID, BOOK.TITLE) .execute(); // The number of processed rows int processed = loader.processed(); // The number of stored rows (INSERT or UPDATE) int stored = loader.stored(); // The number of ignored rows (due to errors, or duplicate rule) int ignored = loader.ignored(); // The errors that may have occurred during loading List<LoaderError> errors = loader.errors(); LoaderError error = errors.get(0); // The exception that caused the error DataAccessException exception = error.exception(); // The row that caused the error int rowIndex = error.rowIndex(); String[] row = error.row(); // The query that caused the error Query query = error.query();
5.12. CRUD with UpdatableRecords
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
Your database application probably consists of 50% - 80% CRUD, whereas only the remaining 20% - 50% of querying is actual querying. Most often, you will operate on records of tables without using any advanced relational concepts. This is called CRUD for
CRUD always uses the same patterns, regardless of the nature of underlying tables. This again, leads to a lot of boilerplate code, if you have to issue your statements yourself. Like Hibernate / JPA and other ORMs, jOOQ facilitates CRUD using a specific API involving org.jooq.UpdatableRecord types.
Primary keys and updatability
In normalised databases, every table has a primary key by which a