Available in versions: Dev (3.20) | Latest (3.19) | 3.18 | 3.17 | 3.16 | 3.15 | 3.14 | 3.13 | 3.12 | 3.11 | 3.10

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.

  • Reference

    This section is a reference for elements in this manual

Table of contents

1.
Copyright, License, and Trademarks
2.
Getting started with jOOQ
2.1.
How to read this manual
2.2.
The sample database used in this manual
2.3.
Different use cases for jOOQ
2.3.1.
jOOQ as a SQL builder without code generation
2.3.2.
jOOQ as a SQL builder with code generation
2.3.3.
jOOQ as a SQL executor
2.3.4.
jOOQ for CRUD
2.3.5.
jOOQ for PROs
2.4.
Downloading jOOQ
2.5.
Tutorials
2.5.1.
jOOQ in 7 easy steps
2.5.1.1.
Step 1: Preparation
2.5.1.2.
Step 2: Your database
2.5.1.3.
Step 3: Code generation
2.5.1.4.
Step 4: Connect to your database
2.5.1.5.
Step 5: Querying
2.5.1.6.
Step 6: Iterating
2.5.1.7.
Step 7: Explore!
2.5.2.
Using jOOQ with Flyway
2.5.3.
Using jOOQ with jbang
2.6.
jOOQ and Java 8
2.7.
jOOQ and Scala
2.8.
jOOQ and Groovy
2.9.
jOOQ and Kotlin
2.10.
jOOQ and NoSQL
2.11.
jOOQ and JPA
2.12.
Build your own
2.13.
jOOQ and backwards-compatibility
3.
SQL building
3.1.
The query DSL type
3.1.1.
DSL subclasses
3.2.
The DSLContext API
3.2.1.
SQL Dialect
3.2.2.
SQL Dialect Family
3.2.3.
SQL Dialect Category (new)
3.2.4.
Connection vs. DataSource
3.2.5.
Custom data
3.2.6.
Custom ExecuteListeners
3.2.7.
Custom Unwrappers
3.2.8.
Custom Settings
3.2.8.1.
Auto-attach Records
3.2.8.2.
Auto-inline bind values
3.2.8.3.
Backslash Escaping
3.2.8.4.
Batch size
3.2.8.5.
Computed column emulation (new)
3.2.8.6.
Diagnostics Connection (new)
3.2.8.7.
Diagnostics Logging (new)
3.2.8.8.
Dialect compatibility
3.2.8.9.
Execute Logging
3.2.8.10.
Execute Logging SQL Exceptions (new)
3.2.8.11.
Fetch Warnings
3.2.8.12.
GROUP_CONCAT Configuration
3.2.8.13.
Identifier style
3.2.8.14.
Implicit join type
3.2.8.15.
Inline Threshold
3.2.8.16.
IN-list Padding
3.2.8.17.
Interpreter Configuration
3.2.8.18.
JDBC Flags
3.2.8.19.
Keyword style
3.2.8.20.
Listener Invocation Order
3.2.8.21.
Locales
3.2.8.22.
Map JPA Annotations
3.2.8.23.
Object qualification
3.2.8.24.
Object qualification for columns (new)
3.2.8.25.
Optimistic Locking
3.2.8.26.
Parameter name prefix
3.2.8.27.
Parameter types
3.2.8.28.
Parser Configuration
3.2.8.29.
Readonly column behaviour
3.2.8.30.
Reflection caching
3.2.8.31.
Return all columns on store
3.2.8.32.
Return computed columns on store (new)
3.2.8.33.
Return DEFAULT columns on store (new)
3.2.8.34.
Return Identity Value On Store
3.2.8.35.
Runtime catalog, schema and table mapping
3.2.8.36.
Scalar subqueries for stored functions
3.2.8.37.
SEEK clause implementation (new)
3.2.8.38.
Statement Type
3.2.8.39.
Updatable Primary Keys
3.2.9.
Thread safety
3.3.
The DSL API
3.3.1.
Mutability (historic)
3.4.
The model API
3.4.1.
Design
3.4.2.
Traversal
3.4.3.
Replacement
3.4.3.1.
Listening Replacer
3.4.3.2.
Decomposing Replacer
3.4.4.
The historic model API
3.5.
SQL Statements (DML)
3.5.1.
The WITH clause
3.5.2.
The WITH RECURSIVE clause
3.5.3.
The SELECT statement
3.5.3.1.
SELECT clause
3.5.3.1.1.
Projection type safety
3.5.3.1.2.
SelectField
3.5.3.1.3.
Tables as SelectField
3.5.3.1.4.
SELECT *
3.5.3.1.5.
SELECT * EXCEPT (...)
3.5.3.1.6.
SELECT DISTINCT
3.5.3.1.7.
SELECT DISTINCT ON
3.5.3.1.8.
Convenience methods
3.5.3.2.
FROM clause
3.5.3.3.
JOIN operator
3.5.3.4.
Implicit path JOIN
3.5.3.5.
WHERE clause
3.5.3.6.
CONNECT BY clause
3.5.3.7.
GROUP BY clause
3.5.3.7.1.
GROUP BY columns
3.5.3.7.2.
GROUP BY column index (new)
3.5.3.7.3.
GROUP BY tables
3.5.3.7.4.
GROUP BY ROLLUP
3.5.3.7.5.
GROUP BY CUBE
3.5.3.7.6.
GROUP BY GROUPING SETS
3.5.3.7.7.
GROUP BY empty grouping set
3.5.3.8.
HAVING clause
3.5.3.9.
WINDOW clauseWINDOW clause
3.5.3.10.
QUALIFY clause
3.5.3.11.
ORDER BY clause
3.5.3.11.1.
Ordering by field index
3.5.3.11.2.
Ordering and NULLS
3.5.3.11.3.
Ordering using CASE expressions
3.5.3.11.4.
Oracle's ORDER SIBLINGS BY clause
3.5.3.12.
LIMIT .. OFFSET clause
3.5.3.13.
WITH TIES clause
3.5.3.14.
SEEK clause
3.5.3.15.
FOR clause
3.5.3.16.
FOR UPDATE clause
3.5.3.17.
Set operations
3.5.3.17.1.
Type safety
3.5.3.17.2.
Projection rowtype
3.5.3.17.3.
Differences to standard SQL
3.5.3.17.4.
UNION
3.5.3.17.5.
INTERSECT
3.5.3.17.6.
EXCEPT
3.5.3.18.
Lexical and logical SELECT clause order
3.5.4.
The INSERT statement
3.5.4.1.
INSERT .. VALUES
3.5.4.2.
INSERT .. DEFAULT VALUES
3.5.4.3.
INSERT .. SET
3.5.4.4.
INSERT .. SELECT
3.5.4.5.
INSERT .. ON DUPLICATE KEY UPDATE
3.5.4.6.
INSERT .. ON DUPLICATE KEY UPDATE .. EXCLUDED
3.5.4.7.
INSERT .. ON DUPLICATE KEY IGNORE
3.5.4.8.
INSERT .. ON CONFLICT .. EXCLUDED
3.5.4.9.
INSERT .. ON CONFLICT
3.5.4.10.
INSERT .. RETURNING
3.5.5.
The UPDATE statement
3.5.5.1.
UPDATE .. SET
3.5.5.2.
UPDATE .. SET ROW
3.5.5.3.
UPDATE .. FROM
3.5.5.4.
UPDATE .. WHERE
3.5.5.5.
UPDATE .. ORDER BY .. LIMIT
3.5.5.6.
UPDATE .. RETURNING
3.5.6.
The DELETE statement
3.5.6.1.
DELETE .. USING
3.5.6.2.
DELETE .. WHERE
3.5.6.3.
DELETE .. ORDER BY .. LIMIT
3.5.6.4.
UPDATE .. RETURNING
3.5.7.
The MERGE statement
3.6.
SQL Statements (DDL)
3.6.1.
The ALTER statement
3.6.1.1.
ALTER DATABASE
3.6.1.1.1.
ALTER DATABASE .. RENAME
3.6.1.1.2.
ALTER DATABASE IF EXISTS
3.6.1.2.
ALTER DOMAIN
3.6.1.2.1.
ALTER DOMAIN .. RENAME
3.6.1.2.2.
ALTER DOMAIN .. SET DEFAULT
3.6.1.2.3.
ALTER DOMAIN .. DROP DEFAULT
3.6.1.2.4.
ALTER DOMAIN .. SET NOT NULL
3.6.1.2.5.
ALTER DOMAIN .. DROP NOT NULL
3.6.1.2.6.
ALTER DOMAIN .. ADD CONSTRAINT
3.6.1.2.7.
ALTER DOMAIN .. RENAME CONSTRAINT
3.6.1.2.8.
ALTER DOMAIN .. RENAME CONSTRAINT IF EXISTS
3.6.1.2.9.
ALTER DOMAIN .. DROP CONSTRAINT
3.6.1.2.10.
ALTER DOMAIN .. DROP CONSTRAINT IF EXISTS
3.6.1.2.11.
ALTER DOMAIN IF EXISTS
3.6.1.3.
ALTER INDEX
3.6.1.3.1.
ALTER INDEX .. RENAME
3.6.1.3.2.
ALTER INDEX IF EXISTS
3.6.1.4.
ALTER SCHEMA
3.6.1.4.1.
ALTER SCHEMA .. RENAME
3.6.1.4.2.
ALTER SCHEMA IF EXISTS
3.6.1.5.
ALTER SEQUENCE
3.6.1.5.1.
ALTER SEQUENCE .. RENAME
3.6.1.5.2.
ALTER SEQUENCE .. CACHE
3.6.1.5.3.
ALTER SEQUENCE .. CYCLE
3.6.1.5.4.
ALTER SEQUENCE .. MINVALUE
3.6.1.5.5.
ALTER SEQUENCE .. t reaches its MAXVALUE
3.6.1.5.6.
ALTER SEQUENCE .. INCREMENT BY
3.6.1.5.7.
ALTER SEQUENCE .. START WITH
3.6.1.5.8.
ALTER SEQUENCE .. RESTART
3.6.1.5.9.
ALTER SEQUENCE IF EXISTS
3.6.1.6.
ALTER TABLE
3.6.1.6.1.
ALTER TABLE .. ADD COLUMN
3.6.1.6.2.
ALTER TABLE .. ADD COLUMN .. FIRST, BEFORE, AFTER
3.6.1.6.3.
ALTER TABLE .. ADD COLUMNS
3.6.1.6.4.
ALTER TABLE .. ADD COLUMN IF NOT EXISTS
3.6.1.6.5.
ALTER TABLE .. ADD PRIMARY KEY
3.6.1.6.6.
ALTER TABLE .. ADD UNIQUE
3.6.1.6.7.
ALTER TABLE .. ADD FOREIGN KEY
3.6.1.6.8.
ALTER TABLE .. ADD CHECK
3.6.1.6.9.
ALTER TABLE .. RENAME
3.6.1.6.10.
ALTER TABLE .. COMMENT
3.6.1.6.11.
ALTER TABLE .. ALTER COLUMN .. SET DEFAULT
3.6.1.6.12.
ALTER TABLE .. ALTER COLUMN .. DROP DEFAULT
3.6.1.6.13.
ALTER TABLE .. ALTER COLUMN .. SET NOT NULL
3.6.1.6.14.
ALTER TABLE .. ALTER COLUMN .. DROP NOT NULL
3.6.1.6.15.
ALTER TABLE .. ALTER COLUMN .. SET TYPE
3.6.1.6.16.
ALTER TABLE .. ALTER CONSTRAINT .. ENFORCED
3.6.1.6.17.
ALTER TABLE .. ALTER CONSTRAINT .. NOT ENFORCED
3.6.1.6.18.
ALTER TABLE .. RENAME COLUMN
3.6.1.6.19.
ALTER TABLE .. RENAME CONSTRAINT
3.6.1.6.20.
ALTER TABLE .. RENAME INDEX
3.6.1.6.21.
ALTER TABLE .. DROP COLUMN
3.6.1.6.22.
ALTER TABLE .. DROP COLUMN RESTRICT
3.6.1.6.23.
ALTER TABLE .. DROP COLUMN CASCADE
3.6.1.6.24.
ALTER TABLE .. DROP COLUMNS
3.6.1.6.25.
ALTER TABLE .. ADD COLUMN IF NOT EXISTS
3.6.1.6.26.
ALTER TABLE .. DROP CONSTRAINT
3.6.1.6.27.
ALTER TABLE .. DROP PRIMARY KEY
3.6.1.6.28.
ALTER TABLE .. DROP UNIQUE
3.6.1.6.29.
ALTER TABLE .. DROP FOREIGN KEY
3.6.1.6.30.
ALTER TABLE .. DROP CONSTRAINT IF EXISTS
3.6.1.6.31.
ALTER TABLE IF EXISTS
3.6.1.7.
ALTER TYPE
3.6.1.7.1.
ALTER TYPE .. RENAME
3.6.1.7.2.
ALTER TYPE .. for enum alterations
3.6.1.8.
ALTER VIEW
3.6.1.8.1.
ALTER VIEW .. AS (new)
3.6.1.8.2.
ALTER VIEW .. COMMENT
3.6.1.8.3.
ALTER VIEW .. RENAME
3.6.1.8.4.
ALTER VIEW IF EXISTS
3.6.2.
The COMMENT statement
3.6.2.1.
COMMENT ON TABLE
3.6.2.2.
COMMENT ON VIEW
3.6.2.3.
COMMENT ON COLUMN
3.6.3.
The CREATE statement
3.6.3.1.
CREATE DATABASE
3.6.3.2.
CREATE DOMAIN
3.6.3.3.
CREATE FUNCTION
3.6.3.3.1.
Scalar functions
3.6.3.3.2.
CREATE OR REPLACE FUNCTION
3.6.3.3.3.
SQL data access characteristics
3.6.3.3.4.
DETERMINISTIC characteristic
3.6.3.3.5.
ON NULL INPUT characteristic
3.6.3.4.
CREATE INDEX
3.6.3.5.
CREATE PROCEDURE
3.6.3.5.1.
CREATE OR REPLACE PROCEDURE
3.6.3.5.2.
SQL data access characteristics
3.6.3.6.
CREATE SCHEMA
3.6.3.7.
CREATE SEQUENCE
3.6.3.7.1.
CREATE SEQUENCE IF NOT EXISTS
3.6.3.7.2.
CREATE SEQUENCE .. CACHE
3.6.3.7.3.
CREATE SEQUENCE .. CYCLE
3.6.3.7.4.
CREATE SEQUENCE .. MINVALUE
3.6.3.7.5.
CREATE SEQUENCE .. MAXVALUE
3.6.3.7.6.
CREATE SEQUENCE .. INCREMENT BY
3.6.3.7.7.
CREATE SEQUENCE .. START WITH
3.6.3.8.
CREATE TABLE
3.6.3.8.1.
Columns
3.6.3.8.2.
Nullability
3.6.3.8.3.
Defaults
3.6.3.8.4.
Identities
3.6.3.8.5.
Computed columns
3.6.3.8.6.
Primary key
3.6.3.8.7.
Unique constraints
3.6.3.8.8.
Foreign keys
3.6.3.8.9.
Check constraints
3.6.3.8.10.
From a SELECT
3.6.3.8.11.
Temporary tables
3.6.3.9.
CREATE TRIGGER
3.6.3.9.1.
Events
3.6.3.9.2.
REFERENCING clause
3.6.3.9.3.
STATEMENT vs ROW triggers
3.6.3.9.4.
WHEN clause
3.6.3.10.
CREATE TYPE
3.6.3.11.
CREATE VIEW
3.6.3.11.1.
CREATE OR REPLACE VIEW
3.6.3.11.2.
WITH CHECK OPTION
3.6.3.11.3.
WITH READ ONLY
3.6.4.
The DROP statement
3.6.4.1.
DROP DATABASE
3.6.4.1.1.
DROP DATABASE IF EXISTS
3.6.4.2.
DROP DOMAIN
3.6.4.2.1.
DROP DOMAIN IF EXISTS
3.6.4.3.
DROP FUNCTION
3.6.4.3.1.
DROP FUNCTION IF EXISTS
3.6.4.4.
DROP INDEX
3.6.4.4.1.
DROP INDEX IF EXISTS
3.6.4.5.
DROP PROCEDURE
3.6.4.5.1.
DROP PROCEDURE IF EXISTS
3.6.4.6.
DROP SCHEMA
3.6.4.6.1.
DROP SCHEMA IF EXISTS
3.6.4.7.
DROP SEQUENCE
3.6.4.7.1.
DROP SEQUENCE IF EXISTS
3.6.4.8.
DROP TABLE
3.6.4.8.1.
DROP TABLE IF EXISTS
3.6.4.9.
DROP TRIGGER
3.6.4.9.1.
DROP TRIGGER IF EXISTS
3.6.4.10.
DROP TYPE
3.6.4.10.1.
DROP TYPE IF EXISTS
3.6.4.11.
DROP VIEW
3.6.4.11.1.
DROP VIEW IF EXISTS
3.6.5.
The GRANT statement
3.6.6.
The REVOKE statement
3.6.7.
The SET statement
3.6.7.1.
SET CATALOG
3.6.7.2.
SET SCHEMA
3.6.8.
The TRUNCATE statement
3.6.9.
Generating DDL from objects
3.7.
Transactional statements (new)
3.7.1.
START TRANSACTION statement (new)
3.7.2.
COMMIT statement (new)
3.7.3.
ROLLBACK statement (new)
3.7.4.
SAVEPOINT statement (new)
3.7.5.
RELEASE SAVEPOINT statement (new)
3.8.
Procedural statements
3.8.1.
Block statement
3.8.2.
CALL statement
3.8.3.
CONTINUE statement
3.8.4.
EXECUTE statement
3.8.5.
EXIT statement
3.8.6.
FOR statement
3.8.7.
GOTO statement
3.8.8.
IF statement
3.8.9.
Labels
3.8.10.
LOOP statement
3.8.11.
REPEAT statement
3.8.12.
SIGNAL
3.8.13.
Variables
3.8.14.
WHILE statement
3.9.
Catalog and schema expressions
3.10.
Table expressions
3.10.1.
Generated Tables
3.10.2.
Aliased Tables
3.10.2.1.
Aliased generated tables
3.10.2.2.
Aliased table expressions
3.10.2.3.
Aliased joined tables (new)
3.10.2.4.
Derived column lists
3.10.2.5.
Unnamed derived tables
3.10.3.
Joined tables
3.10.3.1.
CROSS JOIN
3.10.3.2.
INNER JOIN
3.10.3.3.
OUTER JOIN
3.10.3.4.
SEMI JOIN
3.10.3.5.
ANTI JOIN
3.10.3.6.
ON clause
3.10.3.7.
ON KEY clause
3.10.3.8.
USING clause
3.10.3.9.
NATURAL clause
3.10.3.10.
LATERAL
3.10.3.11.
APPLY
3.10.3.12.
PARTITION BY
3.10.4.
The VALUES() table constructor
3.10.5.
Derived tables
3.10.6.
Inline derived tables
3.10.7.
The Oracle PIVOT clause
3.10.8.
Relational division
3.10.9.
Array and cursor unnesting
3.10.10.
Table-valued functions
3.10.11.
GENERATE_SERIES
3.10.12.
WITH ORDINALITY (new)
3.10.13.
JSON_TABLE
3.10.14.
XMLTABLE
3.10.15.
The DUAL table
3.10.16.
Temporal tables
3.10.17.
Data change delta tables
3.11.
Column expressions
3.11.1.
Table columns
3.11.1.1.
Generated table columns
3.11.1.2.
Dereferenced table columns
3.11.1.3.
Named table columns
3.11.2.
Aliased columns
3.11.3.
Cast expressions
3.11.4.
Datatype coercions
3.11.5.
Readonly columns
3.11.6.
Computed columns
3.11.7.
Collations
3.11.8.
Arithmetic expressions
3.11.9.
String concatenation
3.11.10.
Case sensitivity with strings
3.11.11.
General functions
3.11.11.1.
CHOOSE
3.11.11.2.
COALESCE
3.11.11.3.
DECODE
3.11.11.4.
IIF
3.11.11.5.
NULLIF
3.11.11.6.
NVL
3.11.11.7.
NVL2
3.11.12.
Numeric functions
3.11.12.1.
ABS
3.11.12.2.
ACOS
3.11.12.3.
ASIN
3.11.12.4.
ATAN
3.11.12.5.
ATAN2
3.11.12.6.
CEIL
3.11.12.7.
COS
3.11.12.8.
COSH
3.11.12.9.
COT
3.11.12.10.
COTH
3.11.12.11.
DEG
3.11.12.12.
E
3.11.12.13.
EXP
3.11.12.14.
FLOOR
3.11.12.15.
GREATEST
3.11.12.16.
LEAST
3.11.12.17.
LN
3.11.12.18.
LOG
3.11.12.19.
LOG10
3.11.12.20.
NEG
3.11.12.21.
PI
3.11.12.22.
POWER
3.11.12.23.
RAD
3.11.12.24.
RAND
3.11.12.25.
ROUND
3.11.12.26.
SIGN
3.11.12.27.
SIN
3.11.12.28.
SINH
3.11.12.29.
SQRT
3.11.12.30.
SQUARE
3.11.12.31.
TAN
3.11.12.32.
TANH
3.11.12.33.
TRUNC
3.11.12.34.
WIDTH_BUCKET
3.11.13.
Bitwise functions
3.11.13.1.
BIT_AND
3.11.13.2.
BIT_COUNT
3.11.13.3.
BIT_GET (new)
3.11.13.4.
BIT_NAND
3.11.13.5.
BIT_NOR
3.11.13.6.
BIT_NOT
3.11.13.7.
BIT_OR
3.11.13.8.
BIT_SET (new)
3.11.13.9.
BIT_XNOR
3.11.13.10.
BIT_XOR
3.11.13.11.
SHL
3.11.13.12.
SHR
3.11.14.
String functions
3.11.14.1.
ASCII
3.11.14.2.
BIT_LENGTH
3.11.14.3.
CHR
3.11.14.4.
CONCAT (|| operator)
3.11.14.5.
DIGITS
3.11.14.6.
LEFT
3.11.14.7.
LENGTH
3.11.14.8.
LOWER
3.11.14.9.
LPAD
3.11.14.10.
LTRIM
3.11.14.11.
MD5
3.11.14.12.
MID
3.11.14.13.
OCTET_LENGTH
3.11.14.14.
OVERLAY
3.11.14.15.
POSITION
3.11.14.16.
REGEXP_REPLACE
3.11.14.17.
REPEAT
3.11.14.18.
REPLACE
3.11.14.19.
REVERSE
3.11.14.20.
RIGHT
3.11.14.21.
RPAD
3.11.14.22.
RTRIM
3.11.14.23.
SPACE
3.11.14.24.
SPLIT_PART
3.11.14.25.
SUBSTRING
3.11.14.26.
SUBSTRING_INDEX
3.11.14.27.
TO_CHAR
3.11.14.28.
TO_HEX
3.11.14.29.
TRANSLATE
3.11.14.30.
TRIM
3.11.14.31.
UPPER
3.11.14.32.
UUID
3.11.15.
Datetime functions
3.11.15.1.
CENTURY
3.11.15.2.
CURRENT_DATE
3.11.15.3.
CURRENT_LOCALDATE
3.11.15.4.
CURRENT_LOCALDATETIME
3.11.15.5.
CURRENT_LOCALTIME
3.11.15.6.
CURRENT_OFFSETDATETIME
3.11.15.7.
CURRENT_OFFSETTIME
3.11.15.8.
CURRENT_TIME
3.11.15.9.
CURRENT_TIMESTAMP
3.11.15.10.
DATE
3.11.15.11.
DATEADD
3.11.15.12.
DATEDIFF
3.11.15.13.
DATESUB
3.11.15.14.
DAY
3.11.15.15.
DAY_OF_YEAR
3.11.15.16.
DECADE
3.11.15.17.
EPOCH
3.11.15.18.
EXTRACT
3.11.15.19.
HOUR
3.11.15.20.
ISO_DAY_OF_WEEK
3.11.15.21.
LOCALDATE
3.11.15.22.
LOCALDATEADD
3.11.15.23.
LOCALDATESUB
3.11.15.24.
LOCALDATETIME
3.11.15.25.
LOCALDATETIMEADD
3.11.15.26.
LOCALDATETIMESUB
3.11.15.27.
LOCALTIME
3.11.15.28.
MILLENNIUM
3.11.15.29.
MINUTE
3.11.15.30.
MONTH
3.11.15.31.
QUARTER
3.11.15.32.
SECOND
3.11.15.33.
TIME
3.11.15.34.
TIMESTAMP
3.11.15.35.
TIMESTAMPADD
3.11.15.36.
TIMESTAMPSUB
3.11.15.37.
TO_DATE
3.11.15.38.
TO_LOCALDATE
3.11.15.39.
TO_LOCALDATETIME
3.11.15.40.
TO_TIMESTAMP
3.11.15.41.
TRUNC
3.11.15.42.
YEAR
3.11.16.
ARRAY functions
3.11.16.1.
ARRAY_APPEND (|| operator) (new)
3.11.16.2.
ARRAY_CONCAT (|| operator) (new)
3.11.16.3.
ARRAY_GET
3.11.16.4.
ARRAY_OVERLAP (new)
3.11.16.5.
ARRAY_PREPEND (|| operator) (new)
3.11.16.6.
ARRAY_REMOVE (new)
3.11.16.7.
ARRAY_REPLACE (new)
3.11.16.8.
ARRAY constructor
3.11.16.9.
ARRAY constructor from subquery
3.11.16.10.
CARDINALITY
3.11.17.
JSON functions
3.11.17.1.
JSON_ARRAY
3.11.17.2.
JSON_INSERT (new)
3.11.17.3.
JSON_KEYS (new)
3.11.17.4.
JSON_OBJECT
3.11.17.5.
JSON_REMOVE (new)
3.11.17.6.
JSON_REPLACE (new)
3.11.17.7.
JSON_SET (new)
3.11.17.8.
JSON_VALUE
3.11.17.9.
JSON array element access with -> or ->> (new)
3.11.17.10.
JSON object attribute access with -> or ->> (new)
3.11.18.
XML functions
3.11.18.1.
XMLATTRIBUTES
3.11.18.2.
XMLCOMMENT
3.11.18.3.
XMLCONCAT
3.11.18.4.
XMLDOCUMENT
3.11.18.5.
XMLELEMENT
3.11.18.6.
XMLFOREST
3.11.18.7.
XMLPARSE
3.11.18.8.
XMLPI
3.11.18.9.
XMLQUERY
3.11.18.10.
XMLSERIALIZE
3.11.19.
System functions
3.11.19.1.
CURRENT_SCHEMA
3.11.19.2.
CURRENT_USER
3.11.20.
Spatial functions
3.11.20.1.
ST_Area
3.11.20.2.
ST_AsText
3.11.20.3.
ST_Centroid
3.11.20.4.
ST_Difference
3.11.20.5.
ST_Distance
3.11.20.6.
ST_EndPoint
3.11.20.7.
ST_ExteriorRing
3.11.20.8.
ST_GeometryN
3.11.20.9.
ST_GeometryType
3.11.20.10.
ST_GeomFromText
3.11.20.11.
ST_InteriorRingN
3.11.20.12.
ST_Intersection
3.11.20.13.
ST_Length
3.11.20.14.
ST_NumGeometries
3.11.20.15.
ST_NumInteriorRings
3.11.20.16.
ST_NumPoints
3.11.20.17.
ST_PointN
3.11.20.18.
ST_SRID
3.11.20.19.
ST_StartPoint
3.11.20.20.
ST_Union
3.11.20.21.
ST_X
3.11.20.22.
ST_Y
3.11.20.23.
ST_Z
3.11.21.
Aggregate functions
3.11.21.1.
Grouping
3.11.21.2.
Distinctness
3.11.21.3.
Filtering
3.11.21.4.
Ordering
3.11.21.5.
Ordering WITHIN GROUP
3.11.21.6.
Keeping
3.11.21.7.
ANY_VALUE
3.11.21.8.
ARRAY_AGG
3.11.21.9.
AVG
3.11.21.10.
BIT_AND_AGG
3.11.21.11.
BIT_NAND_AGG
3.11.21.12.
BIT_NOR_AGG
3.11.21.13.
BIT_OR_AGG
3.11.21.14.
BIT_XOR_AGG
3.11.21.15.
BIT_XNOR_AGG
3.11.21.16.
BOOL_AND
3.11.21.17.
BOOL_OR
3.11.21.18.
COLLECT
3.11.21.19.
COUNT
3.11.21.20.
CUME_DIST
3.11.21.21.
DENSE_RANK
3.11.21.22.
EVERY
3.11.21.23.
GROUP_CONCAT
3.11.21.24.
JSON_ARRAYAGG
3.11.21.25.
JSON_OBJECTAGG
3.11.21.26.
LISTAGG
3.11.21.27.
MAX
3.11.21.28.
MEDIAN
3.11.21.29.
MIN
3.11.21.30.
MODE
3.11.21.31.
MULTISET_AGG
3.11.21.32.
PERCENT_RANK
3.11.21.33.
PERCENTILE_CONT
3.11.21.34.
PERCENTILE_DISC
3.11.21.35.
PRODUCT
3.11.21.36.
RANK
3.11.21.37.
SUM
3.11.21.38.
XMLAGG
3.11.22.
Window functions
3.11.22.1.
PARTITION BY
3.11.22.2.
ORDER BY
3.11.22.3.
ROWS, RANGE, GROUPS (frame clause)
3.11.22.4.
EXCLUDE
3.11.22.5.
NULL treatment
3.11.22.6.
FROM FIRST, FROM LAST
3.11.22.7.
Nested aggregate functions
3.11.22.8.
Window aggregation
3.11.22.9.
Window ordered aggregate
3.11.22.10.
ROW_NUMBER
3.11.22.11.
RANK
3.11.22.12.
DENSE_RANK
3.11.22.13.
PERCENT_RANK
3.11.22.14.
CUME_DIST
3.11.22.15.
NTILE
3.11.22.16.
LEAD
3.11.22.17.
LAG
3.11.22.18.
FIRST_VALUE
3.11.22.19.
LAST_VALUE
3.11.22.20.
NTH_VALUE
3.11.23.
User-defined functions
3.11.24.
User-defined aggregate functions
3.11.25.
The CASE expression
3.11.26.
Sequences and serials
3.11.27.
Scalar subqueries
3.11.28.
ARRAY value constructor
3.11.29.
MULTISET value constructor
3.11.30.
Tuples or row value expressions
3.11.31.
Nested records
3.12.
Conditional expressions
3.12.1.
Condition building
3.12.2.
TRUE and FALSE condition
3.12.3.
BOOLEAN columns
3.12.4.
AND, OR, NOT boolean operators
3.12.5.
Boolean operator precedence
3.12.6.
XOR boolean operator (new)
3.12.7.
Comparison predicate
3.12.8.
Comparison predicate (degree > 1)
3.12.9.
Quantified comparison predicate
3.12.10.
BETWEEN predicate
3.12.11.
BETWEEN predicate (degree > 1)
3.12.12.
DISTINCT predicate
3.12.13.
DISTINCT predicate (degree > 1)
3.12.14.
DOCUMENT predicate
3.12.15.
EXISTS predicate
3.12.16.
IN predicate
3.12.17.
IN predicate (degree > 1)
3.12.18.
JSON predicate
3.12.19.
JSON_EXISTS predicate
3.12.20.
LIKE predicate
3.12.21.
Quantified LIKE predicate
3.12.22.
NULL predicate
3.12.23.
NULL predicate (degree > 1)
3.12.24.
OVERLAPS predicate
3.12.25.
SIMILAR TO predicate
3.12.26.
Spatial predicates
3.12.26.1.
ST_Contains
3.12.26.2.
ST_Crosses
3.12.26.3.
ST_Disjoint
3.12.26.4.
ST_Equals
3.12.26.5.
ST_Intersects
3.12.26.6.
ST_IsClosed
3.12.26.7.
ST_IsEmpty
3.12.26.8.
ST_Overlaps
3.12.26.9.
ST_Touches
3.12.26.10.
ST_Within
3.12.27.
UNIQUE predicate
3.12.28.
XMLEXISTS predicate
3.12.29.
Query By Example (QBE)
3.13.
Operator precedence
3.14.
Synthetic SQL clauses
3.15.
Dynamic SQL
3.15.1.
Optional column expressions
3.15.2.
Optional conditional expressions
3.16.
Plain SQL
3.17.
Plain SQL Templating Language
3.18.
Plain SQL raw templates (new)
3.19.
Hints
3.19.1.
MySQL hints
3.19.1.1.
Index hints
3.19.1.2.
STRAIGHT_JOIN
3.19.1.3.
Oracle style hints in MySQL
3.19.2.
Oracle hints
3.19.3.
SQL Server hints
3.19.3.1.
WITH
3.19.3.2.
OPTION
3.20.
SQL Parser
3.20.1.
SQL Parser API
3.20.2.
SQL Parser CLI
3.20.3.
SQL Parser Listener
3.20.4.
SQL translator
3.20.5.
SQL Parser Grammar
3.21.
SQL interpreter
3.22.
Schema diff
3.23.
Schema diff CLI
3.24.
Names and identifiers
3.25.
Bind values and parameters
3.25.1.
Indexed parameters
3.25.2.
Named parameters
3.25.3.
Inlined parameters
3.25.4.
SQL injection
3.26.
QueryParts
3.26.1.
SQL rendering
3.26.2.
Declaration vs reference
3.26.3.
Pretty printing SQL
3.26.4.
Variable binding
3.26.5.
Custom data type bindings
3.26.6.
Custom syntax elements
3.26.7.
Plain SQL QueryParts
3.26.8.
Serializability
3.26.9.
SQL transformation
3.26.9.1.
ANSI JOIN to table lists
3.26.9.2.
Table lists to ANSI JOIN
3.26.9.3.
ROWNUM to LIMIT
3.26.9.4.
QUALIFY to derived table
3.26.9.5.
IN condition subquery with LIMIT to derived table
3.26.9.6.
GROUP BY <column index> (new)
3.26.9.7.
Inline CTE (new)
3.26.9.8.
Unnecessary arithmetic expressions
3.26.9.9.
Pattern based transformation
3.26.9.9.1.
AND to NOT IN
3.26.9.9.2.
Arithmetic comparisons (new)
3.26.9.9.3.
Arithmetic expressions
3.26.9.9.4.
BIT_GET function (new)
3.26.9.9.5.
BIT_SET function (new)
3.26.9.9.6.
CASE searched to CASE simple (new)
3.26.9.9.7.
CASE to CASE abbreviation (new)
3.26.9.9.8.
CASE with DISTINCT FROM to DECODE (new)
3.26.9.9.9.
CASE with ELSE NULL (new)
3.26.9.9.10.
COUNT(*) scalar subquery comparison
3.26.9.9.11.
COUNT(const) (new)
3.26.9.9.12.
COUNT(expr) scalar subquery comparison
3.26.9.9.13.
DISTINCT FROM NULL (new)
3.26.9.9.14.
Empty scalar subquery
3.26.9.9.15.
Flatten CASE (new)
3.26.9.9.16.
Flatten CASE abbreviations (new)
3.26.9.9.17.
Flatten DECODE (new)
3.26.9.9.18.
Hyperbolic functions
3.26.9.9.19.
Idempotent function repetition
3.26.9.9.20.
Inverse hyperbolic functions
3.26.9.9.21.
Logarithmic functions
3.26.9.9.22.
Merge AND predicates
3.26.9.9.23.
Merge BIT_NOT with BIT_NAND
3.26.9.9.24.
Merge BIT_NOT with BIT_NOR
3.26.9.9.25.
Merge BIT_NOT with BIT_XNOR
3.26.9.9.26.
Merge CASE .. WHEN and ELSE clauses (new)
3.26.9.9.27.
Merge CASE .. WHEN clauses (new)
3.26.9.9.28.
Merge IN predicates
3.26.9.9.29.
Merge NOT with comparison predicates
3.26.9.9.30.
Merge NOT with DISTINCT predicate
3.26.9.9.31.
Merge OR predicates
3.26.9.9.32.
Merge range predicates
3.26.9.9.33.
Normalise associative operations
3.26.9.9.34.
Normalise fields compared to values
3.26.9.9.35.
Normalise IN list with single element to comparison
3.26.9.9.36.
NOT AND (new)
3.26.9.9.37.
NOT OR (new)
3.26.9.9.38.
NULL ON NULL INPUT (new)
3.26.9.9.39.
OR to IN
3.26.9.9.40.
Repeated arithmetic negation
3.26.9.9.41.
Repeated bitwise negation
3.26.9.9.42.
Repeated NOT
3.26.9.9.43.
Simplify CASE abbreviations (new)
3.26.9.9.44.
Trigonometric functions
3.26.9.9.45.
Trim
3.26.9.9.46.
Trivial bitwise operations (new)
3.26.9.9.47.
Trivial CASE abbreviations
3.26.9.9.48.
Trivial predicates
3.26.9.9.49.
Unnecessary DISTINCT (new)
3.26.9.9.50.
Unnecessary EXISTS subquery clauses (new)
3.26.9.9.51.
Unnecessary GROUP BY expressions (new)
3.26.9.9.52.
Unnecessary INNER JOIN (new)
3.26.9.9.53.
Unnecessary ORDER BY expressions (new)
3.26.9.9.54.
Unnecessary scalar subquery (new)
3.26.9.9.55.
Unreachable CASE clauses (new)
3.26.9.9.56.
Unreachable DECODE clauses (new)
3.26.10.
Custom SQL transformation with VisitListener
3.26.10.1.
Example: Logging abbreviated bind values
3.27.
Zero-based vs one-based APIs
3.28.
SQL building in Kotlin
3.28.1.
Kotlin MULTISET Collectors
3.28.2.
Kotlin ResultQuery Collectors (new)
3.28.3.
Kotlin BOOLEAN value expressions
3.28.4.
Kotlin ARRAY access
3.28.5.
Kotlin JSON access (new)
3.28.6.
Kotlin coroutine support
3.29.
SQL building in Scala
4.
SQL execution
4.1.
Comparison between jOOQ and JDBC
4.2.
Query vs. ResultQuery
4.3.
Fetching
4.3.1.
Record vs. TableRecord
4.3.2.
Record1 to Record22
4.3.3.
Arrays, Maps and Lists
4.3.4.
ResultQuery as Iterable
4.3.5.
RecordMapper
4.3.6.
POJOs
4.3.7.
RecordMapperProvider
4.3.8.
Ad-hoc Converter
4.3.9.
ConverterProvider
4.3.10.
Lazy fetching
4.3.11.
Lazy fetching with Streams
4.3.12.
Many fetching
4.3.13.
Later fetching
4.3.14.
Reactive Fetching
4.3.15.
ResultSet fetching
4.3.16.
Auto data type conversion
4.3.17.
Custom data type conversion
4.3.18.
Context Converter (new)
4.4.
Static statements vs. Prepared Statements
4.5.
Reusing a Query's PreparedStatement
4.6.
JDBC flags
4.7.
Using JDBC batch operations
4.8.
Sequence execution
4.9.
Stored procedures and functions
4.9.1.
Oracle Packages
4.9.2.
Oracle member procedures
4.10.
Exporting to XML, CSV, JSON, HTML, Text, Charts
4.10.1.
Exporting XML
4.10.2.
Exporting CSV
4.10.3.
Exporting JSON
4.10.4.
Exporting HTML
4.10.5.
Exporting Text
4.10.6.
Exporting Charts
4.10.7.
FormattingProvider
4.11.
Importing data
4.11.1.
The Loader API
4.11.2.
Import options
4.11.2.1.
Throttling
4.11.2.2.
Duplicate handling
4.11.2.3.
Error handling
4.11.3.
Import data sources
4.11.3.1.
Importing CSV
4.11.3.2.
Importing JSON
4.11.3.3.
Importing records
4.11.3.4.
Importing arrays
4.11.3.5.
Importing XML
4.11.4.
Import listeners
4.11.5.
Import result and error handling
4.12.
CRUD with UpdatableRecords
4.12.1.
Simple CRUD
4.12.2.
Records' internal flags
4.12.3.
IDENTITY values
4.12.4.
Navigation methods
4.12.5.
Non-updatable records
4.12.6.
Optimistic locking
4.12.7.
Batch execution
4.12.8.
CRUD SPI: RecordListener
4.13.
DAOs
4.14.
Transaction management
4.15.
Exception handling
4.16.
ExecuteListeners
4.17.
Database meta data
4.17.1.
JDBC meta data
4.17.2.
Interpreted meta data
4.17.3.
XML meta data
4.17.4.
Generated meta data
4.18.
JDBC Connection
4.19.
Batched Connection
4.20.
Mocking Connection
4.21.
Mock File Database
4.22.
Parsing Connection
4.23.
Diagnostics
4.23.1.
Too Many Rows
4.23.2.
Too Many Columns
4.23.3.
Duplicate Statements
4.23.4.
Repeated statements
4.23.5.
Consecutive aggregation (new)
4.23.6.
WasNull calls
4.23.7.
Concatenation in predicates (new)
4.23.8.
Possibly wrong expressions (new)
4.23.9.
Trivial condition
4.23.10.
Transform patterns (new)
4.24.
Logging with LoggerListener
4.25.
Logging with SQLExceptionLoggerListener (new)
4.26.
Logging Connection
4.27.
Performance considerations
4.28.
Alternative execution models
4.28.1.
Using jOOQ with Spring's JdbcTemplate
4.28.2.
Using jOOQ with JPA
4.28.2.1.
Using jOOQ with JPA Native Query
4.28.2.2.
Using jOOQ with JPA entities
4.28.2.3.
Using jOOQ with JPA EntityResult
5.
Code generation
5.1.
Configuration and setup of the generator
5.2.
Advanced generator configuration
5.2.1.
Logging
5.2.2.
Error handling
5.2.3.
Jdbc
5.2.4.
Generator
5.2.5.
Database
5.2.5.1.
Database name and properties
5.2.5.2.
RegexFlags
5.2.5.3.
Includes and Excludes
5.2.5.4.
Include object types
5.2.5.5.
Record Version and Timestamp Fields
5.2.5.6.
Comments
5.2.5.7.
Synthetic objects
5.2.5.7.1.
Synthetic columns
5.2.5.7.2.
Synthetic readonly columns
5.2.5.7.3.
Synthetic readonly ROWIDs
5.2.5.7.4.
Synthetic identities
5.2.5.7.5.
Synthetic primary keys
5.2.5.7.6.
Synthetic unique keys
5.2.5.7.7.
Synthetic foreign keys
5.2.5.8.
Date as timestamp
5.2.5.9.
Ignore procedure return values (deprecated)
5.2.5.10.
Readonly columns
5.2.5.11.
Unsigned types
5.2.5.12.
Catalog and schema mapping
5.2.5.13.
Catalog and schema version providers
5.2.5.14.
Custom ordering of generated code
5.2.5.15.
Forced types
5.2.5.15.1.
Matching of forced types
5.2.5.15.2.
Data type rewriting
5.2.5.15.3.
Qualified converters
5.2.5.15.4.
Inline converters
5.2.5.15.5.
Lambda converters
5.2.5.15.6.
Auto converters (new)
5.2.5.15.7.
Enum converters
5.2.5.15.8.
Jackson converters
5.2.5.15.9.
JAXB converters
5.2.5.15.10.
Data type bindings
5.2.5.15.11.
Client side computed columns
5.2.5.15.12.
Audit columns
5.2.5.15.13.
Visibility Modifier (per forced type)
5.2.5.16.
Table valued functions
5.2.6.
Generate
5.2.6.1.
Annotations
5.2.6.2.
Covariant overrides
5.2.6.2.1.
Overriding as()
5.2.6.2.2.
Overriding rename()
5.2.6.3.
Default catalog and schema
5.2.6.4.
Extended types
5.2.6.5.
Fluent setters
5.2.6.6.
Fully Qualified Types
5.2.6.7.
Global Artefacts
5.2.6.8.
Implicit JOIN paths
5.2.6.9.
Java Time Types
5.2.6.10.
Serial Version UID
5.2.6.11.
Sources
5.2.6.12.
Text blocks
5.2.6.13.
Visibility Modifier (global)
5.2.6.14.
Whitespace (newlines and indentation)
5.2.6.15.
Zero Scale Decimal Types
5.2.7.
Output target configuration
5.3.
Custom code generation dependencies
5.4.
Programmatic generator configuration
5.5.
Custom generator strategies
5.6.
Matcher strategies
5.6.1.
MatcherRule
5.6.2.
Matching catalogs
5.6.3.
Matching schemas
5.6.4.
Matching tables
5.6.5.
Matching fields
5.6.6.
Matching routines
5.6.7.
Matching sequences
5.6.8.
Matching enums
5.6.9.
Matching embeddables
5.6.10.
Matcher examples
5.7.
Custom code sections
5.8.
Generated global artefacts
5.9.
Generated tables
5.10.
Generated records
5.11.
Generated POJOs
5.12.
Generated Interfaces
5.13.
Generated DAOs
5.14.
Generated sequences
5.15.
Generated procedures
5.16.
Generated domains
5.17.
Generated UDTs
5.18.
Data type extensions
5.18.1.
PostgreSQL extensions
5.19.
Embeddable types
5.19.1.
Configuration
5.19.2.
Overlapping embeddable types
5.19.3.
Field replacement
5.19.4.
Embedded keys
5.19.5.
Embedded domains
5.20.
Mapping generated catalogs and schemas
5.21.
Code generation for large schemas
5.22.
Code generation and version control
5.23.
JPADatabase: Code generation from entities
5.24.
XMLDatabase: Code generation from XML files
5.25.
DDLDatabase: Code generation from SQL files
5.26.
LiquibaseDatabase: Code generation from Liquibase XML, YAML, JSON files
5.27.
XMLGenerator: Generating XML
5.28.
KotlinGenerator
5.29.
ScalaGenerator
5.30.
Running the code generator with Maven
5.31.
Running the code generator with Ant
5.32.
Running the code generator with Gradle
5.33.
System properties governing code generation
5.34.
Features requiring generated code
6.
Tools
6.1.
API validation using the Checker Framework or Error Prone
6.2.
jOOQ Refaster
6.3.
jOOQ Console
7.
Coming from JPA
7.1.
Set based thinking
7.2.
Database first
7.3.
Eager or lazy loading
7.4.
First level cache and second level cache
7.5.
Embeddable
7.6.
AttributeConverter
7.7.
User types
7.8.
Implicit JOIN
7.9.
@OneToOne or @ManyToOne
7.10.
@OneToMany or @ManyToMany
8.
Reference
8.1.
Supported RDBMS
8.2.
Commercial only features
8.3.
Experimental features
8.4.
Data types
8.4.1.
BLOBs and CLOBs
8.4.2.
BOOLEAN data type
8.4.3.
Unsigned integer types
8.4.4.
INTERVAL data types
8.4.5.
JSON data types
8.4.6.
XML data types
8.4.7.
Spatial data types
8.4.8.
CURSOR data types
8.4.9.
ARRAY and TABLE data types
8.4.10.
Oracle DATE data type
8.4.11.
Domains
8.5.
SQL to DSL mapping rules
8.6.
Quality Assurance
8.7.
Security
8.7.1.
SQL Injection
8.7.2.
Debug logging
8.7.3.
Exception message
8.7.4.
Contact
8.8.
Migrating to jOOQ 3.0
8.9.
Don't do this
8.9.1.
jOOQ: Implementing the DSL types
8.9.2.
jOOQ: Referencing the Step types
8.9.3.
Schema: NULL columns
8.9.4.
Schema: Unnamed constraints
8.9.5.
Schema: Unnecessary surrogate keys
8.9.6.
Schema: Wrong data types
8.9.7.
SQL: COUNT(*) instead of EXISTS()
8.9.8.
SQL: N+1
8.9.9.
SQL: NATURAL JOIN or JOIN USING
8.9.10.
SQL: NOT IN predicate
8.9.11.
SQL: ORDER BY [column index]
8.9.12.
SQL: Rely on implicit ordering
8.9.13.
SQL: SELECT *
8.9.14.
SQL: SELECT DISTINCT
8.9.15.
SQL: Unnecessary UNION instead of UNION ALL
8.10.
The most important jOOQ types
8.11.
Credits
 

1. 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

 https://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

    https://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 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.
  • 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
  • HANA is a trademark of SAP SE
  • 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
  • Liquibase is a trademark by Datical, Inc
  • Flyway is a trademark by Red Gate Software Ltd
  • 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
  • Dennis Neufeld
  • 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
  • Logan Hauspie
  • Luc Marchaud
  • Lukas Eder
  • Matti Tahvonen
  • Michael Doberenz
  • Michael Simons
  • Michał Kołodziejski
  • Miguel Gonzalez Sanchez
  • Mustafa Yücel
  • Nathaniel Fischer
  • Nicholas Chong W.B.
  • Octavia Togami
  • Oliver Flege
  • Per Lundberg
  • Peter Ertl
  • Richard Bradley
  • Robin Stocker
  • Roland Weisleder
  • Samy Deghou
  • Sander Plas
  • Sean Wellington
  • Sergey Epik
  • Sergey Zhuravlev
  • Stanislas Nanchen
  • Stephan Schroevers
  • Sugiharto Lim
  • Sven Jacobs
  • Szymon Jachim
  • Terence Zhang
  • Thomas Darimont
  • Timothy Wilson
  • Timur Shaidullin
  • Tsukasa Kitachi
  • Victor Bronstein
  • Victor Z. Peng
  • Vladimir Kulev
  • Vladimir Vinogradov
  • Vojtech Polivka
  • Wang Gaoyuan
  • Wyke Oskar
  • Xavier Oliver
  • Zoltan Tamasi

See the following website for details about contributing to jOOQ:
https://www.jooq.org/legal/contributions

 

2. 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.

 

2.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 // com.example.generated.Tables.BOOK.TITLE, com.example.generated.Tables.AUTHOR.LAST_NAME
FK_BOOK_AUTHOR               // 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.

 

2.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 );
 

2.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.

 

2.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 java.sql.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.
 

2.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 java.sql.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.
 

2.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
 

2.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
 

2.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.

 

2.4. Downloading jOOQ

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

jOOQ is distributed over 3 main channels:

The ZIP file

If you choose to download jOOQ over the website, you will be able to download a ZIP file with the following layout:

  • maven-deploy.bat: A Windows batch script to deploy artifacts to a maven repository
  • maven-deploy.sh: A bash script to deploy artifacts to a maven repository
  • maven-install.bat: A Windows batch script to install artifacts to the local maven repository
  • maven-install.sh: A bash script to install artifacts to the local maven repository

The website hosts the latest versions of the jOOQ Open Source Edition as well as all the historic versions of the commercial jOOQ editions including snapshot builds of all distributions that are available to paying customers only.

The commercial artifact repository

The commercial artifact repository hosts all the historic versions of the commercial jOOQ editions including snapshot builds of all distributions that are available to paying customers only.

Below is information regarding how to include these dependencies in Maven / Gradle:

Maven
Gradle (Kotlin)
Gradle (Groovy)

settings.xml

<server>
    <id>jooq-pro</id>
    <username>[your licensee email]</username>
    <password>[your license key]</password>
</server>

pom.xml

<repositories>
    <repository>
        <id>central</id>
        <url>https://repo1.maven.org/maven2/</url>
    </repository>
    <!-- Other repositories ... -->

    <repository>
        <id>jooq-pro</id>
        <url>https://repo.jooq.org/repo</url>
    </repository>
</repositories>
<pluginRepositories>
    <pluginRepository>
        <id>central</id>
        <url>https://repo1.maven.org/maven2/</url>
    </pluginRepository>
    <!-- Other repositories ... -->

    <pluginRepository>
        <id>jooq-pro</id>
        <url>https://repo.jooq.org/repo</url>
    </pluginRepository>
</pluginRepositories>
// The jOOQ-codegen-gradle plugin has been introduced in version 3.19 only.
// The jOOQ-codegen-gradle plugin has been introduced in version 3.19 only.

Dependencies

Depending on the edition you're using, please declare the following dependencies in Maven or Gradle:

Maven
Gradle (Kotlin)
Gradle (Groovy)
<dependency>
    <!-- Use org.jooq              for the Open Source Edition
             org.jooq.pro          for commercial editions with Java 11 support,
             org.jooq.pro-java-8   for commercial editions with Java 8 support,
             org.jooq.pro-java-6   for commercial editions with Java 6 support,
             org.jooq.trial        for the free trial edition with Java 11 support,
             org.jooq.trial-java-8 for the free trial edition with Java 8 support,
             org.jooq.trial-java-6 for the free trial edition with Java 6 support
    
     Note: Only the Open Source Edition is hosted on Maven Central.
           Install the others locally using the provided scripts, or access them from here: https://repo.jooq.org
           See the JDK version support matrix here: https://www.jooq.org/download/support-matrix-jdk -->

    <groupId>org.jooq</groupId>
    <artifactId>jooq</artifactId>
    <version>3.18.20</version>
</dependency>
dependencies {
    // Use org.jooq                for the Open Source Edition
    //     org.jooq.pro            for commercial editions with Java 17 support,
    //     org.jooq.pro-java-8     for commercial editions with Java 8 support,
    //     org.jooq.pro-java-6     for commercial editions with Java 6 support,
    //     org.jooq.trial          for the free trial edition with Java 17 support,
    //     org.jooq.trial-java-8   for the free trial edition with Java 8 support,
    //     org.jooq.trial-java-6   for the free trial edition with Java 6 support
    //
    // Note: Only the Open Source Edition is hosted on Maven Central.
    //       Install the others locally using the provided scripts, or access them from here: https://repo.jooq.org
    //       See the JDK version support matrix here: https://www.jooq.org/download/support-matrix-jdk

    implementation("org.jooq:jooq:3.18.20")
}
dependencies {
    // Use org.jooq                for the Open Source Edition
    //     org.jooq.pro            for commercial editions with Java 17 support,
    //     org.jooq.pro-java-8     for commercial editions with Java 8 support,
    //     org.jooq.pro-java-6     for commercial editions with Java 6 support,
    //     org.jooq.trial          for the free trial edition with Java 17 support,
    //     org.jooq.trial-java-8   for the free trial edition with Java 8 support,
    //     org.jooq.trial-java-6   for the free trial edition with Java 6 support
    //
    // Note: Only the Open Source Edition is hosted on Maven Central.
    //       Install the others locally using the provided scripts, or access them from here: https://repo.jooq.org
    //       See the JDK version support matrix here: https://www.jooq.org/download/support-matrix-jdk

    implementation "org.jooq:jooq:3.18.20"
}
 

2.5. 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.

 

2.5.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.

 

2.5.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:

Open Source Edition

<dependency>
  <groupId>org.jooq</groupId>
  <artifactId>jooq</artifactId>
  <version>3.18.20</version>
</dependency>

<!-- These may not be required, unless you use the GenerationTool manually for code generation -->
<dependency>
  <groupId>org.jooq</groupId>
  <artifactId>jooq-meta</artifactId>
  <version>3.18.20</version>
</dependency>
<dependency>
  <groupId>org.jooq</groupId>
  <artifactId>jooq-codegen</artifactId>
  <version>3.18.20</version>
</dependency>

Commercial Editions (Java 17+)

<!-- Note: These aren't hosted on Maven Central. Import them manually from your distribution -->
<dependency>
  <groupId>org.jooq.pro</groupId>
  <artifactId>jooq</artifactId>
  <version>3.18.20</version>
</dependency>

<!-- These may not be required, unless you use the GenerationTool manually for code generation -->
<dependency>
  <groupId>org.jooq.pro</groupId>
  <artifactId>jooq-meta</artifactId>
  <version>3.18.20</version>
</dependency>
<dependency>
  <groupId>org.jooq.pro</groupId>
  <artifactId>jooq-codegen</artifactId>
  <version>3.18.20</version>
</dependency>

Commercial Editions (Java 11+)

<!-- Note: These aren't hosted on Maven Central. Import them manually from your distribution -->
<dependency>
  <groupId>org.jooq.pro-java-11</groupId>
  <artifactId>jooq</artifactId>
  <version>3.18.20</version>
</dependency>

<!-- These may not be required, unless you use the GenerationTool manually for code generation -->
<dependency>
  <groupId>org.jooq.pro-java-11</groupId>
  <artifactId>jooq-meta</artifactId>
  <version>3.18.20</version>
</dependency>
<dependency>
  <groupId>org.jooq.pro-java-11</groupId>
  <artifactId>jooq-codegen</artifactId>
  <version>3.18.20</version>
</dependency>

Commercial Editions (Java 8+)

<!-- Note: These aren't hosted on Maven Central. Import them manually from your distribution -->
<dependency>
  <groupId>org.jooq.pro-java-8</groupId>
  <artifactId>jooq</artifactId>
  <version>3.18.20</version>
</dependency>

<!-- These may not be required, unless you use the GenerationTool manually for code generation -->
<dependency>
  <groupId>org.jooq.pro-java-8</groupId>
  <artifactId>jooq-meta</artifactId>
  <version>3.18.20</version>
</dependency>
<dependency>
  <groupId>org.jooq.pro-java-8</groupId>
  <artifactId>jooq-codegen</artifactId>
  <version>3.18.20</version>
</dependency>

Commercial Editions (Free Trial, Java 17+)

<!-- Note: These aren't hosted on Maven Central. Import them manually from your distribution -->
<dependency>
  <groupId>org.jooq.trial</groupId>
  <artifactId>jooq</artifactId>
  <version>3.18.20</version>
</dependency>

<!-- These may not be required, unless you use the GenerationTool manually for code generation -->
<dependency>
  <groupId>org.jooq.trial</groupId>
  <artifactId>jooq-meta</artifactId>
  <version>3.18.20</version>
</dependency>
<dependency>
  <groupId>org.jooq.trial</groupId>
  <artifactId>jooq-codegen</artifactId>
  <version>3.18.20</version>
</dependency>

Commercial Editions (Free Trial, Java 11+)

<!-- Note: These aren't hosted on Maven Central. Import them manually from your distribution -->
<dependency>
  <groupId>org.jooq.trial-java-11</groupId>
  <artifactId>jooq</artifactId>
  <version>3.18.20</version>
</dependency>

<!-- These may not be required, unless you use the GenerationTool manually for code generation -->
<dependency>
  <groupId>org.jooq.trial-java-11</groupId>
  <artifactId>jooq-meta</artifactId>
  <version>3.18.20</version>
</dependency>
<dependency>
  <groupId>org.jooq.trial-java-11</groupId>
  <artifactId>jooq-codegen</artifactId>
  <version>3.18.20</version>
</dependency>

Commercial Editions (Free Trial, Java 8+)

<!-- Note: These aren't hosted on Maven Central. Import them manually from your distribution -->
<dependency>
  <groupId>org.jooq.trial-java-8</groupId>
  <artifactId>jooq</artifactId>
  <version>3.18.20</version>
</dependency>

<!-- These may not be required, unless you use the GenerationTool manually for code generation -->
<dependency>
  <groupId>org.jooq.trial-java-8</groupId>
  <artifactId>jooq-meta</artifactId>
  <version>3.18.20</version>
</dependency>
<dependency>
  <groupId>org.jooq.trial-java-8</groupId>
  <artifactId>jooq-codegen</artifactId>
  <version>3.18.20</version>
</dependency>

Commercial Editions (Java 6+)

<!-- Note: These aren't hosted on Maven Central. Import them manually from your distribution -->
<dependency>
  <groupId>org.jooq.pro-java-6</groupId>
  <artifactId>jooq</artifactId>
  <version>3.18.20</version>
</dependency>

<!-- These may not be required, unless you use the GenerationTool manually for code generation -->
<dependency>
  <groupId>org.jooq.pro-java-6</groupId>
  <artifactId>jooq-meta</artifactId>
  <version>3.18.20</version>
</dependency>
<dependency>
  <groupId>org.jooq.pro-java-6</groupId>
  <artifactId>jooq-codegen</artifactId>
  <version>3.18.20</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:
https://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!

 

2.5.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`)
);
 

2.5.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>
  <!-- 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.
         Supported generators:
         - org.jooq.codegen.JavaGenerator
         - org.jooq.codegen.KotlinGenerator
         - org.jooq.codegen.ScalaGenerator
         - org.jooq.codegen.Scala3Generator
         Defaults to org.jooq.codegen.JavaGenerator -->
    <name>org.jooq.codegen.JavaGenerator</name>

    <database>
      <!-- The database type. The format here is:
           org.jooq.meta.[database].[database]Database -->
      <name>org.jooq.meta.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 (<username/> or <user/>) 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:

<packageName/> - set this to the parent package you want to create for the generated classes. Setting the value to test.generated will cause the test.generated.tables.Author and test.generated.tables.records.AuthorRecord classes to be created

<directory/> - the directory to output the generated classes to.

Once you have the JAR files and library.xml in your temp directory, type this on a Windows machine:

java -classpath jooq-3.18.20.jar;^
jooq-meta-3.18.20.jar;^
jooq-codegen-3.18.20.jar;^
reactive-streams-1.0.3.jar;^
r2dbc-spi-1.0.0.RELEASE.jar;^
jakarta.xml.bind-api-3.0.0.jar;^
mysql-connector-java.jar;. ^
org.jooq.codegen.GenerationTool library.xml

... or type this on a UNIX / Linux / Mac system (colons instead of semi-colons):

java -classpath jooq-3.18.20.jar:\
jooq-meta-3.18.20.jar:\
jooq-codegen-3.18.20.jar:\
reactive-streams-1.0.3.jar:\
r2dbc-spi-1.0.0.RELEASE.jar:\
jakarta.xml.bind-api-3.0.0.jar:\
mysql-connector-java.jar:. \
org.jooq.codegen.GenerationTool library.xml
  • 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.18.20 is being used.
  • If you're using a linux style shell on Windows, but a Windows JDK/JRE, you still need to use semi-colons in your classpath! (;) In git-bash, you might have to quote your classpath ("jooq-3.18.20.jar;jooq-meta-3.18.20.jar;...")

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
 

2.5.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.

 

2.5.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.

 

2.5.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();
        }
    }
}
 

2.5.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:
https://ikaisays.com/2011/11/01/getting-started-with-jooq-a-tutorial/

 

2.5.2. Using jOOQ with Flyway

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Flyway - Database Migrations Made EasyWhen 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 updated, 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.

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>
    <!-- Use org.jooq              for the Open Source Edition
             org.jooq.pro          for commercial editions with Java 11 support,
             org.jooq.pro-java-8   for commercial editions with Java 8 support,
             org.jooq.pro-java-6   for commercial editions with Java 6 support,
             org.jooq.trial        for the free trial edition with Java 11 support,
             org.jooq.trial-java-8 for the free trial edition with Java 8 support,
             org.jooq.trial-java-6 for the free trial edition with Java 6 support
    
     Note: Only the Open Source Edition is hosted on Maven Central.
           Install the others locally using the provided scripts, or access them from here: https://repo.jooq.org
           See the JDK version support matrix here: https://www.jooq.org/download/support-matrix-jdk -->

    <groupId>org.jooq</groupId>
    <artifactId>jooq</artifactId>
    <version>3.18.20</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.197</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>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.11.0</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 may suggest 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>
    <!-- Use org.jooq              for the Open Source Edition
             org.jooq.pro          for commercial editions with Java 11 support,
             org.jooq.pro-java-8   for commercial editions with Java 8 support,
             org.jooq.pro-java-6   for commercial editions with Java 6 support,
             org.jooq.trial        for the free trial edition with Java 11 support,
             org.jooq.trial-java-8 for the free trial edition with Java 8 support,
             org.jooq.trial-java-6 for the free trial edition with Java 6 support
    
     Note: Only the Open Source Edition is hosted on Maven Central.
           Install the others locally using the provided scripts, or access them from here: https://repo.jooq.org
           See the JDK version support matrix here: https://www.jooq.org/download/support-matrix-jdk -->

    <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.18.20:generate (default) @ jooq-flyway-example ---
[INFO] --- jooq-codegen-maven:3.18.20: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));
        }
    }
}

Automation

The above steps can be automated in your build using another third party called testcontainers. Please look at this article here for examples on how to do that: https://blog.jooq.org/using-testcontainers-to-generate-jooq-code/

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.

 

2.5.3. Using jOOQ with jbang

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

jbang allows for quickly working with all sorts of Java libraries without the hassle of setting up environments, dependencies, etc. This catalog allows for using jOOQ's code generator right away on an existing database.

For more information on jbang, see:

An example

In a shell, type

git clone https://github.com/jOOQ/jbang-example
cd jbang-example
jbang Example.java

In order to re-generate the example code, e.g. when your schema changes, just type:

jbang codegen@jooq db.xml

If you prefer working with a pre-existing database, just edit the db.xml file and point it to your database. Add the JDBC driver dependency like this:

jbang --deps org.postgresql:postgresql:RELEASE codegen@jooq db.xml

To override the jOOQ version from the default RELEASE to a specific version, use

jbang -Djooq.version=<version> codegen@jooq db.xml
 

2.6. 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(");");
       }
   );
 

2.7. 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.examples.scala.h2.Tables._                           //
import org.jooq.scalaextensions.Conversions._                        // Import implicit defs for overloaded jOOQ/SQL operators
                                                                     //
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.

 

2.8. 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

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.

 

2.9. jOOQ and Kotlin

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

As any other library, jOOQ can be easily used in Kotlin, taking advantage of the many Kotlin language features such as for example:

  • Optional ";" at the end of a Kotlin statement
  • Type inference for local variables

A short example jOOQ application in Kotlin might look like this:

package org.jooq.example.kotlin

import java.util.Properties

import org.jooq.*
import org.jooq.impl.DSL
import org.jooq.impl.DSL.*

import org.jooq.example.db.h2.Tables.*

fun main(args: Array<String>) {

    val properties = Properties();
    properties.load(Properties::class.java.getResourceAsStream("/config.properties"));

    DSL.using(
        properties.getProperty("db.url"),
        properties.getProperty("db.username"),
        properties.getProperty("db.password")
    ).use { ctx ->
        val a = AUTHOR
        val b = BOOK

        ctx.select(a.FIRST_NAME, a.LAST_NAME, b.TITLE)
           .from(a)
           .join(b).on(a.ID.eq(b.AUTHOR_ID))
           .orderBy(1, 2, 3)
           .forEach {
               println("${it[b.TITLE]} by ${it[a.FIRST_NAME]} ${it[a.LAST_NAME]}")
           }
    }
}

Note that Kotlin supports some means of operator overloading. For instance, a + b in Kotlin maps to a formal a.plus(b) method invocation, and jOOQ provides the required synonyms in its API to help you write such expressions.

One particularly nice language feature is the fact that [square brackets] allow for accessing any object's contents via get() and set() methods. Instead of using the above value1(), value2(), and value3() methods, we could also iterate as such:

        ctx.select(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, BOOK.TITLE)
           .from(AUTHOR)
           .join(BOOK).on(AUTHOR.ID.eq(BOOK.AUTHOR_ID))
           .orderBy(1, 2, 3)
           .forEach {
               println("${it[BOOK.TITLE]} by ${it[AUTHOR.FIRST_NAME]} ${it[AUTHOR.LAST_NAME]}")
               // Notice:   ^^^^^^^^^^^^         ^^^^^^^^^^^^^^^^^^^      ^^^^^^^^^^^^^^^^^^
           }

A caveat of Kotlin 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.

 

2.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), 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. E.g. for Cypher, there's Cypher-DSL, which is a much better fit.

jOOQ is about SQL, and about SQL alone.

 

2.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 persisting an object graph, JPA is a good fit. Though note that starting with jOOQ 3.15 you can also load trees with the MULTISET_AGG function and the MULTISET value constructor very easily.

And sometimes, it's best to combine both

 

2.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. The jOOQ Open Source Edition requires Java 8+ to compile and run. The commercial jOOQ Editions require Java 8+ or Java 6+ to compile and run, depending on the distribution.

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
 

2.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 https://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 (including raw type or binary compatibility implications)

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, the exception being raw type compatibility (see #11879), and rare exceptions where API design is clearly lacking.
  • Behavioural compatibility can be expected from patch and minor releases.
  • Any jOOQ SPI XYZ that is meant to be implemented ships with a DefaultXYZ or AbstractXYZ, 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.

 

3. 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.

 

3.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));
 

3.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)
 

3.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:

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();
 

3.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 Informix 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.INFORMIX, 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

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

 

3.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).

 

3.2.3. SQL Dialect Category

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

In jOOQ 3.18, the notion of a SQLDialect.category() was introduced, in order to group several similar SQL dialect families into a common category. An example for this is T-SQL, which is supported by jOOQ in various versions:

Categories are sets of families that often expose similar syntax, due to a common ancestor. Categories include:

  • MYSQL
  • POSTGRES
  • TSQL
 

3.2.4. 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 Java EE 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.

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;
}

Reactive querying

If you wish to use an R2DBC driver, you do not have to supply a org.jooq.ConnectionProvider to your Configuration. Instead, jOOQ can work with a io.r2dbc.spi.Connection (jOOQ will never close it) or io.r2dbc.spi.ConnectionFactory (jOOQ will close all R2DBC Connections that it creates).

 

3.2.5. 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:

public class NoInsertListener implements ExecuteListener {

    @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.

 

3.2.6. 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.

 

3.2.7. Custom Unwrappers

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

JDBC knows the java.sql.Wrapper API, which is implemented by all JDBC types in order to be able to "unwrap" a native driver implementation for any given type. For example:

// This may be some proxy from a connection pool
Connection c = getConnection();

// Sometimes, we want the native driver connection instance
OracleConnection oc = c.unwrap(OracleConnection.class);
Array array = oc.createARRAY("ARRAY_TYPE", new Object[] { "a", "b" });

jOOQ internally makes similar calls occasionally. For this, it needs to unwrap the native java.sql.Connection or java.sql.PreparedStatement instance. Unfortunately, not all third party libraries correctly implement the Wrapper API contract, so this unwrapping might not work. The org.jooq.Unwrapper SPI is designed to allow for custom implementations to be injected into jOOQ configurations:

// Your jOOQ configuration
Configuration c1 = getConfiguration();
Configuration c2 = c.derive(new Unwrapper() {
    @Override
    public <T> T unwrap(Wrapper wrapper, Class<T> iface) {
        try {
            if (wrapper instanceof Connection)
                // ...
            else if (wrapper instanceof Statement)
                // ...
            else
                wrapper.unwrap(iface);
        }
        catch (SQLException e) {
            // ...
        }
    }
});

// Work with the derived configuration, where needed
DSL.using(c2).fetch("...");
 

3.2.8. 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:

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:
https://www.jooq.org/xsd/jooq-runtime-3.18.0.xsd

 

3.2.8.1. 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:

Example configuration

Settings settings = new Settings()
    .withAttachRecords(false); // Defaults to true
 

3.2.8.2. Auto-inline bind values

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Bind values are an important concept in SQL, for performance reasons, as they simplify caching of prepared statements in some RDBMS. jOOQ always creates bind values by default, when you write this:

-- Normally, a bind parameter marker is generated
AUTHOR.ID = ?
// This is the same as AUTHOR.ID.eq(val(1, AUTHOR.ID))
AUTHOR.ID.eq(1);

In some cases, however, it is better not to use a bind variable, but to create inline values, instead, so the optimiser can better apply its statistics. This is useful mainly when:

  • The column is a constant discriminator column in a view, for example
  • The column has very skewed statistics and only few possible values (e.g. a BOOLEAN, an ENUM type or a CHECK COL IN (1, 2, 3)) constraint.

In those cases, it can be useful to enable Settings.transformInlineBindValuesForFieldComparisons and implement a org.jooq.TransformProvider as follows:

Configuration configuration = ...
configuration.settings().setTransformInlineBindValuesForFieldComparisons(true);
configuration.set(new TransformProvider() {
    @Override
    public boolean inlineBindValuesForFieldComparisons(Field<?> field) {
        return field.getType() == Boolean.class
            || field.getDataType().isEnum(); // Or, perhaps, limit this only to certain enums
    }
});

Now, all queries whose predicates match the above TransformProvider content will have their relevant bind values inlined. For example:

-- Inlining applies to some columns now, not all
AUTHOR.ID = ? AND AUTHOR.STATUS = 'ACTIVE'
AUTHOR.ID.eq(1).and(
    AUTHOR.STATUS.eq(Status.ACTIVE));

Related settings include:

 

3.2.8.3. 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 feature ON for MySQL and MariaDB and OFF for all other dialects
  • ON: Turn the feature on.
  • OFF: Turn the feature off.

Example configuration

Settings settings = new Settings()
    .withBackslashEscaping(BackslashEscaping.OFF); // Default to DEFAULT
 

3.2.8.4. Batch size

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

jOOQ offers a transparent batching API, which can buffer all statements generated by jOOQ and other JDBC backed APIs transparently in order to batch them:

// Everything in the below lambda will be buffered and batched
DSL.using(configuration).batched(c -> {
    module1.insertSomething(c);
    module2.insertSomethingElse(c);
});

Use the Settings.batchSize flag to govern the maximum batch statement size of this API:

Settings settings = new Settings()
    .withBatchSize(100); // Default Integer.MAX_VALUE
 

3.2.8.5. Computed column emulation

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Server side computed columns are a useful feature of many RDBMS, where a synthetic column expression is always computed based on other columns of the same row, either on read (VIRTUAL) or on write (STORED). Recent versions of jOOQ have added support for client side computed columns, i.e. columns that are computed by jOOQ in the client rather than by the RDBMS on the server side. This can be especially useful if the computational expression is dynamic, or uses some advanced jOOQ feature like implicit JOIN, or if the RDBMS does not support the feature.

In the latter case, if the RDBMS does not support the feature, jOOQ can emulate it for you based on a schema generated from an RDBMS that does support the feature. E.g. when the schema is generated with H2 but the query is run on TRINO, jOOQ can run the computation directly in generated SQL, for both (VIRTUAL) or (STORED) cases:

Example configuration

Settings settings = new Settings()
    .withEmulateComputedColumns(true); // Defaults to false
 

3.2.8.6. Diagnostics Connection

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The diagnosticsConnection setting allows for turning on/off the diagnostics functionality within jOOQ.

There are 3 possible values:

  • DEFAULT: By default, the diagnostics functionality is turned off, but can be used explicitly via DSLContext.diagnosticsConnection() or DSLContext.diagnosticsDataSource(). This is ideal when building custom diagnostics utilities on top of jOOQ's SPIs.
  • ON: The diagnostics connection is activated implicitly on any JDBC java.sql.Connection that jOOQ works with. This is ideal when quickly turning on diagnostics on a development or integration test environment.
  • OFF: The diagnostics connection is deactivated even when used explicitly.

Example configuration

Settings settings = new Settings()
    .withDiagnosticsConnection(DiagnosticsConnection.ON); // Defaults to DEFAULT
 

3.2.8.7. Diagnostics Logging

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The diagnosticsLogging setting turns off the default diagnostics logging implemented through org.jooq.impl.LoggingDiagnosticsListener

Example configuration

Settings settings = new Settings()
    .withDiagnosticsLogging(false); // Defaults to true
 

3.2.8.8. Dialect compatibility

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

When supporting multiple dialects, there are some dialect specific behaviours that are inconsistent between dialects. This is especially true for NULL values in Oracle.

The following settings govern dialect compatibility behaviour:

  • Settings.renderCoalesceToEmptyStringInConcat: This flag will use COALESCE on all operands of a String concatenation expression in order to turn NULL values to '', which is Oracle's more useful, but not standards compliant behaviour.
 

3.2.8.9. 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

Example configuration

Settings settings = new Settings()
    .withExecuteLogging(false); // Defaults to true
 

3.2.8.10. Execute Logging SQL Exceptions

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The executeLoggingSQLExceptions setting turns off the default logging of SQLExceptions.

Example configuration

Settings settings = new Settings()
    .withExecuteLoggingSQLExceptions(false); // Defaults to true
 

3.2.8.11. 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:

Example configuration

Settings settings = new Settings()
    .withFetchWarnings(false); // Defaults to true
 

3.2.8.12. GROUP_CONCAT Configuration

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The MySQL GROUP_CONCAT function suffers from a controversial design decision where results are truncated after a certain length, the @@group_concat_max_len.

Whenever jOOQ generates a GROUP_CONCAT function, by default, that MySQL system variable is increased to the maximum value for the scope of a single statement, e.g.

SET @T = @@GROUP_CONCAT_MAX_LEN;
SET @@GROUP_CONCAT_MAX_LEN = 4294967295;
SELECT GROUP_CONCAT(TITLE SEPARATOR ', ')
FROM BOOK;
SET @@GROUP_CONCAT_MAX_LEN = @T;

More details here. While this is a reasonable default behaviour (as opposed to the random truncation), it may occasionally be undesired, e.g. if statement batches (; separated statements) aren't possible in a single JDBC statement. The feature can be turned off with

Example configuration

Settings settings = new Settings()
    .withRenderGroupConcatMaxLenSessionVariable(false); // Defaults to true
 

3.2.8.13. 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, whereas name and NAME are not. Please consider your database manual to learn what the proper default case and default case sensitivity is.

The renderQuotedNames and renderNameCase settings allow for overriding the name of all identifiers in jOOQ to a consistent style. Possible options are:

RenderQuotedNames

  • ALWAYS: This will quote all identifiers.
  • EXPLICIT_DEFAULT_QUOTED: This will quote all identifiers, which are not explicitly unquoted using DSL.unquotedName().
  • EXPLICIT_DEFAULT_UNQUOTED: This will not quote any identifiers, unless they are explicitly quoted using DSL.quotedName().
  • NEVER: This will not quote any identifiers.

RenderNameCase

  • AS_IS: This will generate all names in their proper case.
  • LOWER: This will transform all names to lower case.
  • LOWER_IF_UNQUOTED: This will transform all names to lower case if the name is unquoted.
  • UPPER: This will transform all names to upper case.
  • UPPER_IF_UNQUOTED: This will transform all names to upper case if the name is unquoted.

The two flags are independent of one another. If your database supports quoted, case sensitive identifiers, then using LOWER or UPPER on quoted identifiers may not work.

Example configuration

Settings settings = new Settings()
    .withRenderQuotedNames(RenderQuotedNames.EXPLICIT_DEFAULT_UNQUOTED) // Defaults to EXPLICIT_DEFAULT_QUOTED
    .withRenderNameCase(RenderNameCase.LOWER_IF_UNQUOTED);              // Defaults to AS_IS

The behaviour of this setting is influenced by the renderLocale setting.

 

3.2.8.14. Implicit join type

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

jOOQ's very useful implicit JOIN feature can be used to use a path notation to join tables on their actual, or synthetic foreign keys. For example:

// Get all books, their authors, and their respective language
create.select(
          BOOK.author().FIRST_NAME,
          BOOK.author().LAST_NAME,
          BOOK.TITLE,
          BOOK.language().CD.as("language"))
      .from(BOOK)
      .fetch();

By default, this produces:

  • An INNER_JOIN if all columns of the foreign key are NOT NULL
  • A LEFT_JOIN if the foreign key is nullable / optional

The above defaults are important to prevent implicit joins from filtering results when placed in clauses that are not meant to filter, such as the SELECT clause or the ORDER BY clause.

Users may prefer to enforce a different behaviour, including:

  • Always produce a LEFT_JOIN, e.g. because this was the behaviour before jOOQ 3.14
  • Always produce an INNER_JOIN, e.g. because they're migrating off Hibernate / JPA, and depend on Hibernate's implicit joins producing inner joins

This change of behaviour can be achieved with the following setting:

Example configuration

Settings settings = new Settings()
    .withRenderImplicitJoinType(RenderImplicitJoinType.INNER_JOIN);
 

3.2.8.15. Inline Threshold

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Previous sections showed how the SQL generation of bind values can be controlled, e.g. by forcing them to be inlined, or by running a static JDBC statement.

Sometimes, inlining needs to be enforced dynamically, depending on the query content. This is the case when there are a great number of bind variables. Known vendor-specific limits are:

  • Access : 768
  • Ingres : 1024
  • Oracle : 32767
  • PostgreSQL : 32767
  • SQLite : 999
  • SQL Server : 2100
  • Sybase ASE : 2000
  • Teradata : 2536

By default, jOOQ will automatically inline all bind variables in any SQL statement, once these thresholds have been reached. However, it is possible to override this default and provide a setting to re-define a global threshold for all dialects.

Example configuration

Settings settings = new Settings()
    .withInlineThreshold(100); // Defaults to 0, which means the default thresholds are applied
 

3.2.8.16. IN-list Padding

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Databases that feature a cursor cache / statement cache (e.g. Oracle, SQL Server, DB2, etc.) are highly optimised for prepared statement re-use. When a client sends a prepared statement to the server, the server will go to the cache and look up whether there already exists a previously calculated execution plan for the statement (i.e. the SQL string). This is called a "soft-parse" (in Oracle). If not, the execution plan is calculated on the fly. This is called a "hard-parse" (in Oracle).

Preventing hard-parses is extremely important in high throughput OLTP systems where queries are usually not very complex but are run millions of times in a short amount of time. Using bind variables, this is usually not a problem, with the exception of the IN predicate, which generates different SQL strings even when using bind variables:

-- All of these are different SQL statements:
SELECT * FROM AUTHOR WHERE ID IN (?)
SELECT * FROM AUTHOR WHERE ID IN (?, ?)
SELECT * FROM AUTHOR WHERE ID IN (?, ?, ?)
SELECT * FROM AUTHOR WHERE ID IN (?, ?, ?, ?)
SELECT * FROM AUTHOR WHERE ID IN (?, ?, ?, ?, ?)

This problem may not be obvious to Java / jOOQ developers, as they are always produced from the same jOOQ statement:

// All of these are the same jOOQ statement
DSL.using(configuration)
   .select()
   .from(AUTHOR)
   .where(AUTHOR.ID.in(collection))
   .fetch();

Depending on the possible sizes of the collection, it may be worth exploring using arrays or temporary tables as a workaround, or to reuse the original query that produced the set of IDs in the first place (through a semi-join). But sometimes, this is not possible. In this case, users can opt in to a third workaround: enabling the inListPadding setting. If enabled, jOOQ will "pad" the IN list to a length that is a power of two (configurable with Settings.inListPadBase). So, the original queries would look like this instead:

-- Original
SELECT * FROM AUTHOR WHERE ID IN (?)
SELECT * FROM AUTHOR WHERE ID IN (?, ?)
SELECT * FROM AUTHOR WHERE ID IN (?, ?, ?)
SELECT * FROM AUTHOR WHERE ID IN (?, ?, ?, ?)
SELECT * FROM AUTHOR WHERE ID IN (?, ?, ?, ?, ?)
SELECT * FROM AUTHOR WHERE ID IN (?, ?, ?, ?, ?, ?)
-- Padded
SELECT * FROM AUTHOR WHERE ID IN (?)
SELECT * FROM AUTHOR WHERE ID IN (?, ?)
SELECT * FROM AUTHOR WHERE ID IN (?, ?, ?, ?)
SELECT * FROM AUTHOR WHERE ID IN (?, ?, ?, ?)
SELECT * FROM AUTHOR WHERE ID IN (?, ?, ?, ?, ?, ?, ?, ?)
SELECT * FROM AUTHOR WHERE ID IN (?, ?, ?, ?, ?, ?, ?, ?)

This technique will drastically reduce the number of possible SQL strings without impairing too much the usual cases where the IN list is small. When padding, the last bind variable will simply be repeated many times.

Usually, there is a better way - use this as a last resort!

Example configuration

Settings settings = new Settings()
    .withInListPadding(true) // Default to false
    .withInListPadBase(4);   // Default to 2
 

3.2.8.17. Interpreter Configuration

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The SQL Interpreter API ships with a variety of settings that govern its behaviour. These settings include:

  • interpreterDialect: The interpreter input dialect. This dialect is used to decide whether DDL interpretation should be done on an actual in-memory database of a specific type, or using jOOQ's built in DDL interpretation.
  • interpreterDelayForeignKeyDeclarations: Whether the interpreter should delay the application of foreign key declarations (in case of which forward references are possible).
  • interpreterLocale: The locale to use for things like case insensitive comparisons.
  • interpreterNameLookupCaseSensitivity: The identifier case sensitivity that should be applied when interpreting SQL, depending on whether identifiers are quoted or not.
  • interpreterSearchPath: The search path for unqualified schema objects used by the interpreter.

Example configuration

Settings settings = new Settings()
    .withInterpreterDialect(H2)                           // Defaults to DEFAULT
    .withInterpreterDelayForeignKeyDeclarations(true)     // Defaults to false
    .withInterpreterLocale(Locale.forLanguageTag("de"))   // Defaults to Locale.getDefault()
    .withInterpreterNameLookupCaseSensitivity(NEVER)      // Defaults to WHEN_QUOTED
    .withInterpreterSearchPath(...);                      // Defaults to an empty list
 

3.2.8.18. JDBC Flags

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

JDBC statements feature a couple of flags that influence the execution of such a statement. Each of these flags can be configured through jOOQ's org.jooq.Query and org.jooq.ResultQuery on a statement-per-statement basis, but there's also the possibility to centrally specify a value for these flags. These are the three flags:

All of these flags are JDBC-only features with no direct effect on jOOQ. jOOQ only passes them through to the underlying statement.

Example configuration

Settings settings = new Settings()
    .withQueryTimeout(5)
    .withQueryPoolable(DEFAULT)
    .withMaxRows(1000)
    .withFetchSize(20);
 

3.2.8.19. 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 renderKeywordCase setting:

  • AS_IS (the default): Generate keywords as they are defined in the codebase (mostly lower case).
  • LOWER: Generate keywords in lower case.
  • UPPER: Generate keywords in upper case.
  • PASCAL: Generate keywords in pascal case.

Example configuration

Settings settings = new Settings()
    .withRenderKeywordCase(RenderKeywordCase.UPPER); // Defaults to AS_IS
 

3.2.8.20. Listener Invocation Order

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

jOOQ offers a variety of SPIs in the Configuration object. Some of those SPIs are event listeners, that can listen to "start" and "end" events, such as for example the ExecuteListener that listens to the query execution lifecycle.

When registering multiple listeners of a type, the invocation order may be relevant as custom listeners might communicate with each other. In such a case, the following settings allow for overriding the invocation order of "start" and "end" events for each type of listener:

Example configuration

Settings settings = new Settings()
    .withTransactionListenerStartInvocationOrder(DEFAULT) // Defaults to DEFAULT
    .withTransactionListenerEndInvocationOrder(REVERSE)   // Defaults to DEFAULT
    .withVisitListenerStartInvocationOrder(DEFAULT)       // Defaults to DEFAULT
    .withVisitListenerEndInvocationOrder(REVERSE)         // Defaults to DEFAULT
    .withRecordListenerStartInvocationOrder(DEFAULT)      // Defaults to DEFAULT
    .withRecordListenerEndInvocationOrder(REVERSE)        // Defaults to DEFAULT
    .withExecuteListenerStartInvocationOrder(DEFAULT)     // Defaults to DEFAULT
    .withExecuteListenerEndInvocationOrder(REVERSE);      // Defaults to DEFAULT
 

3.2.8.21. Locales

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

When doing locale sensitive operations, such as upper casing or lower casing a name (see Name styles), then it may be important in some areas to be able to specify the java.util.Locale for the operation.

Example configuration

// All of these default to Locale.getDefault(), if not specified explicitly
Settings settings = new Settings()
    .withLocale(Locale.forLanguageTag("de"))             // The default locale if no more specific locales are specified
    .withRenderLocale(Locale.forLanguageTag("de"))       // The locale used when rendering SQL
    .withParseLocale(Locale.forLanguageTag("de"))        // The locale used when parsing SQL
    .withInterpreterLocale(Locale.forLanguageTag("de")); // The locale used when interpreting SQL
 

3.2.8.22. Map JPA Annotations

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The org.jooq.impl.DefaultRecordMapper supports basic JPA mapping (mostly @Table and @Column annotations). Looking up these annotations costs a slight extra overhead (mostly taken care of through reflection caching). It can be turned off using the mapJPAAnnotations setting:

Example configuration

Settings settings = new Settings()
    .withMapJPAAnnotations(false); // Defaults to true
 

3.2.8.23. 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 catalog.schema.table.column
FROM catalog.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 renderCatalog and renderSchema settings are used for this.

Example configuration

new Settings()
  .withRenderCatalog(false)  // Defaults to true
  .withRenderSchema(false);  // Defaults to true

More sophisticated multitenancy approaches are available through the render mapping feature.

 

3.2.8.24. Object qualification for columns

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

By default, jOOQ fully qualifies all columns with their table names (and the tables might themselves be qualified). This is a reasonable default, as any JOIN operation may produce ambiguous column names, such as the ubiquitous names ID or CREATED_AT.

-- Columns always qualified with table name.
SELECT table.column
FROM table
DSL.using(configuration)
   .select(TABLE.COLUMN)
   .from(TABLE);

In rare cases, it may be desirable to drop this qualification, keeping it only either:

  • ALWAYS: This is the default. Columns are always qualified with their table.
  • WHEN_MULTIPLE_TABLES: When the FROM clause has multiple tables.
  • WHEN_AMBIGUOUS_COLUMNS: When the FROM clause produces ambiguous columns.
  • NEVER: Qualification is always dropped. This may produce semantically wrong SQL and is intended only to be used on a query by query basis, if any of the above does not implement requirements as desired.

Example configuration

new Settings()
  .withRenderTable(RenderTable.WHEN_MULTIPLE_TABLES) // Defaults to ALWAYS
 

3.2.8.25. Optimistic Locking

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

There are two settings governing the behaviour of the jOOQ optimistic locking feature:

  • updateRecordVersion: Whether UpdatableRecord instances should modify the record version prior to storing the record. This feature is independent of, but related to optimistic locking.
  • updateRecordTimestamp: Whether UpdatableRecord instances should modify the record timestamp prior to storing the record. This feature is independent of, but related to optimistic locking.
  • executeWithOptimisticLocking: This allows for turning off the feature entirely.
  • executeWithOptimisticLockingExcludeUnversioned: This allows for turning off the feature for updatable records who are not explicitly versioned.

Example configuration

Settings settings = new Settings()
    .withUpdateRecordVersion(true)                              // Defaults to true
    .withUpdateRecordTimestamp(true)                            // Defaults to true
    .withExecuteWithOptimisticLocking(true)                     // Defaults to false
    .withExecuteWithOptimisticLockingExcludeUnversioned(false); // Defaults to false

For more details, please refer to the manual's section about the optimistic locking feature.

 

3.2.8.26. Parameter name prefix

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

When choosing a ParameterType.NAMED to produce named parameters, the default is to use a colon as a prefix to the parameter name.

For example:

-- NAMED
SELECT FIRST_NAME || :1 FROM AUTHOR WHERE ID = :x

Depending on how the named parameters are interpreted, this default is not optimal. A better character might be the $ sign, e.g. in PostgreSQL or R2DBC. For this, the renderNamedParamPrefix setting can be used:

Example configuration

Settings settings = new Settings()
    .withRenderNamedParamPrefix("$"); // Defaults to ":"
 

3.2.8.27. 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();

Example configuration

Settings settings = new Settings()
    .withParamType(ParamType.NAMED); // Defaults to INDEXED

The following setting statementType may override this setting.

 

3.2.8.28. Parser Configuration

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The SQL Parser API ships with a variety of settings that govern its behaviour. These settings include:

  • parseDialect: The parser input dialect. This dialect is used to decide what vendor specific grammar should be applied in case of ambiguities that cannot be resolved from the context.
  • parseDateFormat: The date format that is applied automatically when parsing date formatting functions without an explicit format.
  • parseIgnoreComments: Using this flag, the parser can ignore certain sections that would otherwise be executed by RDBMS. Everything between an parseIgnoreCommentStart and the parseIgnoreCommentStop token will be ignored.
  • parseIgnoreCommentStart: The token that delimits the beginning of a section to be ignored by jOOQ. Ideally, this token is placed inside of a SQL comment.
  • parseIgnoreCommentStop: The token that delimits the end of a section to be ignored by jOOQ. Ideally, this token is placed inside of a SQL comment.
  • parseRetainCommentsBetweenQueries: Whether comments in between statements from Parser.parse() are retained and parsed as ignored queries. Comments inside of statements (including procedural statements) currently aren't supported by jOOQ.
  • parseSearchPath: The search path to look up unqualified identifiers to be used when using parseWithMetaLookups. Most dialects support a single schema on their search path (the CURRENT_SCHEMA). PostgreSQL supports a 'search_path', which allows for listing multiple schemata to use to look up unqualified tables, procedures, etc. in.
  • parseTimestampFormat: The timestamp format that is applied automatically when parsing timestamp formatting functions without an explicit format.
  • parseUnsupportedSyntax: The parser can parse some syntax that jOOQ does not support. By default, such syntax is ignored. Use this flag if you want to fail in such cases.
  • parseUnknownFunctions: The parser only parses "known" (to jOOQ) built in functions, and fails otherwise. This flag allows for parsing any built in function using a standard func_name(arg1, arg2, ...) syntax.
  • parseWithMetaLookups: Whether org.jooq.Meta should be used to look up meta information such as schemas, tables, columns, column types, etc.

An example of using the parseIgnoreComments feature:

-- What you execute
/* [jooq ignore start] */
CREATE SCHEMA s1;
SET SCHEMA s1;
/* [jooq ignore stop] */

/* [jooq ignore start] */ -- /* [jooq ignore stop] */ CREATE SCHEMA s2;
/* [jooq ignore start] */ -- /* [jooq ignore stop] */ SET SCHEMA s2;

CREATE TABLE t (i INTEGER);
-- What the jOOQ parser sees
/*


                      */

/*                                                 */ CREATE SCHEMA s2;
/*                                                 */ SET SCHEMA s2;

CREATE TABLE t (i INTEGER);

Example configuration

Settings settings = new Settings()
    .withParseDialect(SQLSERVER)                         // Defaults to DEFAULT
    .withParseWithMetaLookups(THROW_ON_FAILURE)          // Defaults to OFF
    .withParseSearchPath(
        new ParseSearchSchemata().withSchema("PUBLIC"),
        new ParseSearchSchemata().withSchema("TEST"))
    .withParseUnsupportedSyntax(FAIL)                    // Defaults to IGNORE
    .withParseUnknownFunctions(IGNORE)                   // Defaults to FAIL
    .withParseIgnoreComments(true)                       // Defaults to false
    .withParseIgnoreCommentStart("<ignore>")             // Defaults to "[jooq ignore start]"
    .withParseIgnoreCommentStop("</ignore>")             // Defaults to "[jooq ignore stop]"

In addition to the above settings, there is also a powerful parser listener SPI called the org.jooq.ParseListener.

 

3.2.8.29. Readonly column behaviour

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

When using is readonly columns, by default, any values that are attempted to be inserted or updated are ignored. The following Settings allow for overriding and the default behaviour of readonly columns:

Each one of these flags is of type org.jooq.conf.WriteIfReadonly with these permitted states:

  • WRITE: Write to the column as if it weren't readonly. This effectively turns off the feature.
  • IGNORE: Ignore the column in a relevant statement. This is the default.
  • THROW: Throw an exception if the column is included in a relevant DML statement.

The default behaviour IGNORE is particularly useful when loading POJO data into org.jooq.UpdatableRecord and storing it, while ignoring IDENTITY columns, computed columns, synthetic columns (such as the synthetic ROWIDs), and more.

Example configuration

Settings settings = new Settings()
    .withReadonlyInsert(WriteIfReadonly.THROW)                 // Defaults to IGNORE
    .withReadonlyUpdate(WriteIfReadonly.THROW)                 // Defaults to IGNORE
    .withReadonlyTableRecordInsert(WriteIfReadonly.THROW)      // Defaults to IGNORE
    .withReadonlyUpdatableRecordUpdate(WriteIfReadonly.THROW); // Defaults to IGNORE
 

3.2.8.30. 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.

Example configuration

Settings settings = new Settings()
    .withReflectionCaching(false); // Defaults to true
 

3.2.8.31. Return all columns on store

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

When using the updatable records feature, jOOQ always fetches the generated identity value, if such a value is available and if the return identity on store feature is enabled (it is, by default).

The identity value is not the only value that is generated by default. Specifically, there may be triggers that are used for auditing or other reasons, which generate LAST_UPDATE or LAST_UPDATE_BY values in a record. Users who wish to also automatically fetch these values after all store(), insert(), or update() calls may do so by specifying the returnAllOnUpdatableRecord setting. This setting depends on the availability of INSERT .. RETURNING, UPDATE .. RETURNING, and DELETE .. RETURNING statements, which are not available from all databases, in case of which a refresh() call may be issued, creating a separate round trip to the server.

Example configuration

Settings settings = new Settings()
    .withReturnAllOnUpdatableRecord(true); // Defaults to false
 

3.2.8.32. Return computed columns on store

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

When using the updatable records feature, jOOQ always fetches the generated identity value, if such a value is available and if the return identity on store feature is enabled (it is, by default).

The identity value is not the only value that is generated by default. Specifically, there may be different types of computed columns. Users who wish to also automatically fetch these values after all store(), insert(), or update() calls may do so by specifying the returnDefaultOnUpdatableRecord setting. This setting depends on the availability of INSERT .. RETURNING, UPDATE .. RETURNING, and DELETE .. RETURNING statements, which are not available from all databases, in case of which a refresh() call may be issued, creating a separate round trip to the server.

Example configuration

Settings settings = new Settings()
    .withReturnComputedOnUpdatableRecord(true); // Defaults to false
 

3.2.8.33. Return DEFAULT columns on store

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

When using the updatable records feature, jOOQ always fetches the generated identity value, if such a value is available and if the return identity on store feature is enabled (it is, by default).

The identity value is not the only value that is generated by default. Specifically, there may be other defaulted values, such as creation timestamps and users. Users who wish to also automatically fetch these values after all store(), insert(), or update() calls may do so by specifying the returnDefaultOnUpdatableRecord setting. This setting depends on the availability of INSERT .. RETURNING, UPDATE .. RETURNING, and DELETE .. RETURNING statements, which are not available from all databases, in case of which a refresh() call may be issued, creating a separate round trip to the server.

Example configuration

Settings settings = new Settings()
    .withReturnDefaultOnUpdatableRecord(true); // Defaults to false
 

3.2.8.34. Return Identity Value On Store

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

When using the updatable records feature, jOOQ by default fetches the generated identity value. In some situations, it is desirable for this feature to be turned off using the following flag:

Example configuration

Settings settings = new Settings()
    .withReturnIdentityOnUpdatableRecord(false); // Defaults to true
 

3.2.8.35. Runtime catalog, schema and table mapping

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Most SQL object types are qualified with a org.jooq.Catalog and org.jooq.Schema. In multi-tenant application, users may want to map these identifier namespaces to something other than the default.

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:

Example 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")));

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:

Example configuration

Settings settings = new Settings()
    .withRenderMapping(new RenderMapping()
    .withSchemata(
        new MappedSchema().withInput("DEV")
                          .withTables(
         new MappedTable().withInput("AUTHOR")
                          .withOutput("MY_APP__AUTHOR"))));

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.

Mapping of catalogs

For databases like SQL Server, it is also possible to map catalogs in addition to schemata. The mechanism is exactly the same. So let's assume that we generated code for a table [dev].[dbo].[author] and want to map it to [my_book_world].[dbo].[author] at runtime. This can be achieved as follows:

Example configuration

Settings settings = new Settings()
    .withRenderMapping(new RenderMapping()
    .withCatalogs(
        new MappedCatalog().withInput("DEV")
                           .withOutput("MY_BOOK_WORLD")));

To give you full control of how each and every table gets mapped, a MappedCatalog object can contain MappedSchema (and thus also MappedTable) definitions.

Using regular expressions

All of the above examples were using 1:1 constant name mappings where the input and output schema or table names are fixed by the configuration. With jOOQ 3.8, regular expression can be used as well for mapping, for example:

Example configuration

Settings settings = new Settings()
    .withRenderMapping(new RenderMapping()
    .withSchemata(
        new MappedSchema().withInputExpression(Pattern.compile("DEV_(.*)"))
                          .withOutput("PROD_$1")
                          .withTables(
         new MappedTable().withInputExpression(Pattern.compile("DEV_(.*)"))
                          .withOutput("PROD_$1"))));

The only difference to the constant version is that the input field is replaced by the inputExpression field of type java.util.regex.Pattern, in case of which the meaning of the output field is a pattern replacement, not a constant replacement.

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 catalog, schema and table mappings at code generation time.

Limitations

Mapped objects need to be known to the jOOQ org.jooq.RenderContext, which means that for example plain SQL templates and their contents cannot be mapped. See also features requiring code generation for more details.

 

3.2.8.36. 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.

Example configuration

Settings settings = new Settings()
    .withRenderScalarSubqueriesForStoredFunctions(true);
 

3.2.8.37. SEEK clause implementation

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The SEEK clause is a powerful alternative to the OFFSET clause for pagination. By default, the SEEK clause is transformed into an equivalent ROW predicate as follows:

SELECT id, value
FROM t
WHERE (value, id) > (2, 533)
ORDER BY value, id
LIMIT 5
 
create.select(T.ID, T.VALUE)
      .from(T)
      .orderBy(T.VALUE, T.ID)
      .seek(lastValue, lastId)
      .limit(5)
      .fetch();

That ROW predicate is optimal, syntactically, but may not be optimised optimally by a dialect's underlying optimiser. As such, there are two ways to influence the generation of this predicate in away to possibly help the optimiser choose the right index:

-- Settings.renderRowConditionForSeekClause = false to turn off using the ROW syntax
WHERE value > 2 OR value = 2 AND id > 533

-- Settings.renderRedundantConditionForSeekClause = true to add an additional redundant predicate
WHERE value >= 2 AND (value, id) > (2, 533)
WHERE value >= 2 AND (value > 2 OR value = 2 AND id > 533)

The default in jOOQ is to not do the above, but users can opt into the manual expansion of syntax to benefit performance.

Note that if the ROW syntax isn't supported natively, then jOOQ will expand that to the equivalent OR predicate anyway.

Example configuration

Settings settings = new Settings()
    .withRenderRowConditionForSeekClause(false)       // Default to true
    .withRenderRedundantConditionForSeekClause(true); // Default to false
 

3.2.8.38. Statement Type

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

JDBC knows two types of 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 the paramType == INLINED. See parameter types

Example configuration

Settings settings = new Settings()
    .withStatementType(StatementType.STATIC_STATEMENT); // Defaults to PREPARED_STATEMENT
 

3.2.8.39. 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 from 1 to 2.

Example configuration

Settings settings = new Settings()
    .withUpdatablePrimaryKeys(true); // Defaults to false
 

3.2.9. 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 various Configuration.set() methods must be avoided after initialisation, should a Configuration (and by consequence DSLContext) instance be shared among threads. If you wish to modify some elements of a Configuration for single use, use the Configuration.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 wrapping Configuration 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.

 

3.3. The DSL API

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The DSL API is the primary way to construct queries or query parts in jOOQ. See the model API for an alternative way to interact with the jOOQ query object model.

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

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 with IDE syntax auto 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();
 

3.3.1. Mutability (historic)

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

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.

 

3.4. The model API

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The model API is the secondary way to interact with queries or query parts in jOOQ. See the DSL API for the main way to interact with the jOOQ query object model.

 

3.4.1. Design

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

This is experimental functionality, and as such subject to change. Use at your own risk!

The model API (Query Object Model or org.jooq.impl.QOM) is an auxiliary API implemented by each and every org.jooq.QueryPart allowing for users to get public access to jOOQ's internal query object model structure. For example:

// Create an expression using the DSL API:
Field<String> field = substring(BOOK.TITLE, 2, 4);

// Access the expression's internals using the model API
if (field instanceof QOM.Substring substring) {
    Field<String> string = substring.$string();
    Field<? extends Number> startingPosition = substring.$startingPosition();
    Field<? extends Number> length = substring.$length();
}

Every argument passed to the DSL API has a $ prefixed accessor method on the model API, exposing the wrapped argument. Using these accessor methods, users can traverse the expression tree manually or via the model API traversal API. More recent Java language features like pattern matching can be very helpful for such operations, especially as we're planning to seal the entire query object model API.

All of the model API is immutable, but new expressions can still be created using equivalent $ prefixed setter methods, which don't mutate the original expression but return a copy:

// Produces a substring(BOOK.TITLE, 2, 4) column expression
QOM.Substring substring = (QOM.Substring) substring(BOOK.TITLE, 2, 4);

// Produces a substring(BOOK.TITLE, 3, 5) column expression
substring.$startingPosition(val(3)).$length(val(5));

These basic operations allow for powerful SQL transformations on expression trees created with the DSL API but also with the SQL parser.

 

3.4.2. Traversal

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

This is experimental functionality, and as such subject to change. Use at your own risk!

While the accessor methods from the model API allow for traversing the expression tree manually, a more generic way to traverse the expression tree is using the org.jooq.Traverser API, which can traverse a org.jooq.QueryPart in a similar fashion as a java.util.stream.Collector can iterate a java.util.stream.Stream, collecting and aggregating data about the expression tree. A Traverser consists of this API:

public interface Traverser<A, R> {

    /**
     * A supplier for a temporary data structure to accumulate {@link QueryPart}
     * objects into during traversal.
     */
    Supplier<A> supplier();

    /**
     * An optional traversal abort condition to short circuit traversal e.g.
     * when the searched object has been found.
     */
    Predicate<A> abort();

    /**
     * An optional recursion condition to prevent entering a specific
     * {@link QueryPart}, e.g. when it is undesired to enter any subqueries.
     */
    Predicate<QueryPart> recurse();

    /**
     * An optional recursion condition to prevent entering a specific
     * {@link QueryPart}'s children, e.g. when it is desired to traverse only
     * into certain operators.
     */
    Predicate<QueryPart> recurseChildren();

    /**
     * A callback that is invoked before recursing into a subtree.
     */
    BiFunction<A, QueryPart, A> before();

    /**
     * A callback that is invoked after recursing into a subtree.
     */
    BiFunction<A, QueryPart, A> after();

    /**
     * An optional transformation function to turn the temporary data structure
     * supplied by {@link #supplier()} into the final data structure.
     */
    Function<A, R> finisher();
}

Some elements are similar to that of a java.util.stream.Collector, others are specific to tree traversal.

A simple illustration shows what can be done:

// Any ordinary QueryPart:
Condition condition = BOOK.ID.eq(1);
int count = condition.$traverse(

    // Supplier of the initial data structure: an int
    () -> 0,

    // Print all traversed QueryParts and increment the counter
    (r, q) -> {
        System.out.println("Part " + r + ": " + q);
        return r + 1;
    }
);
System.out.println("Count : " + count);

The above will print:

Part 0: "BOOK"."ID" = 1
Part 1: "BOOK"."ID"
Part 2: 1
Count : 3

Using the same traverser on a slightly more complex QueryPart

Condition condition = BOOK.ID.eq(1).or(BOOK.ID.eq(2));
// ...
Part 0: ("BOOK"."ID" = 1 or "BOOK"."ID" = 2)
Part 1: "BOOK"."ID" = 1
Part 2: "BOOK"."ID"
Part 3: 1
Part 4: "BOOK"."ID" = 2
Part 5: "BOOK"."ID"
Part 6: 2
Count : 7

Re-using your JDK collectors

Any Collector can be turned into a Traverser using Traversers.collecting(Collector). For example, if you want to count all QueryPart items in an expression, instead of the above hand-written traverser, just use the JDK Collectors.counting():

// Contains 3 query parts
long count1 = BOOK.ID.eq(1)
    .$traverse(Traversers.collecting(Collectors.counting());

// Contains 7 query parts
long count2 = BOOK.ID.eq(1).or(BOOK.ID.eq(2))
    .$traverse(Traversers.collecting(Collectors.counting());

Limitations

Just like model API replacement, traversers cannot traverse into "opaque" org.jooq.QueryPart instances, including custom QueryParts or plain SQL templates. See also features requiring code generation for more details.

 

3.4.3. Replacement

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

This is experimental functionality, and as such subject to change. Use at your own risk!

A very powerful way to transform your SQL is to replace specific org.jooq.QueryPart elements in any expression tree by something else using the QueryPart.replace() API. This API treats the expression tree as a persistent data structure, i.e. the resulting tree may consist of parts of the existing tree, but the existing tree is not modified.

Let's assume you wish to implement an optimisation engine that removes redundant SQL clauses. For example, an expression NOT(NOT(p)) can be replaced by p in standard SQL (it may not be the exact same thing in some "clever" dialects without standard BOOLEAN type support):

// Contains redundant operators
Condition c = not(not(BOOK.ID.eq(1)));
System.out.println(c);
System.out.println(c.$replace(q ->
    q instanceof QOM.Not n1 && n1.$arg1() instanceof QOM.Not n2
        ? n2.$arg1()
        : q
));

The above prints:

not (not ("BOOK"."ID" = 1))
"BOOK"."ID" = 1

The replacement algorithm will attempt to run the replacement function recursively on your tree until it no longer affects the tree. This means two things:

  • You can implement all of your replacement logic in a single function, for various rules. The order of application of the rules is the one you define in your function.
  • The algorithm stops only when no more rules apply. If two rules turn A > B and B > A, then the algorithm may never stop.

Here's a more complex example that logs the replacements with println() calls:

// Contains redundant operators
Condition c = not(not(not(BOOK.ID.ne(1))));
QueryPart result = c.$replace(q -> {
    if (q instanceof QOM.Not n1 && n1.$arg1() instanceof QOM.Not n2) {
        System.out.println("Replacing NOT(NOT(p)) by NOT(p): " + q);
        return n2.$arg1();
    }
    else if (q instanceof QOM.Not n1 && n1.$arg1() instanceof QOM.Ne<?> n2) {
        System.out.println("Replacing NOT(x != y) by x = y: " + q);
        return n2.$arg1().eq((Field) n2.$arg2());
    }

    return q;
}));
System.out.println("Result: " + result);

The output is:

Replacing NOT(x != y) by x = y: not ("BOOK"."ID" <> 1)
Replacing NOT(NOT(p)) by NOT(p): not (not ("BOOK"."ID" = 1))
Result: "BOOK"."ID" = 1

As you can see:

  • The replacement function is invoked several times.
  • The second invocation can work on the result of the first invocation, where the NOT (x != y) predicate has already been improved.
  • The replacement works recursively, depth first, and bottom up.
  • It stops when no more replacements take place.

This obviously also works when you use jOOQ's parser, and is extremely useful when used via the parsing connection, e.g. to optimise any type of JDBC or R2DBC based application!

// Contains redundant operators
Condition c = create.parser().parseCondition("not not not book.id != 1");
QueryPart result = c.$replace(q -> {
    if (q instanceof QOM.Not n1 && n1.$arg1() instanceof QOM.Not n2) {
        System.out.println("Replacing NOT(NOT(p)) by NOT(p): " + q);
        return n2.$arg1();
    }
    else if (q instanceof QOM.Not n1 && n1.$arg1() instanceof QOM.Ne<?> n2) {
        System.out.println("Replacing NOT(x != y) by x = y: " + q);
        return n2.$arg1().eq((Field) n2.$arg2());
    }

    return q;
}));
System.out.println("Result: " + result);

The result is exactly the same:

Replacing NOT(x != y) by x = y: not (book.id <> 1)
Replacing NOT(NOT(p)) by NOT(p): not (not (book.id = 1))
Result: book.id = 1
Starting from jOOQ 3.17, this logging can also be achieved using a listening replacer.

Built-in replacers

The following sections show a few examples of built-in replacers.

Limitations

Just like model API traversal, replacers cannot traverse into "opaque" org.jooq.QueryPart instances, including custom QueryParts or plain SQL templates. See also features requiring code generation for more details.

 

3.4.3.1. Listening Replacer

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

This is experimental functionality, and as such subject to change. Use at your own risk!

This replacer doesn't replace anything on its own, but helps debug things from other replacers. For example, a Replacer that removes redundant NOT(NOT(x)) expressions:

// The input condition
Condition c = ctx.parser().parseCondition("not not not book.id != 1");

// The replacer doing the replacement
Replacer r = Replacer.of(q -> {
    if (q instanceof QOM.Not n1 && n1.$arg1() instanceof QOM.Not n2)
        return n2.$arg1();
    else if (q instanceof QOM.Not n1 && n1.$arg1() instanceof QOM.Ne<?> n2)
        return n2.$arg1().eq((Field) n2.$arg2());
    else
        return q;
});

// A proxy to the above replacer, doing some logging
r = Replacers.listening(r, (p1, p2) -> System.out.println("Replacing: " + p1 + " => " + p2));

// The application of the replacement
QueryPart result = c.$replace(r);
System.out.println("Result: " + result);

The above now prints each replacement that the depth first replacement algorithm applies to your expression tree, bottom-up:

Replacing: not (book.id <> 1) => book.id = 1
Replacing: not (not (book.id = 1)) => book.id = 1
Result: book.id = 1
 

3.4.3.2. Decomposing Replacer

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

This is experimental functionality, and as such subject to change. Use at your own risk!

This replacer decomposes compound queries into their component queries. Some DDL statements are compound statements, such as e.g.

ALTER TABLE t
  ADD col1 INT,
  ADD col2 INT;

The above single compound statement can be decomposed into its component statements:

ALTER TABLE t ADD col1 INT;
ALTER TABLE t ADD col2 INT;

Some use-cases, including DDL interpretation may be better served by component statements rather than the compound version.

As this org.jooq.Replacer has to adhere to the contract imposed on replacers, it can only operate on org.jooq.Queries and other org.jooq.Query wrapping elements, such as org.jooq.Block and other procedural scope statements.

 

3.4.4. The historic model API

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

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));

This API is completely mutable, and for historic reasons, early DSL API elements have inherited this mutability.

 

3.5. 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.

 

3.5.1. 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(table(name("a")))
      .fetch();
 

3.5.2. The WITH RECURSIVE clause

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

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.

Assuming a table like this:

CREATE TABLE directory (
  id           INT NOT NULL,
  parent_id    INT,

  -- In PostgreSQL, use TEXT instead, to work around https://github.com/jOOQ/jOOQ/issues/12067
  label        VARCHAR(50),

  CONSTRAINT pk_directory PRIMARY KEY (id),
  CONSTRAINT fk_directory FOREIGN KEY (parent_id) REFERENCES directory (id)
);

INSERT INTO directory VALUES ( 1, null, 'C:');
INSERT INTO directory VALUES ( 2,    1, 'eclipse');
INSERT INTO directory VALUES ( 3,    2, 'configuration');
INSERT INTO directory VALUES ( 4,    2, 'dropins');
INSERT INTO directory VALUES ( 5,    2, 'features');
INSERT INTO directory VALUES ( 7,    2, 'plugins');
INSERT INTO directory VALUES ( 8,    2, 'readme');
INSERT INTO directory VALUES ( 9,    8, 'readme_eclipse.html');
INSERT INTO directory VALUES (10,    2, 'src');
INSERT INTO directory VALUES (11,    2, 'eclipse.exe');

Using WITH RECURSIVE, you can now query the structure of this directory as follows:

WITH RECURSIVE t (
  id,
  name,
  path
) AS (
  SELECT
    DIRECTORY.ID,
    DIRECTORY.LABEL,
    DIRECTORY.LABEL
  FROM
    DIRECTORY
  WHERE
    DIRECTORY.PARENT_ID IS NULL
  UNION ALL
  SELECT
    DIRECTORY.ID,
    DIRECTORY.LABEL,
    t.path
      || '\'
      || DIRECTORY.LABEL
  FROM
    t
  JOIN
    DIRECTORY
  ON t.id = DIRECTORY.PARENT_ID
)
SELECT *
FROM
  t;
CommonTableExpression<?> cte = name("t").fields(
  "id",
  "name",
  "path"
).as(
  select(
    DIRECTORY.ID,
    DIRECTORY.LABEL,
    DIRECTORY.LABEL)
  .from(DIRECTORY)
  .where(DIRECTORY.PARENT_ID.isNull())
  .unionAll(
  select(
    DIRECTORY.ID,
    DIRECTORY.LABEL,
    field(name("t", "path"), VARCHAR)
      .concat("\\")
      .concat(DIRECTORY.LABEL))
  .from(table(name("t")))
  .join(DIRECTORY)
  .on(field(name("t", "id"), INTEGER)
    .eq(DIRECTORY.PARENT_ID)))
);

System.out.println(
    create.withRecursive(cte)
          .selectFrom(cte)
          .fetch()
);

The output would look like this:

+----+---------------------+---------------------------------------+
| id | name                | path                                  |
+----+---------------------+---------------------------------------+
| 1  | C:                  | C:                                    |
| 2  | eclipse             | C:\eclipse                            |
| 3  | configuration       | C:\eclipse\configuration              |
| 4  | dropins             | C:\eclipse\dropins                    |
| 11 | eclipse.exe         | C:\eclipse\eclipse.exe                |
| 5  | features            | C:\eclipse\features                   |
| 7  | plugins             | C:\eclipse\plugins                    |
| 8  | readme              | C:\eclipse\readme                     |
| 9  | readme_eclipse.html | C:\eclipse\readme\readme_eclipse.html |
| 10 | src                 | C:\eclipse\src                        |
+----+---------------------+---------------------------------------+

Caveats

The SQL language expresses the recursion syntactically, meaning the table t in the above example is being referenced from within the declaration of t. This isn't possible in a language like Java. Hence, we must use the identifier API to construct identifier references for tables and columns. This technique usually appears a bit more verbose than ordinary jOOQ API usage that is based on generated code for your schema.

 

3.5.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_IN > 2008
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_IN.gt(2008))
      .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.

 

3.5.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));

The following sections illustrate various features and subclauses of the SELECT clause.

 

3.5.3.1.1. Projection type safety

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

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 SelectField<?>> fields);
public static SelectSelectStep<Record> select(SelectField<?>... fields);

// Typesafe select methods:
public static <T1>         SelectSelectStep<Record1<T1>>         select(SelectField<T1> field1);
public static <T1, T2>     SelectSelectStep<Record2<T1, T2>>     select(SelectField<T1> field1, SelectField<T2> field2);
// [...]

The type that is being projected is the org.jooq.SelectField, see also the next section about SelectField. 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));

// Alternatively, just use var to infer the type:
var s3 = 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.

 

3.5.3.1.2. SelectField

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The org.jooq.SelectField type is used by any projection of the SELECT clause and the INSERT .. RETURNING clause. It has numerous subtypes, which are allowed as projections in jOOQ:

 

3.5.3.1.3. Tables as SelectField

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

An org.jooq.Table expression extends the org.jooq.SelectField type, and as such, can be used in the SELECT clause directly, as well as everywhere else a SelectField is accepted, e.g. in nested records. This is specifically useful for (generated) table references. The following shows how to project a nested org.jooq.TableRecord:

Result<Record2<AuthorRecord, BookRecord>> result =
create.select(AUTHOR, BOOK)
      .from(AUTHOR)
      .join(BOOK).on(AUTHOR.ID.eq(BOOK.AUTHOR_ID))
      .fetch();

This plays very well together with implicit joins:

Result<Record2<AuthorRecord, BookRecord>> result =
create.select(BOOK.author(), BOOK)
      .from(BOOK)
      .fetch();

Behind the scenes, the implementation may either be native in dialects that support this kind of projection (e.g. PostgreSQL), or emulated using the usual nested records emulations.

 

3.5.3.1.4. SELECT *

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

jOOQ supports the asterisk operator in projections both as a qualified asterisk (through Table.asterisk()) and as an unqualified asterisk (through DSL.asterisk()). It is also possible to omit the projection entirely, in case of which an asterisk may appear in generated SQL, if not all column names are known to jOOQ.

Whenever jOOQ generates an asterisk (explicitly, or because jOOQ doesn't know the exact projection), the column order, and the column set are defined by the database server, not jOOQ. If you're using generated code, this may lead to problems as there might be a different column order than expected, as well as too many or too few columns might be projected.

// Explicitly selects all columns available from BOOK - No asterisk
create.select().from(BOOK).fetch();

// Explicitly selects all columns available from BOOK and AUTHOR - No asterisk
create.select().from(BOOK, AUTHOR).fetch();
create.select().from(BOOK).crossJoin(AUTHOR).fetch();

// Renders a SELECT * statement, as columns are unknown to jOOQ - Implicit unqualified asterisk
create.select().from(table(name("BOOK"))).fetch();

// Renders a SELECT * statement - Explicit unqualified asterisk
create.select(asterisk()).from(BOOK).fetch();

// Renders a SELECT BOOK.* statement - Explicit qualified asterisk
create.select(BOOK.asterisk()).from(BOOK).fetch();
create.select(BOOK.asterisk(), AUTHOR.asterisk()).from(BOOK, AUTHOR).fetch();

With all of the above syntaxes, the row type (as discussed below) is unknown to jOOQ and to the Java compiler.

It is worth mentioning that in many cases, using an asterisk is a sign of an inefficient query because if not all columns are needed, too much data is transferred between client and server, plus some joins that could be eliminated otherwise, cannot.

 

3.5.3.1.5. SELECT * EXCEPT (...)

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A useful extension to the previously mentioned standard SQL SELECT * syntax is the BigQuery inspired * EXCEPT (columns) syntax, which takes all of a projection's columns, except some columns. Just like the asterisk itself, this is mainly useful for ad-hoc querying, but it can also be useful for an occasional jOOQ query.

// Renders a SELECT * statement - Explicit unqualified asterisk
create.select(asterisk().except(BOOK.ID)).from(BOOK).fetch();

// Renders a SELECT BOOK.* statement - Explicit qualified asterisk
create.select(BOOK.asterisk().except(BOOK.ID))
      .from(BOOK)
      .fetch();

create.select(BOOK.asterisk().except(BOOK.ID), AUTHOR.asterisk().except(AUTHOR.ID))
      .from(BOOK, AUTHOR)
      .fetch();

If a dialect doesn't support this syntax natively, jOOQ will just expand the syntax for you, explicitly, given the knowledge about meta data in generated code.

Dialect support

This example using jOOQ:

select(asterisk().except(LANGUAGE.ID)).from(LANGUAGE)

Translates to the following dialect specific expressions:

ASE, Access, Aurora MySQL, Aurora Postgres, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, Firebird, HSQLDB, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Postgres, Redshift, SQLDataWarehouse, SQLServer, SQLite, Sybase, Teradata, Trino, Vertica, YugabyteDB

SELECT LANGUAGE.CD, LANGUAGE.DESCRIPTION
FROM LANGUAGE

BigQuery

SELECT * EXCEPT (ID)
FROM LANGUAGE

H2

SELECT * EXCEPT (LANGUAGE.ID)
FROM LANGUAGE

Snowflake

SELECT * EXCLUDE (ID)
FROM LANGUAGE
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.1.6. SELECT DISTINCT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The DISTINCT keyword can be included in the method name, when constructing a SELECT clause, to remove duplicate tuples from the projection.

SELECT DISTINCT BOOK.TITLE FROM BOOK
create.selectDistinct(BOOK.TITLE).from(BOOK).fetch();

Dialect support

This example using jOOQ:

selectDistinct(BOOK.TITLE).from(BOOK)

Translates to the following dialect specific expressions:

All dialects

SELECT DISTINCT BOOK.TITLE
FROM BOOK
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.1.7. SELECT DISTINCT ON

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A useful, though perhaps a bit esoteric PostgreSQL specific extension to SELECT DISTINCT is the ON clause. Using this clause, PostgreSQL users can specify a distinctness criteria, but then produce other columns per distinct group from one of the group's tuples. This is normally not possible in SQL, but with ON, the first tuple in the group according to the ORDER BY clause can be accessed nonetheless. An example:

SELECT DISTINCT ON (BOOK.LANGUAGE_ID)
  BOOK.LANGUAGE_ID, BOOK.TITLE
FROM BOOK
ORDER BY BOOK.LANGUAGE_ID, BOOK.TITLE
Select<?> select1 = create.select(BOOK.LANGUAGE_ID, BOOK.TITLE)
      .distinctOn(BOOK.LANGUAGE_ID)
      .from(BOOK)
      .orderBy(BOOK.LANGUAGE_ID, BOOK.TITLE).fetch();

For syntactic reasons, the order of keywords had to be inversed as the PostgreSQL syntax cannot be easily reproduced in jOOQ's internal DSL. Quite likely, you might find jOOQ's syntax a bit more intuitive, though, as it more clearly separates the SELECT parts and the DISTINCT ON parts. Arguably, the DISTINCT ON clause should be positioned after ORDER BY, where it logically belongs.

Standard SQL equivalence

The PostgreSQL extension isn't really necessary as there is a standard SQL equivalence using ROW_NUMBER filtering. In the below example, we're using an extension to the standard, the QUALIFY clause, to illustrate:

SELECT BOOK.LANGUAGE_ID, BOOK.TITLE
FROM BOOK
QUALIFY ROW_NUMBER() OVER (PARTITION BY BOOK.LANGUAGE_ID ORDER BY BOOK.TITLE) = 1
ORDER BY BOOK.LANGUAGE_ID, BOOK.TITLE
Select<?> select1 = create.select(BOOK.LANGUAGE_ID, BOOK.TITLE)
      .from(BOOK)
      .qualify(rowNumber().over(partitionBy(BOOK.LANGUAGE_ID).orderBy(BOOK.TITLE)).eq(1))
      .orderBy(BOOK.LANGUAGE_ID, BOOK.TITLE).fetch();

Dialect support

This example using jOOQ:

select(BOOK.LANGUAGE_ID, BOOK.TITLE).distinctOn(BOOK.LANGUAGE_ID).from(BOOK).orderBy(BOOK.LANGUAGE_ID, BOOK.TITLE)

Translates to the following dialect specific expressions:

Aurora Postgres, ClickHouse, CockroachDB, DuckDB, H2, Postgres, YugabyteDB

SELECT DISTINCT ON (BOOK.LANGUAGE_ID) BOOK.LANGUAGE_ID, BOOK.TITLE
FROM BOOK
ORDER BY BOOK.LANGUAGE_ID, BOOK.TITLE

DB2, Exasol, Firebird, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica

SELECT t.LANGUAGE_ID, t.TITLE
FROM (
  SELECT
    BOOK.LANGUAGE_ID,
    BOOK.TITLE,
    row_number() OVER (
      PARTITION BY BOOK.LANGUAGE_ID
      ORDER BY BOOK.LANGUAGE_ID, BOOK.TITLE
    ) rn
  FROM BOOK
) t
WHERE rn = 1
ORDER BY LANGUAGE_ID, TITLE

ASE, Access, Aurora MySQL, BigQuery, Derby, HSQLDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.1.8. Convenience methods

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

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();
 

3.5.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:

 

3.5.3.3. JOIN operator

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

jOOQ supports many different types of standard and non-standard SQL JOIN operations. All of these JOIN methods can be called on org.jooq.Table types the (more info in joined tables section), 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();

For more information about the different types of join, please refer to the joined tables section.

 

3.5.3.4. Implicit path JOIN

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

In SQL, a lot of explicit JOIN clauses are written simply to retrieve a parent table's column from a given child table. For example, we'll write:

-- Get all books, their authors, and their respective language
SELECT
  a.first_name,
  a.last_name,
  b.title,
  l.cd AS language
FROM book b
JOIN author a ON b.author_id = a.id
JOIN language l ON b.language_id = l.id;

-- Count the number of books by author and language
SELECT
  a.first_name,
  a.last_name,
  l.cd AS language,
  COUNT(*)
FROM book
JOIN author a ON b.author_id = a.id
JOIN language l ON b.language_id = l.id
GROUP BY a.id, a.first_name, a.last_name, l.cd
ORDER BY a.first_name, a.last_name, l.cd

There is quite a bit of syntactic ceremony (or we could even call it "noise") to get a relatively simple job done. A much simpler notation would be using implicit joins:

-- Get all books, their authors, and their respective language
SELECT
  b.author.first_name,
  b.author.last_name,
  b.title,
  b.language.cd AS language
FROM book b;

-- Count the number of books by author and language
SELECT
  b.author.first_name,
  b.author.last_name,
  b.language.cd AS language,
  COUNT(*)
FROM book b
GROUP BY
  b.author_id,
  b.author.first_name,
  b.author.last_name,
  b.language.cd
ORDER BY
  b.author.first_name,
  b.author.last_name,
  b.language.cd

Notice how this alternative notation (depending on your taste) may look more tidy and straightforward, as the semantics of accessing a table's parent table (or an entity's parent entity) is straightforward.

From jOOQ 3.11 onwards, this syntax is supported for to-one relationship navigation, and from jOOQ 3.19 also for to-many relationship navigation. The code generator produces relevant navigation methods on generated tables, which can be used in a type safe way. The navigation method names are:

  • The parent table name, if there is only one foreign key between child table and parent table
  • The foreign key name, if there are more than one foreign keys between child table and parent table

This default behaviour can be overridden by using a Code Generator Strategy.

The jOOQ version of the previous queries looks like this:

// Get all books, their authors, and their respective language
create.select(
          BOOK.author().FIRST_NAME,
          BOOK.author().LAST_NAME,
          BOOK.TITLE,
          BOOK.language().CD.as("language"))
      .from(BOOK)
      .fetch();

// Count the number of books by author and language
create.select(
          BOOK.author().FIRST_NAME,
          BOOK.author().LAST_NAME,
          BOOK.language().CD.as("language"),
          count())
      .from(BOOK)
      .groupBy(
          BOOK.AUTHOR_ID,
          BOOK.author().FIRST_NAME,
          BOOK.author().LAST_NAME,
          BOOK.language().CD)
      .orderBy(
          BOOK.author().FIRST_NAME,
          BOOK.author().LAST_NAME,
          BOOK.language().CD)
      .fetch();

The generated SQL is almost identical to the original one - there is no performance penalty to this syntax.

Default JOIN type

The default type of join that is generated is:

  • INNER JOIN for to-one path segments with non-nullable parent
  • LEFT JOIN for to-one path segments with nullable parent

These defaults can be overridden with Settings.renderImplicitJoinType

How it works

During the SQL generation phase, implicit join paths are replaced by generated aliases for the path's last table. The paths are translated to a join graph, which is always LEFT JOINed to the path's "root table". If two paths share a common prefix, that prefix is also shared in the join graph.

Known limitations

  • Implicit JOINs can currently only be used to access columns, not to produce joins. I.e. it is not possible to write things like FROM book IMPLICIT JOIN book.author
  • Implicit JOINs are added to the SQL string after the entire SQL statement is available, for performance reasons. This means, that VisitListener SPI implementations cannot observe implicitly joined tables
 

3.5.3.5. 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.

 

3.5.3.6. 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

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();
 

3.5.3.7. 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.

 

3.5.3.7.1. GROUP BY columns

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The GROUP BY columns list specifies the columns whose values are used to form groups. The group columns can then be projected, whereas all the non-group columns can be aggregated. An example of such a grouped aggregation is this query:

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: a different and more powerful way of grouping data is to use the WINDOW clause and window functions.

Dialect support

This example using jOOQ:

select(BOOK.AUTHOR_ID, count()).from(BOOK).groupBy(BOOK.AUTHOR_ID)

Translates to the following dialect specific expressions:

All dialects

SELECT
  BOOK.AUTHOR_ID,
  count(*)
FROM BOOK
GROUP BY BOOK.AUTHOR_ID
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.7.2. GROUP BY column index

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

To work against some of SQL's verbosity, some SQL dialects support grouping by column index (starting from 1):

It is not recommended to use this feature in jOOQ as indexes tend to shift without developers noticing. The feature is supported mainly so jOOQ's SQL parser can parse and translate it. If you want to avoid redundancy in jOOQ, better resort to using a dynamic SQL style.

Dialect support

This example using jOOQ:

select(BOOK.AUTHOR_ID, count()).from(BOOK).groupBy(inline(1))

Translates to the following dialect specific expressions:

ASE, Access, DB2, Derby, Exasol, H2, HSQLDB, Hana, Oracle, SQLDataWarehouse, SQLServer, Sybase

SELECT
  BOOK.AUTHOR_ID,
  count(*)
FROM BOOK
GROUP BY BOOK.AUTHOR_ID

Aurora MySQL, Aurora Postgres, BigQuery, ClickHouse, CockroachDB, DuckDB, Firebird, Informix, MariaDB, MemSQL, MySQL, Postgres, Redshift, SQLite, Snowflake, Teradata, Trino, Vertica, YugabyteDB

SELECT
  BOOK.AUTHOR_ID,
  count(*)
FROM BOOK
GROUP BY 1
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.7.3. GROUP BY tables

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

An org.jooq.Table expression extends the org.jooq.GroupField type, and as such, can be used in the GROUP BY clause directly. This is specifically useful for (generated) table references. The following two statements are equivalent, although their generated SQL may differ, depending on native support:

// Ordinary grouping
create.select(AUTHOR.ID, count())
  .from(AUTHOR)
  .join(BOOK).on(AUTHOR.ID.eq(BOOK.AUTHOR_ID))
  .groupBy(AUTHOR.ID)
  .fetch();

// Convenient grouping by the entire table
create.select(AUTHOR.ID, count())
  .from(AUTHOR)
  .join(BOOK).on(AUTHOR.ID.eq(BOOK.AUTHOR_ID))
  .groupBy(AUTHOR)
  .fetch();
 

3.5.3.7.4. GROUP BY ROLLUP

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

In reports, it may be useful to run multiple aggregations across multiple dimensions of the data in one go. ROLLUP is one way to do this.

SELECT AUTHOR_ID, PUBLISHED_IN, COUNT(*)
FROM BOOK
GROUP BY ROLLUP (AUTHOR_ID, PUBLISHED_IN)
 
create.select(BOOK.AUTHOR_ID, BOOK.PUBLISHED_IN, count())
      .from(BOOK)
      .groupBy(rollup(BOOK.AUTHOR_ID, BOOK.PUBLISHED_IN))
      .fetch();

The above is a more concise (and possibly more performant) form of writing the following UNION ALL query:

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 ()

The ROLLUP function is just syntax sugar for a more complex GROUPING SETS specification. In general:

-- This
ROLLUP (A, B, C)

-- Is just short for this
GROUPING SETS ((A, B, C), (A, B), (A), ())

An example result set might look 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 ()
+-----------+--------------+----------+

Dialect support

This example using jOOQ:

select(BOOK.AUTHOR_ID, BOOK.LANGUAGE_ID, count()).from(BOOK).groupBy(rollup(BOOK.AUTHOR_ID, BOOK.LANGUAGE_ID))

Translates to the following dialect specific expressions:

Aurora MySQL, MariaDB, MySQL

SELECT
  BOOK.AUTHOR_ID,
  BOOK.LANGUAGE_ID,
  count(*)
FROM BOOK
GROUP BY BOOK.AUTHOR_ID, BOOK.LANGUAGE_ID
WITH ROLLUP

Aurora Postgres, ClickHouse, DB2, DuckDB, Hana, MemSQL, Oracle, Postgres, SQLDataWarehouse, SQLServer, Snowflake, Sybase, Teradata, Trino, Vertica

SELECT
  BOOK.AUTHOR_ID,
  BOOK.LANGUAGE_ID,
  count(*)
FROM BOOK
GROUP BY ROLLUP (BOOK.AUTHOR_ID, BOOK.LANGUAGE_ID)

ASE, Access, BigQuery, CockroachDB, Derby, Exasol, Firebird, H2, HSQLDB, Informix, Redshift, SQLite, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.7.5. GROUP BY CUBE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

In reports, it may be useful to run multiple aggregations across multiple dimensions of the data in one go. CUBE is one way to do this.

SELECT AUTHOR_ID, PUBLISHED_IN, COUNT(*)
FROM BOOK
GROUP BY CUBE (AUTHOR_ID, PUBLISHED_IN)
 
create.select(BOOK.AUTHOR_ID, BOOK.PUBLISHED_IN, count())
      .from(BOOK)
      .groupBy(cube(BOOK.AUTHOR_ID, BOOK.PUBLISHED_IN))
      .fetch();

The above is a more concise (and possibly more performant) form of writing the following UNION ALL query:

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
SELECT NULL, PUBLISHED_IN, COUNT(*)
FROM BOOK
GROUP BY LANGUAGE_ID
UNION ALL
SELECT NULL, NULL, COUNT(*)
FROM BOOK
GROUP BY ()

The CUBE function is just syntax sugar for a more complex GROUPING SETS specification. In general:

-- This
CUBE (A, B, C)

-- Is just short for this
GROUPING SETS ((A, B, C), (A, B), (A, C), (B, C), (A), (B), (C), ())

An example result set might look like this:

+-----------+--------------+----------+
| AUTHOR_ID | PUBLISHED_IN | COUNT(*) |
+-----------+--------------+----------+
|      NULL |         NULL |        4 | <- 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)
+-----------+--------------+----------+

Dialect support

This example using jOOQ:

select(BOOK.AUTHOR_ID, BOOK.LANGUAGE_ID, count()).from(BOOK).groupBy(cube(BOOK.AUTHOR_ID, BOOK.LANGUAGE_ID))

Translates to the following dialect specific expressions:

Aurora Postgres, ClickHouse, DB2, DuckDB, Hana, Oracle, Postgres, SQLServer, Snowflake, Sybase, Teradata, Trino, Vertica

SELECT
  BOOK.AUTHOR_ID,
  BOOK.LANGUAGE_ID,
  count(*)
FROM BOOK
GROUP BY CUBE (BOOK.AUTHOR_ID, BOOK.LANGUAGE_ID)

ASE, Access, Aurora MySQL, BigQuery, CockroachDB, Derby, Exasol, Firebird, H2, HSQLDB, Informix, MariaDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.7.6. GROUP BY GROUPING SETS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

In reports, it may be useful to run multiple aggregations across multiple dimensions of the data in one go. GROUPING SETS is one way to do this.

SELECT AUTHOR_ID, PUBLISHED_IN, COUNT(*)
FROM BOOK
GROUP BY GROUPING SETS ((AUTHOR_ID), (PUBLISHED_IN))
 
create.select(BOOK.AUTHOR_ID, BOOK.PUBLISHED_IN, count())
      .from(BOOK)
      .groupBy(groupingSets(BOOK.AUTHOR_ID, BOOK.PUBLISHED_IN))
      .fetch();

The above is a more concise (and possibly more performant) form of writing the following UNION ALL query:

SELECT AUTHOR_ID, NULL AS PUBLISHED_IN, COUNT(*)
FROM BOOK
GROUP BY AUTHOR_ID
UNION ALL
SELECT NULL, PUBLISHED_IN, COUNT(*)
FROM BOOK
GROUP BY LANGUAGE_ID

An example result set might look like this:

+-----------+--------------+----------+
| AUTHOR_ID | PUBLISHED_IN | COUNT(*) |
+-----------+--------------+----------+
|      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)
|         2 |         NULL |        2 | <- GROUP BY (AUTHOR_ID)
+-----------+--------------+----------+

Note that the most common GROUPING SETS specifications have a dedicated, special syntax:

Dialect support

This example using jOOQ:

select(BOOK.AUTHOR_ID, BOOK.LANGUAGE_ID, count()).from(BOOK).groupBy(groupingSets(BOOK.AUTHOR_ID, BOOK.LANGUAGE_ID))

Translates to the following dialect specific expressions:

Aurora Postgres, ClickHouse, DB2, DuckDB, Hana, Oracle, Postgres, SQLServer, Snowflake, Sybase, Teradata, Trino, Vertica

SELECT
  BOOK.AUTHOR_ID,
  BOOK.LANGUAGE_ID,
  count(*)
FROM BOOK
GROUP BY GROUPING SETS (
  (BOOK.AUTHOR_ID),
  (BOOK.LANGUAGE_ID)
)

ASE, Access, Aurora MySQL, BigQuery, CockroachDB, Derby, Exasol, Firebird, H2, HSQLDB, Informix, MariaDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.7.7. GROUP BY empty grouping set

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A special kind of GROUPING SET is the empty grouping set, which can be achieved in standard SQL and many SQL dialects using GROUP BY (). It is implicit, whenever an aggregate function is present in a query, but not an explicit GROUP BY clause.

SELECT COUNT(*)
FROM BOOK
GROUP BY ()
 
create.selectCount()
      .from(BOOK)
      .groupBy()
      .fetch();

Dialect support

This example using jOOQ:

selectCount().from(BOOK).groupBy()

Translates to the following dialect specific expressions:

Access

SELECT count(*)
FROM BOOK, (select count(*) dual from MSysResources) as empty_grouping_dummy_table
GROUP BY empty_grouping_dummy_table.dual

ASE, SQLDataWarehouse

SELECT count(*)
FROM BOOK, (select 1 as dual) as empty_grouping_dummy_table
GROUP BY empty_grouping_dummy_table.dual

Aurora MySQL, MemSQL

SELECT count(*)
FROM BOOK
GROUP BY (SELECT 1
FROM DUAL)

Aurora Postgres, BigQuery, DB2, DuckDB, Exasol, H2, Oracle, Postgres, SQLServer, Sybase, Teradata, Trino

SELECT count(*)
FROM BOOK
GROUP BY ()

ClickHouse, CockroachDB, MariaDB, MySQL, Redshift, SQLite, Vertica, YugabyteDB

SELECT count(*)
FROM BOOK
GROUP BY (SELECT 1)

Derby, HSQLDB

SELECT count(*)
FROM BOOK
GROUP BY 0

Firebird

SELECT count(*)
FROM BOOK
GROUP BY (SELECT 1
FROM RDB$DATABASE)

Hana, Snowflake

SELECT count(*)
FROM BOOK
GROUP BY GROUPING SETS (())

Informix

SELECT count(*)
FROM BOOK, (select 1 as dual from systables where tabid = 1) as empty_grouping_dummy_table
GROUP BY empty_grouping_dummy_table.dual
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.8. 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();
 

3.5.3.9. WINDOW clauseWINDOW 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));

create.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: https://blog.jooq.org/probably-the-coolest-sql-feature-window-functions/

 

3.5.3.10. QUALIFY clause

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A select few dialects support a very useful QUALIFY clause, which can be used to filter using window functions without having to nest the window function calculation in a derived table.

For example, if you do not have access to the WITH TIES clause, you could easily emulate it like this. The following query finds the top 5 author WITH TIES, counting their books:

SELECT AUTHOR_ID, count(*)
FROM BOOK
GROUP BY AUTHOR_ID
QUALIFY rank() OVER (ORDER BY count(*) DESC) <= 5
ORDER BY count(*) DESC
 
create.select(BOOK.AUTHOR_ID, count())
      .from(BOOK)
      .groupBy(BOOK.AUTHOR_ID)
      .qualify(rank().over(orderBy(count().desc())).le(5))
      .orderBy(count().desc())
      .fetch();
If your dialect does not support QUALIFY natively, then jOOQ can apply a transformation from QUALIFY to derived tables.
 

3.5.3.11. 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.

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.

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.

 

3.5.3.11.1. Ordering by field index

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

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(inline(1).asc(), inline(2).desc())
      .fetch();
This practice is generally discouraged as field indexes may shift in the SELECT clause, and developers might forget to update the indexes in ORDER BY. It is mainly useful for quick-and-dirty ad-hoc SQL. See also the don't do this section about this topic.

Dialect support

This example using jOOQ:

select(BOOK.ID).from(BOOK).orderBy(1)

Translates to the following dialect specific expressions:

All dialects

SELECT BOOK.ID
FROM BOOK
ORDER BY 1
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.11.2. Ordering and NULLS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

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

Dialect support

This example using jOOQ:

select(AUTHOR.FIRST_NAME).from(AUTHOR).orderBy(AUTHOR.FIRST_NAME.asc().nullsLast())

Translates to the following dialect specific expressions:

Access, SQLServer

SELECT AUTHOR.FIRST_NAME
FROM AUTHOR
ORDER BY iif(AUTHOR.FIRST_NAME IS NOT NULL, 0, 1), AUTHOR.FIRST_NAME ASC

ASE, Aurora MySQL, MemSQL, MySQL, SQLDataWarehouse, Sybase

SELECT AUTHOR.FIRST_NAME
FROM AUTHOR
ORDER BY CASE
  WHEN AUTHOR.FIRST_NAME IS NOT NULL THEN 0
  ELSE 1
END, AUTHOR.FIRST_NAME ASC

Aurora Postgres, BigQuery, ClickHouse, CockroachDB, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Informix, Oracle, Postgres, SQLite, Snowflake, Teradata, Trino, YugabyteDB

SELECT AUTHOR.FIRST_NAME
FROM AUTHOR
ORDER BY AUTHOR.FIRST_NAME ASC NULLS LAST

DB2, MariaDB, Redshift, Vertica

SELECT AUTHOR.FIRST_NAME
FROM AUTHOR
ORDER BY nvl2(AUTHOR.FIRST_NAME, 0, 1), AUTHOR.FIRST_NAME ASC
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.11.3. Ordering using CASE expressions

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

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(case_(BOOK.TITLE)
               .when("1984", 0)
               .when("Animal Farm", 1)
               .else_(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(Map.of(
          "1984", 1,
          "Animal Farm", 13,
          "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();

Dialect support

This example using jOOQ:

select(BOOK.ID).from(BOOK).orderBy(BOOK.TITLE.sortAsc("1984", "Animal Farm"))

Translates to the following dialect specific expressions:

Access

SELECT BOOK.ID
FROM BOOK
ORDER BY SWITCH(BOOK.TITLE = '1984', 0, BOOK.TITLE = 'Animal Farm', 1) ASC

ASE, Aurora MySQL, Aurora Postgres, BigQuery, ClickHouse, CockroachDB, DB2, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Postgres, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

SELECT BOOK.ID
FROM BOOK
ORDER BY CASE BOOK.TITLE
  WHEN '1984' THEN 0
  WHEN 'Animal Farm' THEN 1
END ASC

Derby

SELECT BOOK.ID
FROM BOOK
ORDER BY CASE
  WHEN BOOK.TITLE = '1984' THEN 0
  WHEN BOOK.TITLE = 'Animal Farm' THEN 1
END ASC
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.11.4. Oracle's ORDER SIBLINGS BY clause

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

jOOQ also supports Oracle's SIBLINGS keyword to be used with ORDER BY clauses for hierarchical queries using CONNECT BY

create.select(sysConnectByPath(DIRECTORY.LABEL, "/").as("dir"))
      .from(DIRECTORY)
      .startWith(DIRECTORY.PARENT_ID.isNull())
      .connectBy(prior(DIRECTORY.ID).eq(DIRECTORY.PARENT_ID))
      .orderSiblingsBy(DIRECTORY.LABEL)
      .fetch();
 

3.5.3.12. 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 has not been standardised 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).orderBy(BOOK.ID).limit(1).offset(2).fetch();

This will limit the result to 1 books skipping the first 2 books (offset 2). 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, and SQLite
SELECT * FROM BOOK ORDER BY ID LIMIT 1 OFFSET 2

-- Derby, SQL Server 2012, Oracle 12c, PostgreSQL, the SQL:2008 standard
SELECT * FROM BOOK ORDER BY ID OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY

-- Informix has SKIP .. FIRST support
SELECT SKIP 2 FIRST 1 * FROM BOOK ORDER BY ID

-- Ingres (almost the SQL:2008 standard)
SELECT * FROM BOOK ORDER BY ID OFFSET 2 FETCH FIRST 1 ROWS ONLY

-- Firebird
SELECT * FROM BOOK ORDER BY ID ROWS 2 TO 3

-- Sybase SQL Anywhere
SELECT TOP 1 START AT 3 * FROM BOOK ORDER BY ID

-- DB2 (almost the SQL:2008 standard, without OFFSET)
SELECT * FROM BOOK ORDER BY ID FETCH FIRST 1 ROWS ONLY

-- Sybase ASE, SQL Server 2008 (without OFFSET)
SELECT TOP 1 * FROM BOOK ORDER BY ID

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.

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.

 

3.5.3.13. WITH TIES clause

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The previous chapter talked about the LIMIT clause, which limits the result set to a certain number of rows. The SQL standard specifies the following syntax:

OFFSET m { ROW | ROWS }
FETCH { FIRST | NEXT } n { ROW | ROWS } { ONLY | WITH TIES }

By default, most users will use the semantics of the ONLY keyword, meaning a LIMIT 5 expression (or FETCH NEXT 5 ROWS ONLY expression) will result in at most 5 rows. The alternative clause WITH TIES will return at most 5 rows, except if the 5th row and the 6th row (and so on) are "tied" according to the ORDER BY clause, meaning that the ORDER BY clause does not deterministically produce a 5th or 6th row. For example, let's look at our book table:

SELECT *
FROM book
ORDER BY author_id
FETCH NEXT 1 ROWS WITH TIES
DSL.using(configuration)
   .selectFrom(BOOK)
   .orderBy(BOOK.AUTHOR_ID)
   .limit(1).withTies()
   .fetch();

Resulting in:

+----+----------+-------------+
| id | actor_id | title       |
+----+----------+-------------+
|  1 |        1 | 1984        |
|  2 |        1 | Animal Farm |
+----+----------+-------------+

We're now getting two rows because both rows "tied" when ordering them by ACTOR_ID. The database cannot really pick the next 1 row, so they're both returned. If we omit the WITH TIES clause, then only a random one of the rows would be returned.

Dialect support

This example using jOOQ:

select(BOOK.ID).from(BOOK).orderBy(BOOK.AUTHOR_ID).limit(1).withTies()

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DB2, Firebird, Hana, MySQL, Redshift, Sybase, Vertica, YugabyteDB

SELECT v0 ID
FROM (
  SELECT
    BOOK.ID v0,
    rank() OVER (ORDER BY BOOK.AUTHOR_ID) rn
  FROM BOOK
) x
WHERE rn BETWEEN (0 + 1) AND (0 + 1)
ORDER BY rn

BigQuery, DuckDB, Exasol, Snowflake

SELECT BOOK.ID
FROM BOOK
QUALIFY rank() OVER (ORDER BY BOOK.AUTHOR_ID) BETWEEN (0 + 1) AND (0 + 1)

ClickHouse, H2, MariaDB, Oracle, Postgres, Trino

SELECT BOOK.ID
FROM BOOK
ORDER BY BOOK.AUTHOR_ID
FETCH NEXT 1 ROWS WITH TIES

Informix

SELECT v0 ID
FROM (
  SELECT *
  FROM (
    SELECT
      BOOK.ID v0,
      rank() OVER (ORDER BY BOOK.AUTHOR_ID) rn
    FROM BOOK
  ) x
) x
WHERE rn BETWEEN (0 + 1) AND (0 + 1)
ORDER BY rn

SQLDataWarehouse, SQLServer, Teradata

SELECT TOP 1 WITH TIES BOOK.ID
FROM BOOK
ORDER BY BOOK.AUTHOR_ID

ASE, Access, Aurora MySQL, Derby, HSQLDB, MemSQL, SQLite

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.14. SEEK clause

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

One of the previous chapters 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 (learn about other synthetic sql syntaxes). 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(lastValue, lastId) // from last page: value = 2, id = 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:

Further information about offset pagination vs. keyset pagination performance can be found on our partner page:

 

3.5.3.15. FOR clause

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

While both XML and JSON usage in SQL has been standardised in more recent versions of the SQL standard, SQL Server has always had some very convenient utilities at the end of a SELECT statement, which allow for converting SQL tables into the most common XML or JSON representations.

Starting with jOOQ 3.14, these syntaxes are supported in jOOQ as well, and if possible, emulated in other dialects which have native XML or JSON support.

FOR XML

Consider the following query

SELECT id, title
FROM book
ORDER BY id
FOR XML PATH ('book'), ROOT ('books')
 
create.select(BOOK.ID, BOOK.TITLE)
      .from(BOOK)
      .orderBy(BOOK.ID)
      .forXML().path("book").root("books")
      .fetch();

This query produces a document like this:

<books>
  <book><id>1</id><title>1984</title></book>
  <book><id>2</id><title>Animal Farm</title></book>
  <book><id>3</id><title>O Alquimista</title></book>
  <book><id>4</id><title>Brida</title></book>
</books>

FOR JSON

JSON is just XML with less syntax and less features. So the FOR JSON syntax in SQL Server is almost the same as the above FOR XML syntax:

SELECT id, title
FROM book
ORDER BY id
FOR JSON PATH
 
create.select(BOOK.ID, BOOK.TITLE)
      .from(BOOK)
      .orderBy(BOOK.ID)
      .forJSON().path()
      .fetch();

This query produces a document like this:

[
  {"id": 1, "title": "1984"},
  {"id": 2, "title": "Animal Farm"},
  {"id": 3, "title": "O Alquimista"},
  {"id": 4, "title": "Brida"}
]
 

3.5.3.16. 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 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 SQL Server database, 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.

 

3.5.3.17. Set operations

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.

All of these set operations come with 2 flavours:

  • DISTINCT (the default): Removing duplicates after applying the set operation
  • ALL: Retaining duplicates after applying the set operation
 

3.5.3.17.1. Type safety

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Two subselects of degree less than 22 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
 

3.5.3.17.2. Projection rowtype

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Much like most dialects use only the first set operation subquery's column names and types for the resulting row type, so does jOOQ.This is particularly interesting when applying converters, including ad-hoc converters or converters attached to generated code.

Since jOOQ does not know which row is produced by which union subquery, it cannot disambiguate these rows in case the projection row type isn't exactly identical. As such, the ad-hoc converter in the following example is ignored:

Result<Record1<Integer>> result =
create.select(BOOK.ID)
   .from(BOOK)
   .union(

    // This has no effect
    select(AUTHOR.ID.convertFrom(i -> -i))
   .from(AUTHOR))
   .fetch();

While this can lead to subtle bugs, it makes perfect sense, knowing that a Converter is always applied at the client side of the execution.

 

3.5.3.17.3. Differences to standard SQL

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

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 set operation subselect may not immediately contain any ORDER BY clause or LIMIT clause (unless you wrap the subselect into a derived table), 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

Dialect support

This example using jOOQ:

select(BOOK.ID).from(BOOK).orderBy(BOOK.ID).limit(1).union(select(AUTHOR.ID).from(AUTHOR).orderBy(AUTHOR.ID).limit(1)).orderBy(1)

Translates to the following dialect specific expressions:

Access, SQLDataWarehouse, Sybase

(
  SELECT TOP 1 BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID
)
UNION (
  SELECT TOP 1 AUTHOR.ID
  FROM AUTHOR
  ORDER BY AUTHOR.ID
)
ORDER BY 1

ASE, SQLServer

SELECT *
FROM (
  SELECT TOP 1 BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID
) x
UNION 
SELECT *
FROM (
  SELECT TOP 1 AUTHOR.ID
  FROM AUTHOR
  ORDER BY AUTHOR.ID
) x
ORDER BY 1

Aurora MySQL, Aurora Postgres, CockroachDB, DuckDB, Exasol, HSQLDB, Hana, MySQL, Redshift, Snowflake, Vertica, YugabyteDB

(
  SELECT BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID
  LIMIT 1
)
UNION (
  SELECT AUTHOR.ID
  FROM AUTHOR
  ORDER BY AUTHOR.ID
  LIMIT 1
)
ORDER BY 1

BigQuery

(
  SELECT BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID
  LIMIT 1
)
UNION DISTINCT (
  SELECT AUTHOR.ID
  FROM AUTHOR
  ORDER BY AUTHOR.ID
  LIMIT 1
)
ORDER BY 1

ClickHouse

SELECT 
  t.*
FROM (
  (
    SELECT BOOK.ID
    FROM BOOK
    ORDER BY BOOK.ID
    LIMIT 1
  )
  UNION DISTINCT (
    SELECT AUTHOR.ID
    FROM AUTHOR
    ORDER BY AUTHOR.ID
    LIMIT 1
  )
) t
ORDER BY 1

DB2, Firebird, Oracle

SELECT *
FROM (
  SELECT BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID
  FETCH NEXT 1 ROWS ONLY
) x
UNION 
SELECT *
FROM (
  SELECT AUTHOR.ID
  FROM AUTHOR
  ORDER BY AUTHOR.ID
  FETCH NEXT 1 ROWS ONLY
) x
ORDER BY 1

Derby, H2, MariaDB, Postgres, Trino

(
  SELECT BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID
  FETCH NEXT 1 ROWS ONLY
)
UNION (
  SELECT AUTHOR.ID
  FROM AUTHOR
  ORDER BY AUTHOR.ID
  FETCH NEXT 1 ROWS ONLY
)
ORDER BY 1

Informix

SELECT *
FROM (
  SELECT BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID
) x
UNION 
SELECT *
FROM (
  SELECT *
  FROM (
    SELECT FIRST 1 AUTHOR.ID
    FROM AUTHOR
    ORDER BY AUTHOR.ID
  ) x
) x
ORDER BY 1

MemSQL

SELECT 
  t.*
FROM (
  (
    SELECT BOOK.ID
    FROM BOOK
    ORDER BY BOOK.ID
    LIMIT 1
  )
  UNION (
    SELECT AUTHOR.ID
    FROM AUTHOR
    ORDER BY AUTHOR.ID
    LIMIT 1
  )
) t
ORDER BY 1

SQLite

SELECT *
FROM (
  SELECT BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID
  LIMIT 1
) x
UNION 
SELECT *
FROM (
  SELECT AUTHOR.ID
  FROM AUTHOR
  ORDER BY AUTHOR.ID
  LIMIT 1
) x
ORDER BY 1

Teradata

(
  SELECT TOP 1 BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID
)
UNION (
  SELECT *
  FROM (
    SELECT TOP 1 AUTHOR.ID
    FROM AUTHOR
    ORDER BY AUTHOR.ID
  ) x
)
ORDER BY 1
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.17.4. UNION

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A UNION operation combines two subquery results of compatible row type into a single result. 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, see also this section of the manual. 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();

Dialect support

This example using jOOQ:

select(BOOK.ID).from(BOOK).union(select(AUTHOR.ID).from(AUTHOR)).orderBy(BOOK.ID)

Translates to the following dialect specific expressions:

ASE, Access, Aurora MySQL, Aurora Postgres, CockroachDB, DB2, Derby, DuckDB, Exasol, H2, HSQLDB, Hana, Informix, MariaDB, MySQL, Oracle, Postgres, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

SELECT BOOK.ID
FROM BOOK
UNION
SELECT AUTHOR.ID
FROM AUTHOR
ORDER BY ID

BigQuery

SELECT BOOK.ID
FROM BOOK
UNION DISTINCT
SELECT AUTHOR.ID
FROM AUTHOR
ORDER BY ID

ClickHouse

SELECT 
  t.*
FROM (
  SELECT BOOK.ID
  FROM BOOK
  UNION DISTINCT
  SELECT AUTHOR.ID
  FROM AUTHOR
) t
ORDER BY ID

Firebird

SELECT BOOK.ID
FROM BOOK
UNION
SELECT AUTHOR.ID
FROM AUTHOR
ORDER BY 1

MemSQL

SELECT 
  t.*
FROM (
  SELECT BOOK.ID
  FROM BOOK
  UNION
  SELECT AUTHOR.ID
  FROM AUTHOR
) t
ORDER BY ID
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.17.5. INTERSECT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

INTERSECT is the operation that produces only those values that are returned by both subselects. By default, this removes duplicate rows. Use INTERSECT ALL in order to retain them, and require duplicates to appear in both subqueries.

SELECT ID FROM BOOK
INTERSECT ALL
SELECT ID FROM AUTHOR
 
create.select(BOOK.ID).from(BOOK)
      .intersectAll(
create.select(AUTHOR.ID).from(AUTHOR))
      .fetch();

Dialect support

This example using jOOQ:

select(BOOK.ID).from(BOOK).intersect(select(AUTHOR.ID).from(AUTHOR)).orderBy(BOOK.ID)

Translates to the following dialect specific expressions:

ASE, Aurora Postgres, CockroachDB, DB2, Derby, DuckDB, Exasol, H2, HSQLDB, Hana, Informix, MariaDB, MySQL, Oracle, Postgres, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

SELECT BOOK.ID
FROM BOOK
INTERSECT
SELECT AUTHOR.ID
FROM AUTHOR
ORDER BY ID

BigQuery

SELECT BOOK.ID
FROM BOOK
INTERSECT DISTINCT
SELECT AUTHOR.ID
FROM AUTHOR
ORDER BY ID

ClickHouse

SELECT 
  t.*
FROM (
  SELECT BOOK.ID
  FROM BOOK
  INTERSECT DISTINCT
  SELECT AUTHOR.ID
  FROM AUTHOR
) t
ORDER BY ID

MemSQL

SELECT 
  t.*
FROM (
  SELECT BOOK.ID
  FROM BOOK
  INTERSECT
  SELECT AUTHOR.ID
  FROM AUTHOR
) t
ORDER BY ID

Access, Aurora MySQL, Firebird, Redshift

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.17.6. EXCEPT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

EXCEPT (or MINUS in Oracle) is the operation that returns only those values that are returned exclusively in the first subselect. By default, this removes duplicate rows. Use EXCEPT ALL in order to retain them, and require duplicates to appear in both subqueries.

SELECT ID FROM BOOK
EXCEPT ALL
SELECT ID FROM AUTHOR
 
create.select(BOOK.ID).from(BOOK)
      .exceptAll(
create.select(AUTHOR.ID).from(AUTHOR))
      .fetch();

Dialect support

This example using jOOQ:

select(BOOK.ID).from(BOOK).except(select(AUTHOR.ID).from(AUTHOR)).orderBy(BOOK.ID)

Translates to the following dialect specific expressions:

ASE, Aurora Postgres, CockroachDB, DB2, Derby, DuckDB, Exasol, H2, HSQLDB, Hana, Informix, MariaDB, MySQL, Postgres, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

SELECT BOOK.ID
FROM BOOK
EXCEPT
SELECT AUTHOR.ID
FROM AUTHOR
ORDER BY ID

BigQuery

SELECT BOOK.ID
FROM BOOK
EXCEPT DISTINCT
SELECT AUTHOR.ID
FROM AUTHOR
ORDER BY ID

ClickHouse

SELECT 
  t.*
FROM (
  SELECT BOOK.ID
  FROM BOOK
  EXCEPT DISTINCT
  SELECT AUTHOR.ID
  FROM AUTHOR
) t
ORDER BY ID

MemSQL

SELECT 
  t.*
FROM (
  SELECT BOOK.ID
  FROM BOOK
  EXCEPT
  SELECT AUTHOR.ID
  FROM AUTHOR
) t
ORDER BY ID

Oracle

SELECT BOOK.ID
FROM BOOK
MINUS
SELECT AUTHOR.ID
FROM AUTHOR
ORDER BY ID

Access, Aurora MySQL, Firebird, Redshift

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.3.18. 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 SQL Server documentation also explains this, with slightly different clauses:

  • FROM
  • ON
  • JOIN
  • WHERE
  • GROUP BY
  • WITH CUBE or WITH 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 and idiomatic idea at first, jOOQ's take here is that this only complicates translation to more advanced SQL statements while impairing readability for those users that are used to writing 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.

jOOQ is designed to look just like SQL. For these reasons, the jOOQ DSL API is modelled in SQL's lexical order.

 

3.5.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. The following sections describe the various operation modes of the jOOQ INSERT statement.

 

3.5.4.1. INSERT .. VALUES

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

INSERT .. VALUES with a single row

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 .. VALUES with multiple rows

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();

If your inserted rows or records are dynamic, you can use valuesOfRows() or valuesOfRecords() instead:

INSERT INTO AUTHOR
       (ID, FIRST_NAME, LAST_NAME)
VALUES (100, 'Hermann', 'Hesse'),
       (101, 'Alfred', 'Döblin');
 
List<Record3<Integer, String, String>> records = ...
create.insertInto(AUTHOR,
        AUTHOR.ID, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
      .valuesOfRecords(records)
      .execute();

Dialect support

This example using jOOQ:

insertInto(AUTHOR).columns(AUTHOR.ID, AUTHOR.LAST_NAME).values(100, "Hesse")

Translates to the following dialect specific expressions:

All dialects

INSERT INTO AUTHOR (ID, LAST_NAME)
VALUES (
  100, 
  'Hesse'
)
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.4.2. INSERT .. DEFAULT VALUES

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A lesser-known syntactic feature of SQL is the INSERT .. DEFAULT VALUES statement, where a single record is inserted, containing only DEFAULT values for every row. It is written as such:

INSERT INTO AUTHOR
DEFAULT VALUES;
 
create.insertInto(AUTHOR)
      .defaultValues()
      .execute();

This can make a lot of sense in situations where you want to "reserve" a row in the database for an subsequent UPDATE statement within the same transaction. Or if you just want to send an event containing trigger-generated default values, such as IDs or timestamps.

The DEFAULT VALUES clause is not supported in all databases, but jOOQ can emulate it using the equivalent statement:

INSERT INTO AUTHOR
    (ID, FIRST_NAME, LAST_NAME, ...)
VALUES (
	DEFAULT,
	DEFAULT,
	DEFAULT, ...);
 
create.insertInto(
        AUTHOR, AUTHOR.ID, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, ...)
      .values(
      	defaultValue(AUTHOR.ID),
      	defaultValue(AUTHOR.FIRST_NAME),
      	defaultValue(AUTHOR.LAST_NAME), ...)
      .execute();

The DEFAULT keyword (or DSL#defaultValue() method) can also be used for individual columns only, although that will have the same effect as leaving the column away entirely.

Dialect support

This example using jOOQ:

insertInto(AUTHOR).defaultValues()

Translates to the following dialect specific expressions:

ASE, Access, Aurora MySQL, BigQuery, DB2, Derby, MariaDB, MemSQL, MySQL, Oracle

INSERT INTO AUTHOR
VALUES (
  DEFAULT,
  DEFAULT,
  DEFAULT,
  DEFAULT,
  DEFAULT,
  DEFAULT
)

Aurora Postgres, ClickHouse, CockroachDB, DuckDB, Exasol, Firebird, H2, HSQLDB, Postgres, SQLDataWarehouse, SQLServer, Teradata, YugabyteDB

INSERT INTO AUTHOR
DEFAULT VALUES

Hana, Informix, Redshift, SQLite, Sybase, Vertica

INSERT INTO AUTHOR (FIRST_NAME, DATE_OF_BIRTH, YEAR_OF_BIRTH, DISTINGUISHED)
VALUES (
  NULL, 
  NULL, 
  NULL, 
  NULL
)

Snowflake

INSERT INTO AUTHOR (FIRST_NAME, DATE_OF_BIRTH, YEAR_OF_BIRTH, DISTINGUISHED)
SELECT NULL, NULL, NULL, NULL

Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.4.3. INSERT .. SET

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

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")
      .execute();

Multi row variant:

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.

Dialect support

This example using jOOQ:

insertInto(AUTHOR).set(AUTHOR.ID, 100).set(AUTHOR.LAST_NAME, "Hesse")

Translates to the following dialect specific expressions:

All dialects

INSERT INTO AUTHOR (ID, LAST_NAME)
VALUES (
  100, 
  'Hesse'
)
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.4.4. INSERT .. SELECT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

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(selectFrom(AUTHOR).where(AUTHOR.DECEASED.isTrue()))
      .execute();

Dialect support

This example using jOOQ:

insertInto(AUTHOR).columns(AUTHOR.ID, AUTHOR.LAST_NAME).select(select(val(100), val("Hesse")))

Translates to the following dialect specific expressions:

Access

INSERT INTO AUTHOR (ID, LAST_NAME)
SELECT 100, 'Hesse'
FROM (
  SELECT count(*) dual
  FROM MSysResources
) AS dual

ASE, Aurora Postgres, BigQuery, ClickHouse, CockroachDB, DuckDB, Exasol, H2, MariaDB, MySQL, Oracle, Postgres, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Trino, Vertica, YugabyteDB

INSERT INTO AUTHOR (ID, LAST_NAME)
SELECT 100, 'Hesse'

Aurora MySQL, MemSQL

INSERT INTO AUTHOR (ID, LAST_NAME)
SELECT 100, 'Hesse'
FROM DUAL

DB2

INSERT INTO AUTHOR (ID, LAST_NAME)
SELECT 100, 'Hesse'
FROM SYSIBM.DUAL

Derby

INSERT INTO AUTHOR (ID, LAST_NAME)
SELECT 100, 'Hesse'
FROM SYSIBM.SYSDUMMY1

Firebird

INSERT INTO AUTHOR (ID, LAST_NAME)
SELECT 100, 'Hesse'
FROM RDB$DATABASE

Hana, Sybase

INSERT INTO AUTHOR (ID, LAST_NAME)
SELECT 100, 'Hesse'
FROM SYS.DUMMY

HSQLDB

INSERT INTO AUTHOR (ID, LAST_NAME)
SELECT 100, 'Hesse'
FROM (VALUES (1)) AS dual (dual)

Informix

INSERT INTO AUTHOR (ID, LAST_NAME)
SELECT 100, 'Hesse'
FROM (
  SELECT 1 AS dual
  FROM systables
  WHERE (tabid = 1)
) AS dual

Teradata

INSERT INTO AUTHOR (ID, LAST_NAME)
SELECT 100, 'Hesse'
FROM (
  SELECT 1 AS "dual"
) AS "dual"
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.4.5. INSERT .. ON DUPLICATE KEY UPDATE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

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();

Dialect support

This example using jOOQ:

insertInto(AUTHOR, AUTHOR.ID, AUTHOR.LAST_NAME).values(3, "X").onDuplicateKeyUpdate().set(AUTHOR.LAST_NAME, "X")

Translates to the following dialect specific expressions:

Aurora MySQL, MariaDB, MemSQL, MySQL

INSERT INTO AUTHOR (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)
ON DUPLICATE KEY UPDATE
  AUTHOR.LAST_NAME = 'X'

Aurora Postgres, CockroachDB, DuckDB, Postgres, YugabyteDB

INSERT INTO AUTHOR (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)
ON CONFLICT (ID)
DO UPDATE
SET
  LAST_NAME = 'X'

DB2

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM SYSIBM.DUAL
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Derby

MERGE INTO AUTHOR
USING SYSIBM.SYSDUMMY1
ON AUTHOR.ID = 3
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)

Exasol

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Firebird

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM RDB$DATABASE
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

H2

MERGE INTO AUTHOR
USING (
  SELECT
    3 ID,
    'X' LAST_NAME
) t
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Hana

MERGE INTO AUTHOR
USING (
  (
    SELECT
      3 ID,
      'X' LAST_NAME
    FROM SYS.DUMMY
  )
) t
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

HSQLDB

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM (VALUES (1)) AS dual (dual)
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Oracle

MERGE INTO AUTHOR
USING (
  (
    SELECT
      3 ID,
      'X' LAST_NAME
  )
) t
ON (AUTHOR.ID = t.ID)
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Snowflake

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

SQLite

INSERT INTO AUTHOR (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)
ON CONFLICT 
DO UPDATE
SET
  LAST_NAME = 'X'

SQLServer

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
);

Sybase

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM SYS.DUMMY
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Teradata

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM (
    SELECT 1 AS "dual"
  ) AS "dual"
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

ASE, Access, BigQuery, ClickHouse, Informix, Redshift, SQLDataWarehouse, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.4.6. INSERT .. ON DUPLICATE KEY UPDATE .. EXCLUDED

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Starting with MySQL 8, there's support for aliasing the INSERT .. VALUES clause in order to reference its columns from the ON DUPLICATE KEY UPDATE clause:

INSERT INTO AUTHOR (ID, LAST_NAME)
VALUES
  (3, 'Koontz'),
  (4, 'Shakespeare') AS t   -- Declare source data alias
ON DUPLICATE KEY UPDATE
SET last_name = t.last_name -- Reference source data alias

jOOQ does not explicitly support this syntax in the DSL API, but the PostgreSQL specific equivalent from INSERT .. ON CONFLICT .. EXCLUDED can be emulated in MySQL as well, so you can just use the DSL.excluded() pseudo table:

// 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")
      .values(4, "Shakespeare")
      .onDuplicateKeyUpdate()
      .set(AUTHOR.LAST_NAME, excluded(AUTHOR.LAST_NAME))
      .execute();

Dialect support

This example using jOOQ:

insertInto(AUTHOR, AUTHOR.ID, AUTHOR.LAST_NAME).values(3, "X").onDuplicateKeyUpdate().set(AUTHOR.LAST_NAME, excluded(AUTHOR.LAST_NAME))

Translates to the following dialect specific expressions:

Aurora MySQL, MariaDB, MemSQL

INSERT INTO AUTHOR (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)
ON DUPLICATE KEY UPDATE
  AUTHOR.LAST_NAME = VALUES(LAST_NAME)

Aurora Postgres, CockroachDB, DuckDB, Postgres, YugabyteDB

INSERT INTO AUTHOR (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)
ON CONFLICT (ID)
DO UPDATE
SET
  LAST_NAME = excluded.LAST_NAME

DB2

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM SYSIBM.DUAL
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Derby

MERGE INTO AUTHOR
USING SYSIBM.SYSDUMMY1
ON AUTHOR.ID = 3
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)

Exasol

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Firebird

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM RDB$DATABASE
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

H2

MERGE INTO AUTHOR
USING (
  SELECT
    3 ID,
    'X' LAST_NAME
) t
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Hana

MERGE INTO AUTHOR
USING (
  (
    SELECT
      3 ID,
      'X' LAST_NAME
    FROM SYS.DUMMY
  )
) t
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

HSQLDB

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM (VALUES (1)) AS dual (dual)
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

MySQL

INSERT INTO AUTHOR (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)
AS t
ON DUPLICATE KEY UPDATE
  AUTHOR.LAST_NAME = t.LAST_NAME

Oracle

MERGE INTO AUTHOR
USING (
  (
    SELECT
      3 ID,
      'X' LAST_NAME
  )
) t
ON (AUTHOR.ID = t.ID)
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Snowflake

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

SQLite

INSERT INTO AUTHOR (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)
ON CONFLICT 
DO UPDATE
SET
  LAST_NAME = excluded.LAST_NAME

SQLServer

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
);

Sybase

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM SYS.DUMMY
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Teradata

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM (
    SELECT 1 AS "dual"
  ) AS "dual"
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

ASE, Access, BigQuery, ClickHouse, Informix, Redshift, SQLDataWarehouse, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.4.7. INSERT .. ON DUPLICATE KEY IGNORE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

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:

// 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();

If the underlying database doesn't have any way to "ignore" failing INSERT statements, (e.g. MySQL via INSERT IGNORE), jOOQ can emulate the statement using a MERGE statement, or using INSERT .. SELECT WHERE NOT EXISTS.

The MySQL INSERT IGNORE statement ignores more constraint violations than just duplicate keys, so the emulation isn't exactly equivalent, see #5211

Dialect support

This example using jOOQ:

insertInto(AUTHOR, AUTHOR.ID, AUTHOR.LAST_NAME).values(3, "X").onDuplicateKeyIgnore()

Translates to the following dialect specific expressions:

Access

INSERT INTO AUTHOR (ID, LAST_NAME)
SELECT t.v0, t.v1
FROM (
  SELECT
    3 v0,
    'X' v1
  FROM (
    SELECT count(*) dual
    FROM MSysResources
  ) AS dual
  WHERE NOT EXISTS (
    SELECT 1 one
    FROM AUTHOR
    WHERE AUTHOR.ID = 3
  )
) t

ASE, ClickHouse, Redshift, SQLDataWarehouse, Trino, Vertica

INSERT INTO AUTHOR (ID, LAST_NAME)
SELECT t.v0, t.v1
FROM (
  SELECT
    3 v0,
    'X' v1
  WHERE NOT EXISTS (
    SELECT 1 one
    FROM AUTHOR
    WHERE AUTHOR.ID = 3
  )
) t

Aurora MySQL, MariaDB, MySQL

INSERT IGNORE INTO AUTHOR (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)

Aurora Postgres, CockroachDB, DuckDB, Postgres, SQLite, YugabyteDB

INSERT INTO AUTHOR (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)
ON CONFLICT
DO NOTHING

BigQuery

INSERT INTO AUTHOR (ID, LAST_NAME)
SELECT t.v0, t.v1
FROM (
  SELECT
    3 v0,
    'X' v1
  FROM UNNEST([STRUCT(1 AS dual)]) AS dual
  WHERE NOT EXISTS (
    SELECT 1 one
    FROM AUTHOR
    WHERE AUTHOR.ID = 3
  )
) t

DB2

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM SYSIBM.DUAL
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Derby

MERGE INTO AUTHOR
USING SYSIBM.SYSDUMMY1
ON AUTHOR.ID = 3
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)

Exasol, MemSQL

INSERT INTO AUTHOR (ID, LAST_NAME)
SELECT t.v0, t.v1
FROM (
  SELECT
    3 v0,
    'X' v1
  FROM DUAL
  WHERE NOT EXISTS (
    SELECT 1 one
    FROM AUTHOR
    WHERE AUTHOR.ID = 3
  )
) t

Firebird

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM RDB$DATABASE
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

H2

MERGE INTO AUTHOR
USING (
  SELECT
    3 ID,
    'X' LAST_NAME
) t
ON AUTHOR.ID = t.ID
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Hana

MERGE INTO AUTHOR
USING (
  (
    SELECT
      3 ID,
      'X' LAST_NAME
    FROM SYS.DUMMY
  )
) t
ON AUTHOR.ID = t.ID
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

HSQLDB

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM (VALUES (1)) AS dual (dual)
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Informix

INSERT INTO AUTHOR (ID, LAST_NAME)
SELECT t.v0, t.v1
FROM (
  SELECT
    3 v0,
    'X' v1
  FROM (
    SELECT 1 AS dual
    FROM systables
    WHERE (tabid = 1)
  ) AS dual
  WHERE NOT EXISTS (
    SELECT 1 one
    FROM AUTHOR
    WHERE AUTHOR.ID = 3
  )
) t

Oracle

MERGE INTO AUTHOR
USING (
  (
    SELECT
      3 ID,
      'X' LAST_NAME
  )
) t
ON (AUTHOR.ID = t.ID)
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Snowflake

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

SQLServer

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
);

Sybase

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM SYS.DUMMY
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Teradata

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM (
    SELECT 1 AS "dual"
  ) AS "dual"
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.4.8. INSERT .. ON CONFLICT .. EXCLUDED

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A special use-case for INSERT .. ON CONFLICT is an UPSERT statement where all (or most of) the values to be updated are the same values that would have been inserted if there wasn't a conflict - with the exception of primary keys. In PostgreSQL, this is done using the EXCLUDED pseudo table. The keyword should be read as "rows that have been excluded from the INSERT part of the statement."

// 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")
      .onConflict(AUTHOR.ID)
      .doUpdate()
      .set(AUTHOR.LAST_NAME, excluded(AUTHOR.LAST_NAME))
      .execute();

Dialect support

This example using jOOQ:

insertInto(AUTHOR, AUTHOR.ID, AUTHOR.LAST_NAME).values(3, "X").onConflict(AUTHOR.ID).doUpdate().set(AUTHOR.LAST_NAME, excluded(AUTHOR.LAST_NAME))

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DuckDB, Postgres, SQLite, YugabyteDB

INSERT INTO AUTHOR (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)
ON CONFLICT (ID)
DO UPDATE
SET
  LAST_NAME = excluded.LAST_NAME

DB2

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM SYSIBM.DUAL
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Derby

MERGE INTO AUTHOR
USING SYSIBM.SYSDUMMY1
ON AUTHOR.ID = 3
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)

Exasol

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Firebird

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM RDB$DATABASE
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

H2

MERGE INTO AUTHOR
USING (
  SELECT
    3 ID,
    'X' LAST_NAME
) t
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Hana

MERGE INTO AUTHOR
USING (
  (
    SELECT
      3 ID,
      'X' LAST_NAME
    FROM SYS.DUMMY
  )
) t
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

HSQLDB

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM (VALUES (1)) AS dual (dual)
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Oracle

MERGE INTO AUTHOR
USING (
  (
    SELECT
      3 ID,
      'X' LAST_NAME
  )
) t
ON (AUTHOR.ID = t.ID)
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

SQLServer

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
);

Sybase

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM SYS.DUMMY
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Teradata

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM (
    SELECT 1 AS "dual"
  ) AS "dual"
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  LAST_NAME = t.LAST_NAME
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, Informix, MariaDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, Snowflake, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.4.9. INSERT .. ON CONFLICT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The PostgreSQL database offers an alternative syntax to MySQL's vendor specific INSERT .. ON DUPLICATE KEY syntax, which allows for specifying an explicit (reference by constraint name) or implicit (reference by column list) unique constraint for conflict resolution.

// 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")
      .onConflict(AUTHOR.ID)
      .doUpdate()
      .set(AUTHOR.LAST_NAME, "Koontz")
      .execute();

Dialect support

This example using jOOQ:

insertInto(AUTHOR, AUTHOR.ID, AUTHOR.LAST_NAME).values(3, "X").onConflict(AUTHOR.ID).doUpdate().set(AUTHOR.LAST_NAME, "X")

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DuckDB, Postgres, SQLite, YugabyteDB

INSERT INTO AUTHOR (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)
ON CONFLICT (ID)
DO UPDATE
SET
  LAST_NAME = 'X'

DB2

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM SYSIBM.DUAL
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Derby

MERGE INTO AUTHOR
USING SYSIBM.SYSDUMMY1
ON AUTHOR.ID = 3
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  3, 
  'X'
)

Exasol

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Firebird

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM RDB$DATABASE
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

H2

MERGE INTO AUTHOR
USING (
  SELECT
    3 ID,
    'X' LAST_NAME
) t
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Hana

MERGE INTO AUTHOR
USING (
  (
    SELECT
      3 ID,
      'X' LAST_NAME
    FROM SYS.DUMMY
  )
) t
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

HSQLDB

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM (VALUES (1)) AS dual (dual)
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Oracle

MERGE INTO AUTHOR
USING (
  (
    SELECT
      3 ID,
      'X' LAST_NAME
  )
) t
ON (AUTHOR.ID = t.ID)
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

SQLServer

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
);

Sybase

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM SYS.DUMMY
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  AUTHOR.LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

Teradata

MERGE INTO AUTHOR
USING (
  SELECT 3, 'X'
  FROM (
    SELECT 1 AS "dual"
  ) AS "dual"
) t (ID, LAST_NAME)
ON AUTHOR.ID = t.ID
WHEN MATCHED THEN UPDATE SET
  LAST_NAME = 'X'
WHEN NOT MATCHED THEN INSERT (ID, LAST_NAME)
VALUES (
  t.ID, 
  t.LAST_NAME
)

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, Informix, MariaDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, Snowflake, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.4.10. INSERT .. RETURNING

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

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")
      .returningResult(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
      .returningResult(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 returningResult() clause.

Dialect support

This example using jOOQ:

insertInto(AUTHOR, AUTHOR.LAST_NAME).values("Doe").returningResult(AUTHOR.ID)

Translates to the following dialect specific expressions:

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, Derby, Exasol, HSQLDB, Hana, Informix, MemSQL, MySQL, Oracle, Redshift, Snowflake, Sybase, Teradata, Trino, Vertica

INSERT INTO AUTHOR (LAST_NAME)
VALUES ('Doe')

Aurora Postgres, CockroachDB, DuckDB, Firebird, Postgres, SQLite, YugabyteDB

INSERT INTO AUTHOR (LAST_NAME)
VALUES ('Doe')
RETURNING AUTHOR.ID

DB2, H2

SELECT ID
FROM FINAL TABLE (
  INSERT INTO AUTHOR (LAST_NAME)
  VALUES ('Doe')
) AUTHOR

MariaDB

INSERT INTO AUTHOR (LAST_NAME)
VALUES ('Doe')
RETURNING ID

SQLDataWarehouse, SQLServer

INSERT INTO AUTHOR (LAST_NAME)
OUTPUT inserted.ID
VALUES ('Doe')
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.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 might be implemented in the 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();

The following subsections discuss the various subclauses of the DELETE statement.

 

3.5.5.1. UPDATE .. SET

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The SET clause allows for setting new values on updated records in a table.

Dialect support

This example using jOOQ:

update(BOOK).set(BOOK.TITLE, "New Title")

Translates to the following dialect specific expressions:

ASE, Access, Aurora MySQL, DB2, Derby, Exasol, Firebird, H2, HSQLDB, Informix, MariaDB, MemSQL, MySQL, Oracle, SQLDataWarehouse, SQLServer, Sybase, Trino

UPDATE BOOK
SET
  BOOK.TITLE = 'New Title'

Aurora Postgres, CockroachDB, DuckDB, Postgres, Redshift, SQLite, Snowflake, Teradata, Vertica, YugabyteDB

UPDATE BOOK
SET
  TITLE = 'New Title'

BigQuery

UPDATE BOOK
SET
  BOOK.TITLE = 'New Title'
WHERE TRUE

ClickHouse

UPDATE BOOK
SET
  TITLE = 'New Title'
WHERE TRUE

Hana

UPDATE BOOK
FROM BOOK
SET
  BOOK.TITLE = 'New Title'
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.5.2. UPDATE .. SET ROW

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The SET clause allows for setting ROW value expressions on updated records in a table.

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 correlated subqueries in the SET clause, in case of which multiple columns can be updated with a single subquery, instead of only 1. See also UPDATE .. FROM for an alternative syntax for this scenario.

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.

Dialect support

This example using jOOQ:

update(BOOK).set(row(BOOK.TITLE, BOOK.LANGUAGE_ID), row("New Title", 1))

Translates to the following dialect specific expressions:

ASE, Access, Aurora MySQL, Derby, Exasol, Firebird, Informix, MariaDB, MemSQL, MySQL, SQLDataWarehouse, SQLServer, Sybase

UPDATE BOOK
SET
  BOOK.TITLE = 'New Title',
  BOOK.LANGUAGE_ID = 1

Aurora Postgres, CockroachDB, DB2, H2, HSQLDB, Trino

UPDATE BOOK
SET
  (TITLE, LANGUAGE_ID) = ('New Title', 1)

BigQuery

UPDATE BOOK
SET
  BOOK.TITLE = 'New Title',
  BOOK.LANGUAGE_ID = 1
WHERE TRUE

ClickHouse

UPDATE BOOK
SET
  TITLE = 'New Title',
  LANGUAGE_ID = 1
WHERE TRUE

DuckDB, Redshift, SQLite, Snowflake, Teradata, Vertica

UPDATE BOOK
SET
  TITLE = 'New Title',
  LANGUAGE_ID = 1

Hana

UPDATE BOOK
FROM BOOK
SET
  (TITLE, LANGUAGE_ID) = ('New Title', 1)

Oracle

UPDATE BOOK
SET
  (TITLE, LANGUAGE_ID) = (
    SELECT 'New Title', 1
  )

Postgres, YugabyteDB

UPDATE BOOK
SET
  (TITLE, LANGUAGE_ID) = ROW ('New Title', 1)
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.5.3. UPDATE .. FROM

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Some databases, including for example 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. For example, 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, for convenience (see the section about scalar subqueries for more details):

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();

Dialect support

This example using jOOQ:

update(BOOK_TO_BOOK_STORE).set(BOOK_TO_BOOK_STORE.STOCK, 0).from(BOOK).where(BOOK_TO_BOOK_STORE.BOOK_ID.eq(BOOK.ID)).and(BOOK.AUTHOR_ID.eq(1))

Translates to the following dialect specific expressions:

ASE, BigQuery, Oracle, SQLServer, Sybase

UPDATE BOOK_TO_BOOK_STORE
SET
  BOOK_TO_BOOK_STORE.STOCK = 0
FROM BOOK
WHERE (
  BOOK_TO_BOOK_STORE.BOOK_ID = BOOK.ID
  AND BOOK.AUTHOR_ID = 1
)

Aurora MySQL, MariaDB, MemSQL, MySQL, Trino

UPDATE BOOK_TO_BOOK_STORE
  CROSS JOIN BOOK
SET
  BOOK_TO_BOOK_STORE.STOCK = 0
WHERE (
  BOOK_TO_BOOK_STORE.BOOK_ID = BOOK.ID
  AND BOOK.AUTHOR_ID = 1
)

Aurora Postgres, CockroachDB, DuckDB, Postgres, SQLite, Snowflake, YugabyteDB

UPDATE BOOK_TO_BOOK_STORE
SET
  STOCK = 0
FROM BOOK
WHERE (
  BOOK_TO_BOOK_STORE.BOOK_ID = BOOK.ID
  AND BOOK.AUTHOR_ID = 1
)

ClickHouse

UPDATE BOOK_TO_BOOK_STORE
SET
  STOCK = 0
WHERE (NAME, BOOK_ID) IN (
  SELECT BOOK_TO_BOOK_STORE.NAME, BOOK_TO_BOOK_STORE.BOOK_ID
  FROM BOOK_TO_BOOK_STORE, BOOK
  WHERE (
    BOOK_TO_BOOK_STORE.BOOK_ID = BOOK.ID
    AND BOOK.AUTHOR_ID = 1
  )
)

DB2, Derby, Exasol, Firebird, H2, HSQLDB

MERGE INTO BOOK_TO_BOOK_STORE
USING BOOK
ON (
  BOOK_TO_BOOK_STORE.BOOK_ID = BOOK.ID
  AND BOOK.AUTHOR_ID = 1
)
WHEN MATCHED THEN UPDATE SET
  BOOK_TO_BOOK_STORE.STOCK = 0

Hana

UPDATE BOOK_TO_BOOK_STORE
FROM BOOK_TO_BOOK_STORE, BOOK
SET
  BOOK_TO_BOOK_STORE.STOCK = 0
WHERE (
  BOOK_TO_BOOK_STORE.BOOK_ID = BOOK.ID
  AND BOOK.AUTHOR_ID = 1
)

Redshift

UPDATE BOOK_TO_BOOK_STORE
SET
  STOCK = 0
WHERE (BOOK_TO_BOOK_STORE.NAME, BOOK_TO_BOOK_STORE.BOOK_ID) IN (
  SELECT BOOK_TO_BOOK_STORE.NAME, BOOK_TO_BOOK_STORE.BOOK_ID
  FROM BOOK_TO_BOOK_STORE, BOOK
  WHERE (
    BOOK_TO_BOOK_STORE.BOOK_ID = BOOK.ID
    AND BOOK.AUTHOR_ID = 1
  )
)

SQLDataWarehouse

UPDATE BOOK_TO_BOOK_STORE
SET
  BOOK_TO_BOOK_STORE.STOCK = 0
WHERE EXISTS (
  SELECT alias_1.v0, alias_1.v1
  FROM (
    SELECT
      BOOK_TO_BOOK_STORE.NAME v0,
      BOOK_TO_BOOK_STORE.BOOK_ID v1
    FROM BOOK_TO_BOOK_STORE, BOOK
    WHERE (
      BOOK_TO_BOOK_STORE.BOOK_ID = BOOK.ID
      AND BOOK.AUTHOR_ID = 1
    )
  ) alias_1
  WHERE (
    BOOK_TO_BOOK_STORE.NAME = alias_1.v0
    AND BOOK_TO_BOOK_STORE.BOOK_ID = alias_1.v1
  )
)

Teradata, Vertica

MERGE INTO BOOK_TO_BOOK_STORE
USING BOOK
ON (
  BOOK_TO_BOOK_STORE.BOOK_ID = BOOK.ID
  AND BOOK.AUTHOR_ID = 1
)
WHEN MATCHED THEN UPDATE SET
  STOCK = 0

Access, Informix

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.5.4. UPDATE .. WHERE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The WHERE clause allows for adding a conditional expressions to the UPDATE statement, which restricts the rows to be updated.

Dialect support

This example using jOOQ:

update(BOOK).set(BOOK.TITLE, "New Title").where(BOOK.ID.eq(1))

Translates to the following dialect specific expressions:

ASE, Access, Aurora MySQL, BigQuery, DB2, Derby, Exasol, Firebird, H2, HSQLDB, Informix, MariaDB, MemSQL, MySQL, Oracle, SQLDataWarehouse, SQLServer, Sybase, Trino

UPDATE BOOK
SET
  BOOK.TITLE = 'New Title'
WHERE BOOK.ID = 1

Aurora Postgres, CockroachDB, DuckDB, Postgres, Redshift, SQLite, Snowflake, Teradata, Vertica, YugabyteDB

UPDATE BOOK
SET
  TITLE = 'New Title'
WHERE BOOK.ID = 1

ClickHouse

UPDATE BOOK
SET
  TITLE = 'New Title'
WHERE ID = 1

Hana

UPDATE BOOK
FROM BOOK
SET
  BOOK.TITLE = 'New Title'
WHERE BOOK.ID = 1
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.5.5. UPDATE .. ORDER BY .. LIMIT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The ORDER BY and LIMIT clauses allow for updating only a subset of the data in a table, based on their ordering. This can be useful to implement queue semantics, e.g. to update only the top row, and possibly return it in one go.

Dialect support

This example using jOOQ:

update(BOOK).set(BOOK.TITLE, "New Title").orderBy(BOOK.ID.asc()).limit(1)

Translates to the following dialect specific expressions:

Aurora MySQL, DB2, MariaDB, MySQL, Trino

UPDATE BOOK
SET
  BOOK.TITLE = 'New Title'
ORDER BY BOOK.ID ASC
LIMIT 1

Aurora Postgres, DuckDB, Redshift, SQLite, Snowflake, Vertica, YugabyteDB

UPDATE BOOK
SET
  TITLE = 'New Title'
WHERE BOOK.ID IN (
  SELECT BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID ASC
  LIMIT 1
)

BigQuery, Exasol, HSQLDB

UPDATE BOOK
SET
  BOOK.TITLE = 'New Title'
WHERE BOOK.ID IN (
  SELECT BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID ASC
  LIMIT 1
)

ClickHouse

UPDATE BOOK
SET
  TITLE = 'New Title'
WHERE ID IN (
  SELECT BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID ASC
  LIMIT 1
)

CockroachDB

UPDATE BOOK
SET
  TITLE = 'New Title'
ORDER BY BOOK.ID ASC
LIMIT 1

Derby, H2, Oracle

UPDATE BOOK
SET
  BOOK.TITLE = 'New Title'
WHERE BOOK.ID IN (
  SELECT BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID ASC
  FETCH NEXT 1 ROWS ONLY
)

Firebird

UPDATE BOOK
SET
  BOOK.TITLE = 'New Title'
ORDER BY BOOK.ID ASC
ROWS 1

Hana

UPDATE BOOK
FROM BOOK
SET
  BOOK.TITLE = 'New Title'
WHERE BOOK.ID IN (
  SELECT BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID ASC
  LIMIT 1
)

Informix

UPDATE BOOK
SET
  BOOK.TITLE = 'New Title'
WHERE BOOK.ID IN (
  SELECT *
  FROM (
    SELECT FIRST 1 BOOK.ID
    FROM BOOK
    ORDER BY BOOK.ID ASC
  ) x
)

MemSQL

UPDATE BOOK
SET
  BOOK.TITLE = 'New Title'
WHERE BOOK.ID IN (
  SELECT *
  FROM (
    SELECT BOOK.ID
    FROM BOOK
    ORDER BY BOOK.ID ASC
    LIMIT 1
  ) t
)

Postgres

UPDATE BOOK
SET
  TITLE = 'New Title'
WHERE BOOK.ID IN (
  SELECT BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID ASC
  FETCH NEXT 1 ROWS ONLY
)

SQLDataWarehouse, SQLServer, Sybase

UPDATE BOOK
SET
  BOOK.TITLE = 'New Title'
WHERE BOOK.ID IN (
  SELECT TOP 1 BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID ASC
)

Teradata

UPDATE BOOK
SET
  TITLE = 'New Title'
WHERE BOOK.ID IN (
  SELECT *
  FROM (
    SELECT TOP 1 BOOK.ID
    FROM BOOK
    ORDER BY BOOK.ID ASC
  ) x
)

ASE, Access

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.5.6. UPDATE .. RETURNING

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Various dialect support a RETURNING clause or something similar 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))
                  .returningResult(BOOK.TITLE)
                  .fetchOne().getValue(BOOK.TITLE);

Dialect support

This example using jOOQ:

update(BOOK).set(BOOK.TITLE, "New Title").returningResult(BOOK.ID)

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, Postgres, SQLite, YugabyteDB

UPDATE BOOK
SET
  TITLE = 'New Title'
RETURNING BOOK.ID

DB2, H2

SELECT ID
FROM FINAL TABLE (
  UPDATE BOOK
  SET
    BOOK.TITLE = 'New Title'
) BOOK

Firebird

UPDATE BOOK
SET
  BOOK.TITLE = 'New Title'
RETURNING BOOK.ID

MariaDB

INSERT INTO BOOK (
  ID,
  AUTHOR_ID,
  TITLE,
  PUBLISHED_IN,
  LANGUAGE_ID
)
SELECT
  BOOK.ID,
  BOOK.AUTHOR_ID,
  BOOK.TITLE,
  BOOK.PUBLISHED_IN,
  BOOK.LANGUAGE_ID
FROM BOOK
ON DUPLICATE KEY UPDATE
  BOOK.TITLE = 'New Title'
RETURNING ID

Oracle

DECLARE
  o0 DBMS_SQL.NUMBER_TABLE;
  c0 sys_refcursor;
BEGIN
  UPDATE BOOK
  SET
    BOOK.TITLE = 'New Title'
  RETURNING BOOK.ID
  BULK COLLECT INTO o0;
  ? := SQL%ROWCOUNT;
  OPEN c0 FOR SELECT * FROM TABLE(o0);
  ? := c0;
END;

SQLServer

UPDATE BOOK
SET
  BOOK.TITLE = 'New Title'
OUTPUT inserted.ID

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, Derby, DuckDB, Exasol, HSQLDB, Hana, Informix, MemSQL, MySQL, Redshift, SQLDataWarehouse, Snowflake, Sybase, Teradata, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.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 might be implemented in the future. An example delete query is given here:

DELETE AUTHOR
 WHERE ID = 100;
 
create.delete(AUTHOR)
      .where(AUTHOR.ID.eq(100))
      .execute();

The following subsections discuss the various subclauses of the DELETE statement.

 

3.5.6.1. DELETE .. USING

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The USING clause allows for a JOIN between the table from which to delete rows with other tables.

Dialect support

This example using jOOQ:

deleteFrom(BOOK).using(AUTHOR).where(BOOK.AUTHOR_ID.eq(AUTHOR.ID)).and(AUTHOR.LAST_NAME.eq("Poe"))

Translates to the following dialect specific expressions:

ASE, Oracle, SQLServer, Sybase

DELETE FROM BOOK
FROM AUTHOR
WHERE (
  BOOK.AUTHOR_ID = AUTHOR.ID
  AND AUTHOR.LAST_NAME = 'Poe'
)

Aurora MySQL, MariaDB, MySQL

DELETE FROM BOOK
USING BOOK, AUTHOR
WHERE (
  BOOK.AUTHOR_ID = AUTHOR.ID
  AND AUTHOR.LAST_NAME = 'Poe'
)

Aurora Postgres, CockroachDB, DuckDB, Postgres, Redshift, Snowflake, YugabyteDB

DELETE FROM BOOK
USING AUTHOR
WHERE (
  BOOK.AUTHOR_ID = AUTHOR.ID
  AND AUTHOR.LAST_NAME = 'Poe'
)

BigQuery, Hana, SQLDataWarehouse, SQLite, Trino, Vertica

DELETE FROM BOOK
WHERE BOOK.ID IN (
  SELECT BOOK.ID
  FROM BOOK, AUTHOR
  WHERE (
    BOOK.AUTHOR_ID = AUTHOR.ID
    AND AUTHOR.LAST_NAME = 'Poe'
  )
)

ClickHouse

DELETE FROM BOOK
WHERE ID IN (
  SELECT BOOK.ID
  FROM BOOK, AUTHOR
  WHERE (
    BOOK.AUTHOR_ID = AUTHOR.ID
    AND AUTHOR.LAST_NAME = 'Poe'
  )
)

DB2, Derby, Firebird, H2, HSQLDB

MERGE INTO BOOK
USING AUTHOR
ON (
  BOOK.AUTHOR_ID = AUTHOR.ID
  AND AUTHOR.LAST_NAME = 'Poe'
)
WHEN MATCHED THEN DELETE

Exasol

MERGE INTO BOOK
USING AUTHOR
ON (
  BOOK.AUTHOR_ID = AUTHOR.ID
  AND AUTHOR.LAST_NAME = 'Poe'
)
WHEN MATCHED THEN UPDATE SET
  BOOK.ID = BOOK.ID DELETE

MemSQL

DELETE BOOK
FROM BOOK, AUTHOR
WHERE (
  BOOK.AUTHOR_ID = AUTHOR.ID
  AND AUTHOR.LAST_NAME = 'Poe'
)

Teradata

DELETE BOOK
FROM AUTHOR
WHERE (
  BOOK.AUTHOR_ID = AUTHOR.ID
  AND AUTHOR.LAST_NAME = 'Poe'
)

Access, Informix

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.6.2. DELETE .. WHERE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The WHERE clause allows for adding a conditional expressions to the DELETE statement, which restricts the rows to be deleted.

Dialect support

This example using jOOQ:

deleteFrom(BOOK).where(BOOK.ID.in(1, 2, 3))

Translates to the following dialect specific expressions:

ASE, Access, Aurora MySQL, Aurora Postgres, BigQuery, CockroachDB, DB2, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Postgres, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

DELETE FROM BOOK
WHERE BOOK.ID IN (
  1, 2, 3
)

ClickHouse

DELETE FROM BOOK
WHERE ID IN (
  1, 2, 3
)
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.6.3. DELETE .. ORDER BY .. LIMIT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The ORDER BY and LIMIT clauses allow for deleting only a subset of the data in a table, based on their ordering. This can be useful to implement queue semantics, e.g. to delete only the top row, and possibly return it in one go.

Dialect support

This example using jOOQ:

deleteFrom(BOOK).orderBy(BOOK.ID.asc()).limit(1)

Translates to the following dialect specific expressions:

Aurora MySQL, CockroachDB, DB2, MariaDB, MySQL, Trino

DELETE FROM BOOK
ORDER BY BOOK.ID ASC
LIMIT 1

Aurora Postgres, BigQuery, DuckDB, Exasol, HSQLDB, Hana, Redshift, SQLite, Snowflake, Vertica, YugabyteDB

DELETE FROM BOOK
WHERE BOOK.ID IN (
  SELECT BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID ASC
  LIMIT 1
)

ClickHouse

DELETE FROM BOOK
WHERE ID IN (
  SELECT BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID ASC
  LIMIT 1
)

Derby, H2, Oracle, Postgres

DELETE FROM BOOK
WHERE BOOK.ID IN (
  SELECT BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID ASC
  FETCH NEXT 1 ROWS ONLY
)

Firebird

DELETE FROM BOOK
ORDER BY BOOK.ID ASC
ROWS 1

Informix

DELETE FROM BOOK
WHERE BOOK.ID IN (
  SELECT *
  FROM (
    SELECT FIRST 1 BOOK.ID
    FROM BOOK
    ORDER BY BOOK.ID ASC
  ) x
)

MemSQL

DELETE FROM BOOK
WHERE BOOK.ID IN (
  SELECT *
  FROM (
    SELECT BOOK.ID
    FROM BOOK
    ORDER BY BOOK.ID ASC
    LIMIT 1
  ) t
)

SQLDataWarehouse, SQLServer, Sybase

DELETE FROM BOOK
WHERE BOOK.ID IN (
  SELECT TOP 1 BOOK.ID
  FROM BOOK
  ORDER BY BOOK.ID ASC
)

Teradata

DELETE FROM BOOK
WHERE BOOK.ID IN (
  SELECT *
  FROM (
    SELECT TOP 1 BOOK.ID
    FROM BOOK
    ORDER BY BOOK.ID ASC
  ) x
)

ASE, Access

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.6.4. UPDATE .. RETURNING

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The RETURNING clause allows for returning expressions based on the deleted rows.

Dialect support

This example using jOOQ:

deleteFrom(BOOK).where(BOOK.ID.eq(1)).returningResult(BOOK.TITLE)

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, Firebird, Postgres, SQLite, YugabyteDB

DELETE FROM BOOK
WHERE BOOK.ID = 1
RETURNING BOOK.TITLE

DB2, H2

SELECT TITLE
FROM OLD TABLE (
  DELETE FROM BOOK
  WHERE BOOK.ID = 1
) BOOK

MariaDB

DELETE FROM BOOK
WHERE BOOK.ID = 1
RETURNING TITLE

Oracle

DECLARE
  o0 DBMS_SQL.VARCHAR2_TABLE;
  c0 sys_refcursor;
BEGIN
  DELETE FROM BOOK
  WHERE BOOK.ID = 1
  RETURNING BOOK.TITLE
  BULK COLLECT INTO o0;
  ? := SQL%ROWCOUNT;
  OPEN c0 FOR SELECT * FROM TABLE(o0);
  ? := c0;
END;

SQLServer

DELETE FROM BOOK
OUTPUT deleted.TITLE
WHERE BOOK.ID = 1

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, Derby, DuckDB, Exasol, HSQLDB, Hana, Informix, MemSQL, MySQL, Redshift, SQLDataWarehouse, Snowflake, Sybase, Teradata, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.5.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.18.20, 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();

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.

 

3.6. 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, or MODIFY 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 of ALTER [object type] .. RENAME TO ..
  • Some syntax may not be supported, or not be supported consistently, such as the various IF EXISTS and IF 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.

Commercial support for emulations

A lot of DDL queries come with syntax that requires emulation using anonymous blocks. While basic anonymous blocks are supported in the jOOQ Open Source Edition as well, more sophisticated blocks and other procedural logic is a commercial only feature.

 

3.6.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.

 

3.6.1.1. ALTER DATABASE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The only property of a database that can be changed, currently, is its name.

 

3.6.1.1.1. ALTER DATABASE .. RENAME

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Like most object types, databases can be renamed:

// Renaming the database
create.alterDatabase("old_database").renameTo("new_database").execute();

Dialect support

This example using jOOQ:

alterDatabase("d").renameTo("e")

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, Postgres, YugabyteDB

ALTER DATABASE d RENAME TO e

ClickHouse

RENAME DATABASE d TO e

ASE, Access, Aurora MySQL, BigQuery, DB2, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.1.2. ALTER DATABASE IF EXISTS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A popular subclause of DDL statements that jOOQ can usually emulate, is the IF EXISTS clause:

// Renaming the database
create.alterDatabaseIfExists("old_database").renameTo("new_database").execute();

Dialect support

This example using jOOQ:

alterDatabaseIfExists("d").renameTo("e")

Translates to the following dialect specific expressions:

Aurora Postgres, Postgres, YugabyteDB

DO $$
BEGIN
  ALTER DATABASE d RENAME TO e;
EXCEPTION
  WHEN SQLSTATE '3D000' THEN NULL;
END $$

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.2. ALTER DOMAIN

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The ALTER DOMAIN statement allows for altering DOMAIN types for use as data types in table columns.

 

3.6.1.2.1. ALTER DOMAIN .. RENAME

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Like most object types, domains can be renamed:

// Renaming the domain
create.alterDomain("old_domain").renameTo("new_domain").execute();

Dialect support

This example using jOOQ:

alterDomain("d").renameTo("e")

Translates to the following dialect specific expressions:

Aurora Postgres, Postgres, YugabyteDB

ALTER DOMAIN d RENAME TO e

Firebird

ALTER DOMAIN d TO e

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, H2, HSQLDB, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.2.2. ALTER DOMAIN .. SET DEFAULT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A domain's DEFAULT value can be set to a new value using this syntax:

// Setting a default to the domain
create.alterDomain("d").setDefault(1).execute();

Dialect support

This example using jOOQ:

alterDomain("d").setDefault(1)

Translates to the following dialect specific expressions:

Aurora Postgres, Firebird, HSQLDB, Postgres, YugabyteDB

ALTER DOMAIN d SET DEFAULT 1

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, H2, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.2.3. ALTER DOMAIN .. DROP DEFAULT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A domain's DEFAULT value can be set to a new value using this syntax:

// Dropping a default from the domain
create.alterDomain("d").dropDefault().execute();

Dialect support

This example using jOOQ:

alterDomain("d").dropDefault()

Translates to the following dialect specific expressions:

Aurora Postgres, Firebird, HSQLDB, Postgres, YugabyteDB

ALTER DOMAIN d DROP DEFAULT

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, H2, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.2.4. ALTER DOMAIN .. SET NOT NULL

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A domain's NOT NULL constraint can be set using this syntax:

// Setting a NOT NULL constraint to the domain
create.alterDomain("d").setNotNull().execute();

Dialect support

This example using jOOQ:

alterDomain("d").setNotNull()

Translates to the following dialect specific expressions:

Aurora Postgres, Postgres

ALTER DOMAIN d SET NOT NULL

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.2.5. ALTER DOMAIN .. DROP NOT NULL

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A domain's NOT NULL constraint can be dropped using this syntax:

// Dropping the NOT NULL constraint from the domain
create.alterDomain("d").dropNotNull().execute();

Dialect support

This example using jOOQ:

alterDomain("d").dropNotNull()

Translates to the following dialect specific expressions:

Aurora Postgres, Postgres

ALTER DOMAIN d DROP NOT NULL

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.2.6. ALTER DOMAIN .. ADD CONSTRAINT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A domain's CHECK constraints can be added using this syntax:

// Add CHECK constraints to a domain
create.alterDomain("d").add(constraint("c").check(value(INTEGER).gt(0))).execute();

Dialect support

This example using jOOQ:

alterDomain("d").add(constraint("c").check(value(INTEGER).gt(0)))

Translates to the following dialect specific expressions:

Aurora Postgres, HSQLDB, Postgres

ALTER DOMAIN d ADD CONSTRAINT c CHECK (value > 0)

Firebird

ALTER DOMAIN d ADD CHECK (value > 0)

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, H2, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.2.7. ALTER DOMAIN .. RENAME CONSTRAINT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Like most object types, constraints belonging to domains can be renamed:

// Renaming the domain
create.alterDomain("d").renameConstraint("old_constraint").to("new_constraint").execute();

Dialect support

This example using jOOQ:

alterDomain("d").renameConstraint("c").to("e")

Translates to the following dialect specific expressions:

Aurora Postgres, Postgres

ALTER DOMAIN d RENAME CONSTRAINT c TO e

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.2.8. ALTER DOMAIN .. RENAME CONSTRAINT IF EXISTS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A popular subclause of DDL statements that jOOQ can usually emulate, is the IF EXISTS clause:

// Renaming the domain
create.alterDomain("d").renameConstraintIfExists("old_constraint").to("new_constraint").execute();

Dialect support

This example using jOOQ:

alterDomain("d").renameConstraintIfExists("c").to("e")

Translates to the following dialect specific expressions:

Aurora Postgres, Postgres

DO $$
BEGIN
  ALTER DOMAIN d RENAME CONSTRAINT c TO e;
EXCEPTION
  WHEN SQLSTATE '42704' THEN NULL;
END $$

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.2.9. ALTER DOMAIN .. DROP CONSTRAINT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A domain's CHECK constraint can be dropped using this syntax:

// Drop CHECK constraints from a domain
create.alterDomain("d").dropConstraint("c").execute();

Dialect support

This example using jOOQ:

alterDomain("d").dropConstraint("c")

Translates to the following dialect specific expressions:

Aurora Postgres, HSQLDB, Postgres

ALTER DOMAIN d DROP CONSTRAINT c

Firebird

ALTER DOMAIN d DROP CONSTRAINT

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, H2, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.2.10. ALTER DOMAIN .. DROP CONSTRAINT IF EXISTS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A popular subclause of DDL statements that jOOQ can usually emulate, is the IF EXISTS clause:

// Drop CHECK constraints from a domain
create.alterDomain("d").dropConstraintIfExists("c").execute();

Dialect support

This example using jOOQ:

alterDomain("d").dropConstraintIfExists("c")

Translates to the following dialect specific expressions:

Aurora Postgres, Postgres

ALTER DOMAIN d DROP CONSTRAINT IF EXISTS c

Firebird

EXECUTE BLOCK
AS
BEGIN
  EXECUTE STATEMENT '
    ALTER DOMAIN d DROP CONSTRAINT
  ';
  WHEN sqlcode -607 DO
    BEGIN END
END

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, H2, HSQLDB, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.2.11. ALTER DOMAIN IF EXISTS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A popular subclause of DDL statements that jOOQ can usually emulate, is the IF EXISTS clause:

// Renaming the database
create.alterDomainIfExists("old_domain").renameTo("new_domain").execute();

Dialect support

This example using jOOQ:

alterDomainIfExists("d").renameTo("e")

Translates to the following dialect specific expressions:

Aurora Postgres, Postgres, YugabyteDB

ALTER DOMAIN IF EXISTS d RENAME TO e

Firebird

ALTER DOMAIN IF EXISTS d TO e

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, H2, HSQLDB, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.3. ALTER INDEX

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The only property of an index that can be changed, currently, is its name.

 

3.6.1.3.1. ALTER INDEX .. RENAME

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Like most object types, indexes can be renamed:

// Renaming the index
create.alterIndex("old_index").renameTo("new_index").execute();

Dialect support

This example using jOOQ:

alterIndex("i").renameTo("j")

Translates to the following dialect specific expressions:

ASE

EXEC sp_rename 'i', j, 'index'

Aurora MySQL, MariaDB, MemSQL, MySQL

ALTER TABLE  RENAME INDEX i TO j

Aurora Postgres, CockroachDB, H2, HSQLDB, Oracle, Postgres

ALTER INDEX i RENAME TO j

DB2, Derby, Hana

RENAME INDEX i TO j

SQLDataWarehouse, SQLServer

EXEC sp_rename 'i', j, 'INDEX'

Access, BigQuery, ClickHouse, DuckDB, Exasol, Firebird, Informix, Redshift, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.3.2. ALTER INDEX IF EXISTS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A popular subclause of DDL statements that jOOQ can usually emulate, is the IF EXISTS clause:

// Renaming the index
create.alterIndexIfExists("old_index").renameTo("new_index").execute();

Dialect support

This example using jOOQ:

alterIndexIfExists("i").renameTo("j")

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, H2, Postgres

ALTER INDEX IF EXISTS i RENAME TO j

DB2

BEGIN
  DECLARE CONTINUE HANDLER FOR SQLSTATE '42704' BEGIN END;
  EXECUTE IMMEDIATE '
    RENAME INDEX i TO j
  ';
END

Hana

DO BEGIN
  DECLARE EXIT HANDLER FOR SQL_ERROR_CODE 259 BEGIN END;
  EXECUTE IMMEDIATE '
    RENAME INDEX i TO j
  ';
END;

MariaDB

BEGIN NOT ATOMIC
  DECLARE CONTINUE HANDLER FOR SQLSTATE '42000' BEGIN END;
  DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' BEGIN END;
  ALTER TABLE  RENAME INDEX i TO j;
END

MySQL

CREATE PROCEDURE block_1727885030483_7678903()
MODIFIES SQL DATA
BEGIN
  DECLARE CONTINUE HANDLER FOR SQLSTATE '42000' BEGIN END;
  ALTER TABLE  RENAME INDEX i TO j;
END;
CALL block_1727885030483_7678903();
DROP PROCEDURE block_1727885030483_7678903;

Oracle

BEGIN
  EXECUTE IMMEDIATE '
    ALTER INDEX i RENAME TO j
  ';
EXCEPTION
  WHEN others THEN
    IF sqlerrm LIKE 'ORA-01418%' THEN NULL;
    ELSE RAISE;
    END IF;
END;

SQLDataWarehouse

BEGIN TRY
  EXEC sp_rename 'i', j, 'INDEX'
END TRY
BEGIN CATCH
  IF error_number() != 2714 BEGIN
    DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
    DECLARE @ErrorSeverity INT = ERROR_SEVERITY();
    DECLARE @ErrorState INT = ERROR_STATE();
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
  END;
END CATCH

SQLServer

BEGIN TRY
  EXEC sp_rename 'i', j, 'INDEX'
END TRY
BEGIN CATCH
  IF error_number() != 2714 THROW;
END CATCH

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, Derby, DuckDB, Exasol, Firebird, HSQLDB, Informix, MemSQL, Redshift, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.4. ALTER SCHEMA

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The only property of a schema that can be changed, currently, is its name.

 

3.6.1.4.1. ALTER SCHEMA .. RENAME

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Like most object types, schemas can be renamed:

// Renaming the schema
create.alterSchema("old_schema").renameTo("new_schema").execute();

Dialect support

This example using jOOQ:

alterSchema("s").renameTo("t")

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, H2, HSQLDB, Postgres, Redshift, Snowflake, Vertica

ALTER SCHEMA s RENAME TO t

ClickHouse

RENAME DATABASE s TO t

Hana

RENAME SCHEMA s TO t

ASE, Access, Aurora MySQL, BigQuery, DB2, Derby, DuckDB, Exasol, Firebird, Informix, MariaDB, MemSQL, MySQL, Oracle, SQLDataWarehouse, SQLServer, SQLite, Sybase, Teradata, Trino, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.4.2. ALTER SCHEMA IF EXISTS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A popular subclause of DDL statements that jOOQ can usually emulate, is the IF EXISTS clause:

// Renaming the schema
create.alterSchemaIfExists("old_schema").renameTo("new_schema").execute();

Dialect support

This example using jOOQ:

alterSchemaIfExists("s").renameTo("t")

Translates to the following dialect specific expressions:

H2, Snowflake

ALTER SCHEMA IF EXISTS s RENAME TO t

Hana

DO BEGIN
  DECLARE EXIT HANDLER FOR SQL_ERROR_CODE 362 BEGIN END;
  EXECUTE IMMEDIATE '
    RENAME SCHEMA s TO t
  ';
END;

ASE, Access, Aurora MySQL, Aurora Postgres, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, Firebird, HSQLDB, Informix, MariaDB, MemSQL, MySQL, Oracle, Postgres, Redshift, SQLDataWarehouse, SQLServer, SQLite, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.5. ALTER SEQUENCE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Most sequence properties can be altered after a sequence has been created.

 

3.6.1.5.1. ALTER SEQUENCE .. RENAME

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Like most object types, sequences can be renamed:

// Renaming the sequence
create.alterSequence("old_sequence").renameTo("new_sequence").execute();

Dialect support

This example using jOOQ:

alterSequence("s").renameTo("t")

Translates to the following dialect specific expressions:

Aurora Postgres, HSQLDB, Postgres, Snowflake, Vertica

ALTER SEQUENCE s RENAME TO t

MariaDB

ALTER TABLE s RENAME TO t

Oracle

RENAME s TO t

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, Firebird, H2, Hana, Informix, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLServer, SQLite, Sybase, Teradata, Trino, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.5.2. ALTER SEQUENCE .. CACHE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Some RDBMS support the CACHE clause to specify a cache size for a sequence, allowing to pre-allocate sequence values in the current session or in some other scope.

// Caching sequence values
create.alterSequence("s").cache(200).execute();

// Turning off the caching of sequence values
create.alterSequence("s").noCache().execute();

Dialect support

This example using jOOQ:

alterSequence("s").cache(200)

Translates to the following dialect specific expressions:

Aurora Postgres, DB2, H2, Hana, Informix, MariaDB, Oracle, Postgres, SQLServer, Sybase, Vertica, YugabyteDB

ALTER SEQUENCE s CACHE 200

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, Derby, DuckDB, Exasol, Firebird, HSQLDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Snowflake, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.5.3. ALTER SEQUENCE .. CYCLE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The CYCLE clause can be used to enable cycling of a sequence to its MINVALUE as soon as it reaches its MAXVALUE.

// Enable cycling
create.alterSequence("s").cycle().execute();

// Disable cycling
create.alterSequence("s").noCycle().execute();

Dialect support

This example using jOOQ:

alterSequence("s").cycle()

Translates to the following dialect specific expressions:

Aurora Postgres, DB2, H2, HSQLDB, Hana, Informix, MariaDB, Oracle, Postgres, SQLServer, Sybase, Vertica, YugabyteDB

ALTER SEQUENCE s CYCLE

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, Derby, DuckDB, Exasol, Firebird, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Snowflake, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.5.4. ALTER SEQUENCE .. MINVALUE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The MINVALUE of a sequence is the minimum value a sequence will cycle to, as soon as it reaches its MAXVALUE.

// Specify a MINVALUE
create.alterSequence("s").minvalue(1).execute();

// Disable the MINVALUE
create.alterSequence("s").noMinvalue().execute();

Dialect support

This example using jOOQ:

alterSequence("s").minvalue(1)

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DB2, H2, HSQLDB, Hana, Informix, MariaDB, Oracle, Postgres, SQLServer, Sybase, Vertica, YugabyteDB

ALTER SEQUENCE s MINVALUE 1

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, Derby, DuckDB, Exasol, Firebird, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Snowflake, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.5.5. ALTER SEQUENCE .. t reaches its MAXVALUE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The t reaches its MAXVALUE of a sequence is the maximum value a sequence will reach before it cycles to its MINVALUE.

// Specify a MAXVALUE
create.alterSequence("s").maxvalue(1).execute();

// Disable the MAXVALUE
create.alterSequence("s").noMaxvalue().execute();

Dialect support

This example using jOOQ:

alterSequence("s").maxvalue(1)

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DB2, H2, HSQLDB, Hana, Informix, MariaDB, Oracle, Postgres, SQLServer, Sybase, Vertica, YugabyteDB

ALTER SEQUENCE s MAXVALUE 1

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, Derby, DuckDB, Exasol, Firebird, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Snowflake, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.5.6. ALTER SEQUENCE .. INCREMENT BY

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

by default, sequences increment by 1, but this can be changed with the INCREMENT BY clause:

// Specify the sequence's increment value
create.alterSequence("s").incrementBy(10).execute();

Dialect support

This example using jOOQ:

alterSequence("s").incrementBy(10)

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DB2, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, Oracle, Postgres, SQLServer, Snowflake, Sybase, Vertica, YugabyteDB

ALTER SEQUENCE s INCREMENT BY 10

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, Derby, DuckDB, Exasol, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.5.7. ALTER SEQUENCE .. START WITH

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A sequence can be manually restarted using the RESTART clause, in case of which it will be restarted at the START WITH value, which can be altered as follows:

// Specify the sequence's start value
create.alterSequence("s").startWith(10).execute();

Dialect support

This example using jOOQ:

alterSequence("s").startWith(10)

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, MariaDB, Postgres, YugabyteDB

ALTER SEQUENCE s START WITH 10

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, DB2, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Informix, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.5.8. ALTER SEQUENCE .. RESTART

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A sequence can be manually restarted using the RESTART clause, in case of which it will be restarted at the START WITH value.

// Restart the sequence
create.alterSequence("s").restart().execute();

Alternatively, an explicit value can be provide to restart the sequence with:

// Restart the sequence
create.alterSequence("s").restartWith(10).execute();

Dialect support

This example using jOOQ:

alterSequence("s").restart()

Translates to the following dialect specific expressions:

Aurora Postgres, DB2, HSQLDB, MariaDB, Postgres, SQLServer, YugabyteDB

ALTER SEQUENCE s RESTART

Hana, Informix, Vertica

ALTER SEQUENCE s RESTART WITH 1

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, Derby, DuckDB, Exasol, Firebird, H2, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLite, Snowflake, Sybase, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.5.9. ALTER SEQUENCE IF EXISTS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A popular subclause of DDL statements that jOOQ can usually emulate, is the IF EXISTS clause:

// Renaming the schema
create.alterSequenceIfExists("old_sequence").renameTo("new_sequence").execute();

Dialect support

This example using jOOQ:

alterSequenceIfExists("s").renameTo("t")

Translates to the following dialect specific expressions:

Aurora Postgres, Postgres, Snowflake

ALTER SEQUENCE IF EXISTS s RENAME TO t

MariaDB

ALTER TABLE s RENAME TO t

Oracle

BEGIN
  EXECUTE IMMEDIATE '
    RENAME s TO t
  ';
EXCEPTION
  WHEN others THEN
    IF sqlerrm LIKE 'ORA-04043%' THEN NULL;
    ELSE RAISE;
    END IF;
END;

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Informix, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLServer, SQLite, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6. 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.

 

3.6.1.6.1. ALTER TABLE .. ADD COLUMN

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Most dialects support altering a table to add a single column per statement.

// Adding a single column to a table. These statements are equivalent
create.alterTable("table").add("column", INTEGER).execute();
create.alterTable("table").addColumn("column", INTEGER).execute();

Dialect support

This example using jOOQ:

alterTable("t").add("c", INTEGER)

Translates to the following dialect specific expressions:

Access, DB2, Firebird, Informix, Teradata

ALTER TABLE t ADD c integer

ASE, Sybase

ALTER TABLE t ADD c int NULL

Aurora MySQL, Aurora Postgres, Derby, DuckDB, Exasol, H2, HSQLDB, MariaDB, MemSQL, MySQL, Postgres, Redshift, SQLDataWarehouse, SQLServer, SQLite, Vertica, YugabyteDB

ALTER TABLE t ADD c int

BigQuery

ALTER TABLE t ADD COLUMN c int64

ClickHouse

ALTER TABLE t ADD COLUMN c Nullable(integer)

CockroachDB

ALTER TABLE t ADD c int4

Hana

ALTER TABLE t ADD (c integer)

Oracle, Snowflake

ALTER TABLE t ADD c number(10)

Trino

ALTER TABLE t ADD COLUMN c int
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.2. ALTER TABLE .. ADD COLUMN .. FIRST, BEFORE, AFTER

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Most RDBMS maintain both a logical and physical ordering of the columns in a table. While the physical ordering (how column values are stored in a disk block) are implementation details that are hidden from the SQL language, the logical ordering is relevant for numerous SQL features, including:

  • The behaviour of the SELECT * syntax (the * is expanded according to the logical ordering of columns in a table)
  • Client languages, such as PL/SQL, store data in record representations when using TABLE%ROWTYPE and similar syntax
  • Libraries like jOOQ will use this ordering for code generation of tables, records, and more
  • Client applications and SQL editors like Dbeaver will display results in the declared logical column order

All RDBMS will maintain the logical column ordering declared in the CREATE TABLE statement, and will append new columns to the end of a table when using the ALTER TABLE .. ADD COLUMN statement.

But only few RDBMS allow for modifying the logical column position when altering a table, after the table has been created. This is done with one of the following syntaxes:

// Adding a single column and specify its position
create.alterTable("table").add("column", INTEGER).after("other_column").execute();
create.alterTable("table").add("column", INTEGER).before("other_column").execute();
create.alterTable("table").add("column", INTEGER).first().execute();

AFTER

The AFTER clause will allow for specifying a column after which the new column will be added.

Dialect support

This example using jOOQ:

alterTable("t").add("c", INTEGER).after("other")

Translates to the following dialect specific expressions:

Aurora MySQL, H2, MariaDB, MemSQL, MySQL

ALTER TABLE t ADD c int AFTER other

ClickHouse

ALTER TABLE t ADD COLUMN c Nullable(integer) AFTER other

ASE, Access, Aurora Postgres, BigQuery, CockroachDB, DB2, Derby, DuckDB, Exasol, Firebird, HSQLDB, Hana, Informix, Oracle, Postgres, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website

BEFORE

The BEFORE clause will allow for specifying a column before which the new column will be added.

Dialect support

This example using jOOQ:

alterTable("t").add("c", INTEGER).before("other")

Translates to the following dialect specific expressions:

H2, HSQLDB

ALTER TABLE t ADD c int BEFORE other

ASE, Access, Aurora MySQL, Aurora Postgres, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, Firebird, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Postgres, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website

FIRST

The FIRST clause will allow for specifying that the new column will be added before all the other columns.

Dialect support

This example using jOOQ:

alterTable("t").add("c", INTEGER).first()

Translates to the following dialect specific expressions:

Aurora MySQL, H2, MariaDB, MemSQL, MySQL

ALTER TABLE t ADD c int FIRST

ClickHouse

ALTER TABLE t ADD COLUMN c Nullable(integer) FIRST

Firebird

EXECUTE BLOCK
AS
BEGIN
  EXECUTE STATEMENT '
    ALTER TABLE t ADD c integer
  ';
  EXECUTE STATEMENT '
    ALTER TABLE t ALTER c POSITION 1
  ';
END

ASE, Access, Aurora Postgres, BigQuery, CockroachDB, DB2, Derby, DuckDB, Exasol, HSQLDB, Hana, Informix, Oracle, Postgres, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.3. ALTER TABLE .. ADD COLUMNS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

If multiple columns should be added atomically, and to save server round trips, some RDBMS support adding multiple columns to a table using a single ALTER TABLE statement.

// Adding several columns to a table in one go
create.alterTable("table").add(field(name("column1"), INTEGER), field(name("column2"), INTEGER)).execute();

Dialect support

This example using jOOQ:

alterTable("t").add(field("c1", INTEGER), field("c2", INTEGER))

Translates to the following dialect specific expressions:

ASE

ALTER TABLE t ADD 
  c1 int NULL,
  c2 int NULL

Aurora MySQL, Aurora Postgres, MariaDB, MemSQL, MySQL, Postgres, YugabyteDB

ALTER TABLE t ADD c1 int,
ADD c2 int

BigQuery

ALTER TABLE t ADD COLUMN c1 int64,
ADD COLUMN c2 int64

ClickHouse

ALTER TABLE t ADD COLUMN c1 Nullable(integer),
ADD COLUMN c2 Nullable(integer)

CockroachDB

ALTER TABLE t ADD c1 int4,
ADD c2 int4

DB2

ALTER TABLE t ADD c1 integer
ADD c2 integer

Firebird, Teradata

ALTER TABLE t ADD c1 integer,
ADD c2 integer

H2

ALTER TABLE t ADD (
  c1 int,
  c2 int
)

Hana, Informix

ALTER TABLE t ADD (
  c1 integer,
  c2 integer
)

Oracle, Snowflake

ALTER TABLE t ADD (
  c1 number(10),
  c2 number(10)
)

SQLDataWarehouse, SQLServer

ALTER TABLE t ADD 
  c1 int,
  c2 int

Access, Derby, DuckDB, Exasol, HSQLDB, Redshift, SQLite, Sybase, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.4. ALTER TABLE .. ADD COLUMN IF NOT EXISTS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A popular subclause of DDL statements that jOOQ can usually emulate, is the IF NOT EXISTS clause:

Dialect support

This example using jOOQ:

alterTable("t").addIfNotExists("c", INTEGER)

Translates to the following dialect specific expressions:

Aurora Postgres, DuckDB, Exasol, H2, MariaDB, Postgres, YugabyteDB

ALTER TABLE t ADD IF NOT EXISTS c int

BigQuery

ALTER TABLE t ADD COLUMN IF NOT EXISTS c int64

ClickHouse

ALTER TABLE t ADD COLUMN IF NOT EXISTS c Nullable(integer)

CockroachDB

ALTER TABLE t ADD IF NOT EXISTS c int4

DB2

BEGIN
  DECLARE CONTINUE HANDLER FOR SQLSTATE '42711' BEGIN END;
  EXECUTE IMMEDIATE '
    ALTER TABLE t ADD c integer
  ';
END

Hana

DO BEGIN
  DECLARE EXIT HANDLER FOR SQL_ERROR_CODE 308 BEGIN END;
  EXECUTE IMMEDIATE '
    ALTER TABLE t ADD (c integer)
  ';
END;

Oracle

BEGIN
  EXECUTE IMMEDIATE '
    ALTER TABLE t ADD c number(10)
  ';
EXCEPTION
  WHEN others THEN
    IF sqlerrm LIKE 'ORA-01430%' THEN NULL;
    ELSE RAISE;
    END IF;
END;

SQLDataWarehouse

BEGIN TRY
  EXEC sp_executesql N'
    ALTER TABLE t ADD c int
  ';
END TRY
BEGIN CATCH
  IF error_number() != 2705 BEGIN
    DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
    DECLARE @ErrorSeverity INT = ERROR_SEVERITY();
    DECLARE @ErrorState INT = ERROR_STATE();
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
  END;
END CATCH

SQLServer

BEGIN TRY
  EXEC sp_executesql N'
    ALTER TABLE t ADD c int
  ';
END TRY
BEGIN CATCH
  IF error_number() != 2705 THROW;
END CATCH

Teradata

ALTER TABLE t ADD IF NOT EXISTS c integer

Trino

ALTER TABLE t ADD COLUMN IF NOT EXISTS c int

ASE, Access, Aurora MySQL, Derby, Firebird, HSQLDB, Informix, MemSQL, MySQL, Redshift, SQLite, Snowflake, Sybase, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.5. ALTER TABLE .. ADD PRIMARY KEY

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

All constraint types can be added with the ALTER TABLE statement. This includes PRIMARY KEY constraints:

// Adding an unnamed constraint to a table
create.alterTable("table").add(primaryKey("id")).execute();

// Adding a named constraint to a table
create.alterTable("table").add(constraint("pk").primaryKey("id")).execute();
It is recommended to always provide a name with a constraint to simplify schema management. See also this section of the manual

Dialect support

This example using jOOQ:

alterTable("t").add(constraint("pk").primaryKey("id"))

Translates to the following dialect specific expressions:

ASE, Access, Aurora MySQL, Aurora Postgres, CockroachDB, DB2, Derby, Exasol, Firebird, H2, HSQLDB, Hana, MariaDB, MemSQL, MySQL, Oracle, Postgres, Redshift, SQLServer, Snowflake, Sybase, Teradata, Vertica, YugabyteDB

ALTER TABLE t ADD CONSTRAINT pk PRIMARY KEY (id)

BigQuery

ALTER TABLE t ADD PRIMARY KEY (id) NOT ENFORCED

ClickHouse

ALTER TABLE t ADD PRIMARY KEY (id)

Informix

ALTER TABLE t ADD CONSTRAINT PRIMARY KEY (id) CONSTRAINT pk

SQLDataWarehouse

ALTER TABLE t ADD CONSTRAINT pk PRIMARY KEY NONCLUSTERED (id) NOT ENFORCED

DuckDB, SQLite, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.6. ALTER TABLE .. ADD UNIQUE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

All constraint types can be added with the ALTER TABLE statement. This includes UNIQUE constraints:

// Adding an unnamed constraint to a table
create.alterTable("table").add(unique("user_name")).execute();

// Adding a named constraint to a table
create.alterTable("table").add(constraint("uk").unique("user_name")).execute();
It is recommended to always provide a name with a constraint to simplify schema management. See also this section of the manual

Dialect support

This example using jOOQ:

alterTable("t").add(constraint("uk").unique("user_name"))

Translates to the following dialect specific expressions:

ASE, Access, Aurora MySQL, Aurora Postgres, BigQuery, ClickHouse, CockroachDB, DB2, Derby, Exasol, Firebird, H2, HSQLDB, Hana, MariaDB, MemSQL, MySQL, Oracle, Postgres, Redshift, SQLServer, Snowflake, Sybase, Teradata, Vertica, YugabyteDB

ALTER TABLE t ADD CONSTRAINT uk UNIQUE (user_name)

Informix

ALTER TABLE t ADD CONSTRAINT UNIQUE (user_name) CONSTRAINT uk

SQLDataWarehouse

ALTER TABLE t ADD CONSTRAINT uk UNIQUE (user_name) NOT ENFORCED

DuckDB, SQLite, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.7. ALTER TABLE .. ADD FOREIGN KEY

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

All constraint types can be added with the ALTER TABLE statement. This includes FOREIGN KEY constraints:

// Adding an unnamed constraint to a table
create.alterTable("table").add(foreignKey("author_id").references("author")).execute();

// Adding a named constraint to a table
create.alterTable("table").add(constraint("fk").foreignKey("author_id").references("author")).execute();
It is recommended to always provide a name with a constraint to simplify schema management. See also this section of the manual

Dialect support

This example using jOOQ:

alterTable("t").add(constraint("pk").foreignKey("author_id").references("author"))

Translates to the following dialect specific expressions:

ASE, Access, Aurora MySQL, Aurora Postgres, CockroachDB, DB2, Derby, Exasol, Firebird, H2, HSQLDB, Hana, MariaDB, MemSQL, MySQL, Oracle, Postgres, Redshift, SQLDataWarehouse, SQLServer, Snowflake, Sybase, Teradata, Vertica, YugabyteDB

ALTER TABLE t ADD CONSTRAINT pk FOREIGN KEY (author_id) REFERENCES author

BigQuery, ClickHouse

ALTER TABLE t ADD CONSTRAINT pk FOREIGN KEY (author_id) REFERENCES author NOT ENFORCED

Informix

ALTER TABLE t ADD CONSTRAINT FOREIGN KEY (author_id) REFERENCES author CONSTRAINT pk

DuckDB, SQLite, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.8. ALTER TABLE .. ADD CHECK

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

All constraint types can be added with the ALTER TABLE statement. This includes CHECK constraints:

// Adding an unnamed constraint to a table
create.alterTable("table").add(check(length(field(name("c"), INTEGER)).gt(0))).execute();

// Adding a named constraint to a table
create.alterTable("table").add(constraint("ck").check(length(field(name("c"), INTEGER)).gt(0))).execute();
It is recommended to always provide a name with a constraint to simplify schema management. See also this section of the manual

Dialect support

This example using jOOQ:

alterTable("t").add(constraint("ck").check(field("c", INTEGER).gt(0)))

Translates to the following dialect specific expressions:

ASE, Access, Aurora Postgres, ClickHouse, CockroachDB, DB2, Derby, Firebird, H2, HSQLDB, Hana, MariaDB, MySQL, Oracle, Postgres, SQLServer, Sybase, Teradata, Vertica, YugabyteDB

ALTER TABLE t ADD CONSTRAINT ck CHECK (c > 0)

Informix

ALTER TABLE t ADD CONSTRAINT CHECK (c > 0) CONSTRAINT ck

Aurora MySQL, BigQuery, DuckDB, Exasol, MemSQL, Redshift, SQLDataWarehouse, SQLite, Snowflake, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.9. ALTER TABLE .. RENAME

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Like most object types, tables can be renamed:

// Rename a table
create.alterTable("old_table").renameTo("new_table").execute();

Dialect support

This example using jOOQ:

alterTable("t").renameTo("u")

Translates to the following dialect specific expressions:

Access, Aurora MySQL, Aurora Postgres, BigQuery, CockroachDB, DuckDB, H2, HSQLDB, MariaDB, MemSQL, MySQL, Oracle, Postgres, Redshift, SQLite, Snowflake, Trino, Vertica, YugabyteDB

ALTER TABLE t RENAME TO u

ASE, SQLServer

EXEC sp_rename 't', u

ClickHouse, DB2, Derby, Exasol, Hana, Informix, Teradata

RENAME TABLE t TO u

SQLDataWarehouse

RENAME OBJECT t TO u

Sybase

ALTER TABLE t RENAME u

Firebird

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.10. ALTER TABLE .. COMMENT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

For convenience, jOOQ supports MySQL's COMMENT syntax also on ALTER TABLE, which corresponds to the more standard COMMENT ON TABLE statement

// Specify a new comment on a table
create.alterTable("table").comment("a comment describing the table").execute();

Dialect support

This example using jOOQ:

alterTable("t").comment("comment")

Translates to the following dialect specific expressions:

Aurora MySQL, MariaDB, MemSQL, MySQL

ALTER TABLE t COMMENT 'comment'

Aurora Postgres, CockroachDB, DB2, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Oracle, Postgres, Sybase, Teradata, Trino, Vertica, YugabyteDB

COMMENT ON TABLE t IS 'comment'

BigQuery

ALTER TABLE t SET OPTIONS (DESCRIPTION = 'comment')

ClickHouse

ALTER TABLE t MODIFY COMMENT 'comment'

Snowflake

ALTER TABLE t SET COMMENT = 'comment'

SQLServer

BEGIN TRY
  DECLARE @u varchar(max) = schema_name();
  EXEC sp_addextendedproperty 'MS_Description', 'comment', 'schema', @u, 'table', 't', DEFAULT, DEFAULT
END TRY
BEGIN CATCH
  EXEC sp_updateextendedproperty 'MS_Description', 'comment', 'schema', @u, 'table', 't', DEFAULT, DEFAULT
END CATCH

ASE, Access, Derby, Informix, Redshift, SQLDataWarehouse, SQLite

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.11. ALTER TABLE .. ALTER COLUMN .. SET DEFAULT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A column DEFAULT value can be added to a column using the ALTER TABLE's SET DEFAULT clause on a column:

// Specify a new default value for a column
create.alterTable("table").alter("column").setDefault(1).execute();

Dialect support

This example using jOOQ:

alterTable("t").alter("c").setDefault(1)

Translates to the following dialect specific expressions:

Access, Aurora Postgres, CockroachDB, DB2, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Postgres, Snowflake, Sybase, YugabyteDB

ALTER TABLE t ALTER c SET DEFAULT 1

ASE

ALTER TABLE t REPLACE c DEFAULT 1

Aurora MySQL, BigQuery, MariaDB, MySQL, Vertica

ALTER TABLE t ALTER COLUMN c SET DEFAULT 1

ClickHouse

ALTER TABLE t MODIFY COLUMN c DEFAULT 1

MemSQL, Oracle

ALTER TABLE t MODIFY c DEFAULT 1

SQLServer

DECLARE @constraint NVARCHAR(max);
DECLARE @command NVARCHAR(max);

SELECT @constraint = name
FROM sys.default_constraints
WHERE parent_object_id = object_id('t')
AND parent_column_id = columnproperty(object_id('t'), 'c', 'ColumnId');

IF @constraint IS NOT NULL
BEGIN
  SET @command = 'ALTER TABLE ' + 't' + ' DROP CONSTRAINT ' + @constraint
  EXECUTE sp_executesql @command

  SET @command = 'ALTER TABLE ' + 't' + ' ADD CONSTRAINT ' + @constraint + ' DEFAULT ' + '1' + ' FOR ' + 'c'
  EXECUTE sp_executesql @command
END
ELSE
BEGIN
  SET @command = 'ALTER TABLE ' + 't' + ' ADD DEFAULT ' + '1' + ' FOR ' + 'c'
  EXECUTE sp_executesql @command
END

Hana, Informix, Redshift, SQLDataWarehouse, SQLite, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.12. ALTER TABLE .. ALTER COLUMN .. DROP DEFAULT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

An existing column DEFAULT value can be removed from a column using the ALTER TABLE's DROP DEFAULT clause on a column:

// Drop the default from a column
create.alterTable("table").alter("column").dropDefault().execute();

Dialect support

This example using jOOQ:

alterTable("t").alter("c").dropDefault()

Translates to the following dialect specific expressions:

Access, Aurora Postgres, CockroachDB, DB2, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Postgres, Snowflake, Sybase, YugabyteDB

ALTER TABLE t ALTER c DROP DEFAULT

ASE

ALTER TABLE t REPLACE c DEFAULT NULL

Aurora MySQL, MariaDB, MySQL

ALTER TABLE t ALTER COLUMN c SET DEFAULT NULL

BigQuery, Vertica

ALTER TABLE t ALTER COLUMN c DROP DEFAULT

ClickHouse

ALTER TABLE t MODIFY COLUMN c REMOVE DEFAULT

MemSQL, Oracle

ALTER TABLE t MODIFY c DEFAULT NULL

SQLServer

DECLARE @constraint NVARCHAR(max);
DECLARE @command NVARCHAR(max);

SELECT @constraint = name
FROM sys.default_constraints
WHERE parent_object_id = object_id('t')
AND parent_column_id = columnproperty(object_id('t'), 'c', 'ColumnId');

IF @constraint IS NOT NULL
BEGIN
  SET @command = 'ALTER TABLE ' + 't' + ' DROP CONSTRAINT ' + @constraint
  EXECUTE sp_executesql @command

  SET @command = 'ALTER TABLE ' + 't' + ' ADD CONSTRAINT ' + @constraint + ' DEFAULT ' + NULL + ' FOR ' + 'c'
  EXECUTE sp_executesql @command
END
ELSE
BEGIN
  SET @command = 'ALTER TABLE ' + 't' + ' ADD DEFAULT ' + NULL + ' FOR ' + 'c'
  EXECUTE sp_executesql @command
END

Hana, Informix, Redshift, SQLDataWarehouse, SQLite, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.13. ALTER TABLE .. ALTER COLUMN .. SET NOT NULL

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A column can be set to NOT NULL using the ALTER TABLE's NOT NULL clause on a column:

// Specify the not null constraint on a column
create.alterTable("table").alter("column").setNotNull().execute();

The existing data in the column must not contain any nulls or an error will be raised.

Dialect support

This example using jOOQ:

alterTable("t").alter("c").setNotNull()

Translates to the following dialect specific expressions:

ASE, Oracle

ALTER TABLE t MODIFY c NOT NULL

Aurora Postgres, CockroachDB, DB2, Derby, DuckDB, Firebird, H2, HSQLDB, Postgres, Snowflake, YugabyteDB

ALTER TABLE t ALTER c SET NOT NULL

Exasol

ALTER TABLE t MODIFY c SET NOT NULL

Vertica

ALTER TABLE t ALTER COLUMN c SET NOT NULL

Access, Aurora MySQL, BigQuery, ClickHouse, Hana, Informix, MariaDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLServer, SQLite, Sybase, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.14. ALTER TABLE .. ALTER COLUMN .. DROP NOT NULL

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

An existing column's NOT NULL constraint can be dropped using the ALTER TABLE's NOT NULL clause on a column:

// Drop the not null constraint on a column
create.alterTable("table").alter("column").dropNotNull().execute();

Dialect support

This example using jOOQ:

alterTable("t").alter("c").dropNotNull()

Translates to the following dialect specific expressions:

ASE, Oracle

ALTER TABLE t MODIFY c NULL

Aurora Postgres, CockroachDB, DB2, Derby, DuckDB, Firebird, H2, HSQLDB, Postgres, Snowflake, YugabyteDB

ALTER TABLE t ALTER c DROP NOT NULL

Exasol

ALTER TABLE t MODIFY c DROP NOT NULL

Vertica

ALTER TABLE t ALTER COLUMN c DROP NOT NULL

Access, Aurora MySQL, BigQuery, ClickHouse, Hana, Informix, MariaDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLServer, SQLite, Sybase, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.15. ALTER TABLE .. ALTER COLUMN .. SET TYPE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The type of a column can be changed using the ALTER TABLE's SET TYPE clause on a column:

// Set a new data type on the column
create.alterTable("table").alter("column").set(VARCHAR(50)).execute();

Whether this operation is supported for any given pair of existing/new types is vendor specific. If the existing data doesn't conform to the new type, an error is raised by the database.

Dialect support

This example using jOOQ:

alterTable("t").alter("c").set(VARCHAR(50))

Translates to the following dialect specific expressions:

Access

ALTER TABLE t ALTER c text(50)

ASE, Exasol, MemSQL

ALTER TABLE t MODIFY c varchar(50)

Aurora MySQL, MariaDB, MySQL

ALTER TABLE t CHANGE COLUMN c c varchar(50)

Aurora Postgres, Firebird, Postgres, YugabyteDB

ALTER TABLE t ALTER c TYPE varchar(50)

ClickHouse

ALTER TABLE t MODIFY COLUMN c Nullable(String(50))

CockroachDB

ALTER TABLE t ALTER c TYPE string(50)

DB2, Derby, DuckDB

ALTER TABLE t ALTER c SET DATA TYPE varchar(50)

H2, HSQLDB, Snowflake, Sybase

ALTER TABLE t ALTER c varchar(50)

Hana

ALTER TABLE t ALTER(c varchar(50))

Informix

ALTER TABLE t MODIFY c lvarchar(50)

Oracle

ALTER TABLE t MODIFY c varchar2(50)

SQLDataWarehouse, SQLServer

ALTER TABLE t ALTER COLUMN c varchar(50)

Trino

ALTER TABLE t ALTER COLUMN c SET DATA TYPE varchar(50)

BigQuery, Redshift, SQLite, Teradata, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.16. ALTER TABLE .. ALTER CONSTRAINT .. ENFORCED

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A constraint can be enabled using the ALTER TABLE's ENFORCED clause on a constraint:

// Set the enforced flag on a constraint
create.alterTable("table").alterConstraint("uk").enforced().execute();

If enforcement of the constraint fails, an error is raised by the database.

Dialect support

This example using jOOQ:

alterTable("t").alterConstraint("c").enforced()

Translates to the following dialect specific expressions:

DB2, MySQL

ALTER TABLE t ALTER CONSTRAINT c ENFORCED

Oracle

ALTER TABLE t MODIFY CONSTRAINT c ENABLE

ASE, Access, Aurora MySQL, Aurora Postgres, BigQuery, ClickHouse, CockroachDB, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, MemSQL, Postgres, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.17. ALTER TABLE .. ALTER CONSTRAINT .. NOT ENFORCED

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A constraint can be disabled using the ALTER TABLE's NOT ENFORCED clause on a constraint:

// Remove the enforced flag from a constraint
create.alterTable("table").alterConstraint("uk").notEnforced().execute();

Dialect support

This example using jOOQ:

alterTable("t").alterConstraint("c").notEnforced()

Translates to the following dialect specific expressions:

DB2, MySQL

ALTER TABLE t ALTER CONSTRAINT c NOT ENFORCED

Oracle

ALTER TABLE t MODIFY CONSTRAINT c DISABLE

ASE, Access, Aurora MySQL, Aurora Postgres, BigQuery, ClickHouse, CockroachDB, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, MemSQL, Postgres, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.18. ALTER TABLE .. RENAME COLUMN

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Like most object types, columns can be renamed:

// Rename a column
create.alterTable("table").renameColumn("old_column").to("new_column").execute();

Dialect support

This example using jOOQ:

alterTable("t").renameColumn("c").to("d")

Translates to the following dialect specific expressions:

Access, Aurora MySQL, Aurora Postgres, BigQuery, ClickHouse, CockroachDB, DB2, DuckDB, Exasol, MariaDB, MySQL, Oracle, Postgres, Redshift, SQLDataWarehouse, SQLite, Snowflake, Trino, Vertica, YugabyteDB

ALTER TABLE t RENAME COLUMN c TO d

ASE

EXEC sp_rename 't.c', d, 'column'

Derby, Hana, Informix

RENAME COLUMN c TO d

Firebird

ALTER TABLE t ALTER COLUMN c TO d

H2, HSQLDB

ALTER TABLE t ALTER COLUMN c RENAME TO d

MemSQL

ALTER TABLE t CHANGE c d

SQLServer

EXEC sp_rename 't.c', d, 'COLUMN'

Sybase, Teradata

ALTER TABLE t RENAME c TO d
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.19. ALTER TABLE .. RENAME CONSTRAINT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Like most object types, constraints can be renamed:

// Rename a constraint
create.alterTable("table").renameConstraint("old_constraint").to("new_constraint").execute();

Dialect support

This example using jOOQ:

alterTable("t").renameConstraint("c").to("d")

Translates to the following dialect specific expressions:

Access, Aurora MySQL, Aurora Postgres, CockroachDB, DB2, H2, Informix, Oracle, Postgres, SQLDataWarehouse, SQLServer, Snowflake, Sybase, Vertica, YugabyteDB

ALTER TABLE t RENAME CONSTRAINT c TO d

ASE

EXEC sp_rename 't.c', d, 'index'

HSQLDB

ALTER CONSTRAINT c RENAME TO d

Teradata

ALTER TABLE t RENAME c TO d

BigQuery, ClickHouse, Derby, DuckDB, Exasol, Firebird, Hana, MariaDB, MemSQL, MySQL, Redshift, SQLite, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.20. ALTER TABLE .. RENAME INDEX

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Like most object types, indexes can be renamed. As some RDBMS treat indexes as objects contained in tables, just like constraints, this syntax is offered also as an alternative to the more popular variant of the ALTER INDEX .. RENAME statement:

// Rename a index (as a convenience for the ALTER INDEX statement)
create.alterTable("table").renameIndex("old_index").to("new_index").execute();

Dialect support

This example using jOOQ:

alterTable("t").renameIndex("i").to("j")

Translates to the following dialect specific expressions:

ASE

EXEC sp_rename 't.i', j, 'index'

Aurora MySQL, MariaDB, MemSQL, MySQL

ALTER TABLE t RENAME INDEX i TO j

Aurora Postgres, CockroachDB, H2, HSQLDB, Oracle, Postgres

ALTER INDEX i RENAME TO j

DB2, Derby, Hana

RENAME INDEX i TO j

SQLDataWarehouse, SQLServer

EXEC sp_rename 't.i', j, 'INDEX'

Access, BigQuery, ClickHouse, DuckDB, Exasol, Firebird, Informix, Redshift, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.21. ALTER TABLE .. DROP COLUMN

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A single column can be dropped from a table using the ALTER TABLE's DROP COLUMN clause:

// Drop a single column
create.alterTable("table").drop("column").execute();

Dialect support

This example using jOOQ:

alterTable("t").drop("c")

Translates to the following dialect specific expressions:

Access, Aurora MySQL, Aurora Postgres, CockroachDB, DB2, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Informix, MariaDB, MemSQL, MySQL, Postgres, SQLite, Snowflake, Sybase, Teradata, Vertica, YugabyteDB

ALTER TABLE t DROP c

ASE

ALTER TABLE t DROP c WITH NO DATACOPY

BigQuery, ClickHouse, Oracle, SQLDataWarehouse, SQLServer, Trino

ALTER TABLE t DROP COLUMN c

Hana

ALTER TABLE t DROP (c)

Redshift

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.22. ALTER TABLE .. DROP COLUMN RESTRICT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

An optional RESTRICT clause can be added to a DROP COLUMN clause to hint at the operation failing when there are objects depending on the column.

// Add a RESTRICT clause when dropping columns
create.alterTable("table").drop("column").restrict().execute();

In most RDBMS, RESTRICT semantics is the default when dropping columns, although implementations do not agree on what dependent objects are included in the RESTRICT check.

Dialect support

This example using jOOQ:

alterTable("t").drop("c").restrict()

Translates to the following dialect specific expressions:

CockroachDB, DB2, Derby, DuckDB, HSQLDB, Postgres, YugabyteDB

ALTER TABLE t DROP c RESTRICT

Oracle

ALTER TABLE t DROP COLUMN c

ASE, Access, Aurora MySQL, Aurora Postgres, BigQuery, ClickHouse, Exasol, Firebird, H2, Hana, Informix, MariaDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.23. ALTER TABLE .. DROP COLUMN CASCADE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

An optional CASCADE clause can be added to a DROP COLUMN clause to cascade the DROP operatoin to dependent objects.

// Add a CASCADE clause when dropping columns
create.alterTable("table").drop("column").cascade().execute();

Note: Implementations do not agree on what dependent objects are included in the CASCADE operation.

Dialect support

This example using jOOQ:

alterTable("t").drop("c").cascade()

Translates to the following dialect specific expressions:

CockroachDB, DB2, Derby, HSQLDB, Postgres, YugabyteDB

ALTER TABLE t DROP c CASCADE

Oracle

ALTER TABLE t DROP COLUMN c CASCADE CONSTRAINTS

ASE, Access, Aurora MySQL, Aurora Postgres, BigQuery, ClickHouse, DuckDB, Exasol, Firebird, H2, Hana, Informix, MariaDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.24. ALTER TABLE .. DROP COLUMNS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Multiple columns can be dropped from a table using the ALTER TABLE's DROP COLUMNS clause:

// Drop several columns in one go
create.alterTable("table").drop("column1", "column2").execute();

Dialect support

This example using jOOQ:

alterTable("t").drop("c1", "c2")

Translates to the following dialect specific expressions:

ASE

ALTER TABLE t DROP c1, c2 WITH NO DATACOPY

Aurora MySQL, Aurora Postgres, CockroachDB, Firebird, MariaDB, MemSQL, MySQL, Postgres, Teradata, YugabyteDB

ALTER TABLE t DROP c1, DROP c2

BigQuery, ClickHouse

ALTER TABLE t DROP COLUMN c1, DROP COLUMN c2

DB2

ALTER TABLE t DROP c1 DROP c2

H2, Snowflake

ALTER TABLE t DROP c1, c2

Hana, Informix, Oracle

ALTER TABLE t DROP (c1, c2)

SQLDataWarehouse, SQLServer

ALTER TABLE t DROP COLUMN c1, c2

Access, Derby, DuckDB, Exasol, HSQLDB, Redshift, SQLite, Sybase, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.25. ALTER TABLE .. ADD COLUMN IF NOT EXISTS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A popular subclause of DDL statements that jOOQ can usually emulate, is the IF EXISTS clause:

Dialect support

This example using jOOQ:

alterTable("t").dropIfExists("c")

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DuckDB, Exasol, H2, MariaDB, Postgres, Teradata, YugabyteDB

ALTER TABLE t DROP IF EXISTS c

BigQuery, ClickHouse, Trino

ALTER TABLE t DROP COLUMN IF EXISTS c

DB2

BEGIN
  DECLARE CONTINUE HANDLER FOR SQLSTATE '42703' BEGIN END;
  DECLARE CONTINUE HANDLER FOR SQLSTATE '42704' BEGIN END;
  EXECUTE IMMEDIATE '
    ALTER TABLE t DROP c
  ';
END

Hana

DO BEGIN
  DECLARE EXIT HANDLER FOR SQL_ERROR_CODE 257 BEGIN END;
  DECLARE EXIT HANDLER FOR SQL_ERROR_CODE 260 BEGIN END;
  DECLARE EXIT HANDLER FOR SQL_ERROR_CODE 397 BEGIN END;
  EXECUTE IMMEDIATE '
    ALTER TABLE t DROP (c)
  ';
END;

Oracle

BEGIN
  EXECUTE IMMEDIATE '
    ALTER TABLE t DROP COLUMN c
  ';
EXCEPTION
  WHEN others THEN
    IF sqlerrm LIKE 'ORA-00904%' THEN NULL;
    ELSIF sqlerrm LIKE 'ORA-02443%' THEN NULL;
    ELSE RAISE;
    END IF;
END;

SQLDataWarehouse

BEGIN TRY
  EXEC sp_executesql N'
    ALTER TABLE t DROP COLUMN c
  ';
END TRY
BEGIN CATCH
  IF error_number() != 4924 and error_number() != 3728 and error_number() != 3727 BEGIN
    DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
    DECLARE @ErrorSeverity INT = ERROR_SEVERITY();
    DECLARE @ErrorState INT = ERROR_STATE();
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
  END;
END CATCH

SQLServer

BEGIN TRY
  EXEC sp_executesql N'
    ALTER TABLE t DROP COLUMN c
  ';
END TRY
BEGIN CATCH
  IF error_number() != 4924 and error_number() != 3728 and error_number() != 3727 THROW;
END CATCH

ASE, Access, Aurora MySQL, Derby, Firebird, HSQLDB, Informix, MemSQL, MySQL, Redshift, SQLite, Snowflake, Sybase, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.26. ALTER TABLE .. DROP CONSTRAINT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A constraint can be dropped from the table using the ALTER TABLE's DROP CONSTRAINT clause:

// Drop a constraint
create.alterTable("table").dropConstraint("uk").execute();

Alternative syntaxes are available to restrict dropping only to specific types of constraints:

Dialect support

This example using jOOQ:

alterTable("t").dropConstraint("c")

Translates to the following dialect specific expressions:

ASE, Access, Aurora Postgres, CockroachDB, DB2, Derby, Exasol, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, Oracle, Postgres, Redshift, SQLDataWarehouse, SQLServer, Snowflake, Sybase, Teradata, Vertica, YugabyteDB

ALTER TABLE t DROP CONSTRAINT c

BigQuery, ClickHouse

ALTER TABLE t DROP PRIMARY KEY

Aurora MySQL, DuckDB, MemSQL, MySQL, SQLite, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.27. ALTER TABLE .. DROP PRIMARY KEY

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A PRIMARY KEY constraint can be dropped from the table using the ALTER TABLE's DROP PRIMARY KEY clause:

// Drop specific types of constraints
create.alterTable("table").dropPrimaryKey().execute();

Dialect support

This example using jOOQ:

alterTable("t").dropPrimaryKey()

Translates to the following dialect specific expressions:

Aurora MySQL, BigQuery, DB2, Derby, Exasol, H2, HSQLDB, Hana, MariaDB, MySQL, Snowflake

ALTER TABLE t DROP PRIMARY KEY

Postgres

DO $$
DECLARE
  n varchar;
BEGIN
  n := (
    SELECT constraint_name
    FROM information_schema.table_constraints
    WHERE (
      (table_name = 't')
      AND (constraint_type = 'PRIMARY KEY')
    )
  );
  EXECUTE ('alter table "t" drop constraint ' || n);
END;
$$

ASE, Access, Aurora Postgres, ClickHouse, CockroachDB, DuckDB, Firebird, Informix, MemSQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.28. ALTER TABLE .. DROP UNIQUE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A UNIQUE constraint can be dropped from the table using the ALTER TABLE's DROP UNIQUE clause:

// Drop specific types of constraints
create.alterTable("table").dropUnique("uk").execute();

Dialect support

This example using jOOQ:

alterTable("t").dropUnique("c")

Translates to the following dialect specific expressions:

ASE, Access, Aurora MySQL, Aurora Postgres, DB2, Derby, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, MySQL, Oracle, Postgres, Redshift, SQLDataWarehouse, SQLServer, Snowflake, Sybase, Teradata, Vertica, YugabyteDB

ALTER TABLE t DROP CONSTRAINT c

CockroachDB

DROP INDEX c CASCADE

BigQuery, ClickHouse, DuckDB, Exasol, MemSQL, SQLite, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.29. ALTER TABLE .. DROP FOREIGN KEY

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A FOREIGN KEY constraint can be dropped from the table using the ALTER TABLE's DROP FOREIGN KEY clause:

// Drop specific types of constraints
create.alterTable("table").dropForeignKey("fk").execute();

Dialect support

This example using jOOQ:

alterTable("t").dropForeignKey("c")

Translates to the following dialect specific expressions:

ASE, Access, Aurora Postgres, CockroachDB, DB2, Derby, Exasol, Firebird, H2, HSQLDB, Hana, Informix, Oracle, Postgres, Redshift, SQLDataWarehouse, SQLServer, Snowflake, Sybase, Teradata, Vertica, YugabyteDB

ALTER TABLE t DROP CONSTRAINT c

Aurora MySQL, MariaDB, MySQL

ALTER TABLE t DROP FOREIGN KEY c

BigQuery

ALTER TABLE t DROP PRIMARY KEY

ClickHouse, DuckDB, MemSQL, SQLite, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.30. ALTER TABLE .. DROP CONSTRAINT IF EXISTS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A popular subclause of DDL statements that jOOQ can usually emulate, is the IF EXISTS clause:

Dialect support

This example using jOOQ:

alterTable("t").dropConstraintIfExists("c")

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, H2, MariaDB, Postgres

ALTER TABLE t DROP CONSTRAINT IF EXISTS c

BigQuery, ClickHouse

ALTER TABLE t DROP PRIMARY KEY

DB2

BEGIN
  DECLARE CONTINUE HANDLER FOR SQLSTATE '42703' BEGIN END;
  DECLARE CONTINUE HANDLER FOR SQLSTATE '42704' BEGIN END;
  EXECUTE IMMEDIATE '
    ALTER TABLE t DROP CONSTRAINT c
  ';
END

Firebird

EXECUTE BLOCK
AS
BEGIN
  EXECUTE STATEMENT '
    ALTER TABLE t DROP CONSTRAINT c
  ';
  WHEN sqlcode -607 DO
    BEGIN END
END

Hana

DO BEGIN
  DECLARE EXIT HANDLER FOR SQL_ERROR_CODE 257 BEGIN END;
  DECLARE EXIT HANDLER FOR SQL_ERROR_CODE 260 BEGIN END;
  DECLARE EXIT HANDLER FOR SQL_ERROR_CODE 397 BEGIN END;
  EXECUTE IMMEDIATE '
    ALTER TABLE t DROP CONSTRAINT c
  ';
END;

Oracle

BEGIN
  EXECUTE IMMEDIATE '
    ALTER TABLE t DROP CONSTRAINT c
  ';
EXCEPTION
  WHEN others THEN
    IF sqlerrm LIKE 'ORA-00904%' THEN NULL;
    ELSIF sqlerrm LIKE 'ORA-02443%' THEN NULL;
    ELSE RAISE;
    END IF;
END;

SQLServer

BEGIN TRY
  EXEC sp_executesql N'
    ALTER TABLE t DROP CONSTRAINT c
  ';
END TRY
BEGIN CATCH
  IF error_number() != 4924 and error_number() != 3728 and error_number() != 3727 THROW;
END CATCH

YugabyteDB

DO $$
BEGIN
  ALTER TABLE t DROP CONSTRAINT c;
EXCEPTION
  WHEN SQLSTATE '42704' THEN NULL;
END $$

ASE, Access, Aurora MySQL, Derby, DuckDB, Exasol, HSQLDB, Informix, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.6.31. ALTER TABLE IF EXISTS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A popular subclause of DDL statements that jOOQ can usually emulate, is the IF EXISTS clause:

// Renaming the table
create.alterTableIfExists("old_table").renameTo("new_table").execute();

Dialect support

This example using jOOQ:

alterTableIfExists("old").renameTo("new")

Translates to the following dialect specific expressions:

Aurora Postgres, BigQuery, CockroachDB, DuckDB, H2, Oracle, Postgres, Snowflake, Trino, YugabyteDB

ALTER TABLE IF EXISTS old RENAME TO new

DB2

BEGIN
  DECLARE CONTINUE HANDLER FOR SQLSTATE '42704' BEGIN END;
  EXECUTE IMMEDIATE '
    RENAME TABLE old TO new
  ';
END

Hana

DO BEGIN
  DECLARE EXIT HANDLER FOR SQL_ERROR_CODE 259 BEGIN END;
  EXECUTE IMMEDIATE '
    RENAME TABLE old TO new
  ';
END;

MariaDB

BEGIN NOT ATOMIC
  DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' BEGIN END;
  ALTER TABLE old RENAME TO new;
END

SQLServer

BEGIN TRY
  EXEC sp_executesql N'
    EXEC sp_rename ''old'', new
  ';
END TRY
BEGIN CATCH
  IF error_number() != 4902 and error_number() != 15165 and error_number() != 15225 THROW;
END CATCH

ASE, Access, Aurora MySQL, ClickHouse, Derby, Exasol, Firebird, HSQLDB, Informix, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Sybase, Teradata, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.7. ALTER TYPE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The ALTER TYPE statement allows of altering existing types. It supports the following subclauses:

 

3.6.1.7.1. ALTER TYPE .. RENAME

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Like most object types, types can be renamed. This is independent of the object type:

// Renaming the type
create.alterType("old_type").renameTo("new_type").execute();

Dialect support

This example using jOOQ:

alterType("old").renameTo("new")

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, Postgres

ALTER TYPE old RENAME TO new

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, DB2, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, MemSQL, MySQL, Oracle, Redshift, SQLDataWarehouse, SQLServer, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica, YugabyteDB

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.7.2. ALTER TYPE .. for enum alterations

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Some alterations are specific to enum types, e.g. in PostgreSQL. These include:

// Adding an enum value to an existing type
create.alterType("type").addValue("new_enum_value").execute();

// Renaming an enum value of an existing type to a new value
create.alterType("type").renameValue("old_enum_value").to("new_enum_value").execute();

// Move an enum type to a new schema
create.alterType("type").setSchema("new_schema").execute();
 

3.6.1.8. ALTER VIEW

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The ALTER VIEW statement allows of altering existing views. It supports the following subclauses:

 

3.6.1.8.1. ALTER VIEW .. AS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

This statement allows for replacing the contents of an existing view.

Dialect support

This example using jOOQ:

alterView("v").as(select(one().as("a")))

Translates to the following dialect specific expressions:

Aurora Postgres, Postgres, YugabyteDB

DO $$
BEGIN
  DROP VIEW v;
  CREATE VIEW v
  AS
  SELECT 1 a;
END;
$$

DB2

BEGIN
  EXECUTE IMMEDIATE 'DROP VIEW v';
  EXECUTE IMMEDIATE 'CREATE VIEW v
  AS
  SELECT 1 a
  FROM SYSIBM.DUAL';
END

Firebird

EXECUTE BLOCK AS
BEGIN
  EXECUTE STATEMENT 'DROP VIEW v';
  EXECUTE STATEMENT 'CREATE VIEW v
  AS
  SELECT 1 a
  FROM RDB$DATABASE';
END

H2

CREATE ALIAS block_1727885023582_3644937 AS $$
  void x(Connection c) throws SQLException {
    try (PreparedStatement s = c.prepareStatement(
      "DROP VIEW v"
    )) {
      s.execute();
    }
    try (PreparedStatement s = c.prepareStatement(
      "CREATE VIEW v\n" +
      "AS\n" +
      "SELECT 1 a"
    )) {
      s.execute();
    }
  }
$$;
CALL block_1727885023582_3644937();
DROP ALIAS block_1727885023582_3644937;

Hana

DO BEGIN
  EXECUTE IMMEDIATE 'DROP VIEW v';
  EXECUTE IMMEDIATE 'CREATE VIEW v
  AS
  SELECT 1 a
  FROM SYS.DUMMY';
END;

MariaDB

BEGIN NOT ATOMIC
  DROP VIEW v;
  CREATE VIEW v
  AS
  SELECT 1 a;
END;

MySQL

CREATE PROCEDURE block_1727885030497_4828349()
MODIFIES SQL DATA
BEGIN
  DROP VIEW v;
  CREATE VIEW v
  AS
  SELECT 1 a;
END;
CALL block_1727885030497_4828349();
DROP PROCEDURE block_1727885030497_4828349;

Oracle

BEGIN
  EXECUTE IMMEDIATE 'DROP VIEW v';
  EXECUTE IMMEDIATE 'CREATE VIEW v
  AS
  SELECT 1 a';
END;

SQLDataWarehouse, Vertica

BEGIN
  DROP VIEW v;
  CREATE VIEW v
  AS
  SELECT 1 a;
END;

SQLServer

ALTER VIEW v
AS
SELECT 1 a

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, Derby, DuckDB, Exasol, HSQLDB, Informix, MemSQL, Redshift, SQLite, Snowflake, Sybase, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.8.2. ALTER VIEW .. COMMENT

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

This statement allows for changing the comment associated with a view. It is an alias for the COMMENT ON VIEW statement

Dialect support

This example using jOOQ:

alterView("v").comment("new comment")

Translates to the following dialect specific expressions:

Aurora Postgres, Firebird, Hana, Postgres, Sybase, Teradata, Trino, Vertica, YugabyteDB

COMMENT ON VIEW v IS 'new comment'

BigQuery

ALTER VIEW v SET OPTIONS (DESCRIPTION = 'new comment')

DB2, DuckDB, H2, HSQLDB, Oracle

COMMENT ON TABLE v IS 'new comment'

Snowflake

ALTER VIEW v SET COMMENT = 'new comment'

SQLServer

BEGIN TRY
  DECLARE @u varchar(max) = schema_name();
  EXEC sp_addextendedproperty 'MS_Description', 'new comment', 'schema', @u, 'view', 'v', DEFAULT, DEFAULT
END TRY
BEGIN CATCH
  EXEC sp_updateextendedproperty 'MS_Description', 'new comment', 'schema', @u, 'view', 'v', DEFAULT, DEFAULT
END CATCH

ASE, Access, Aurora MySQL, ClickHouse, CockroachDB, Derby, Exasol, Informix, MariaDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.8.3. ALTER VIEW .. RENAME

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

This statement allows for renaming a view.

Dialect support

This example using jOOQ:

alterView("v").renameTo("new_name")

Translates to the following dialect specific expressions:

ASE, SQLServer

EXEC sp_rename v, new_name

Aurora Postgres, CockroachDB, DuckDB, H2, Postgres, Snowflake, Trino, Vertica

ALTER VIEW v RENAME TO new_name

Exasol, Teradata

RENAME VIEW v TO new_name

HSQLDB, YugabyteDB

ALTER TABLE v RENAME TO new_name

Oracle

RENAME v TO new_name

Access, Aurora MySQL, BigQuery, ClickHouse, DB2, Derby, Firebird, Hana, Informix, MariaDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Sybase

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.1.8.4. ALTER VIEW IF EXISTS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A popular subclause of DDL statements that jOOQ can usually emulate, is the IF EXISTS clause:

Dialect support

This example using jOOQ:

alterViewIfExists("v").renameTo("new_name")

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DuckDB, H2, Postgres

ALTER VIEW IF EXISTS v RENAME TO new_name

Oracle

BEGIN
  EXECUTE IMMEDIATE '
    RENAME v TO new_name
  ';
EXCEPTION
  WHEN others THEN
    IF sqlerrm LIKE 'ORA-00942%' THEN NULL;
    ELSIF sqlerrm LIKE 'ORA-04043%' THEN NULL;
    ELSE RAISE;
    END IF;
END;

Snowflake

ALTER VIEW v RENAME TO new_name

SQLServer

BEGIN TRY
  EXEC sp_rename v, new_name
END TRY
BEGIN CATCH
  IF error_number() != 15225 THROW;
END CATCH

YugabyteDB

ALTER TABLE IF EXISTS v RENAME TO new_name

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, DB2, Derby, Exasol, Firebird, HSQLDB, Hana, Informix, MariaDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Sybase, Teradata, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.2. The COMMENT statement

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The COMMENT statement can be used to store a description for an object from the database catalog.

It is available for the following types of object:

 

3.6.2.1. COMMENT ON TABLE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

This statement is used to comment on a table.

Dialect support

This example using jOOQ:

commentOnTable("table").is("the comment")

Translates to the following dialect specific expressions:

Aurora MySQL, MariaDB, MemSQL, MySQL

ALTER TABLE table COMMENT = 'the comment'

Aurora Postgres, CockroachDB, DB2, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Oracle, Postgres, Sybase, Teradata, Trino, Vertica, YugabyteDB

COMMENT ON TABLE table IS 'the comment'

BigQuery

ALTER TABLE table SET OPTIONS (DESCRIPTION = 'the comment')

ClickHouse

ALTER TABLE table MODIFY COMMENT 'the comment'

Snowflake

ALTER TABLE table SET COMMENT = 'the comment'

SQLServer

BEGIN TRY
  DECLARE @u varchar(max) = schema_name();
  EXEC sp_addextendedproperty 'MS_Description', 'the comment', 'schema', @u, 'table', 'table', DEFAULT, DEFAULT
END TRY
BEGIN CATCH
  EXEC sp_updateextendedproperty 'MS_Description', 'the comment', 'schema', @u, 'table', 'table', DEFAULT, DEFAULT
END CATCH

ASE, Access, Derby, Informix, Redshift, SQLDataWarehouse, SQLite

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.2.2. COMMENT ON VIEW

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

This statement is used to comment on a view.

Dialect support

This example using jOOQ:

commentOnView("view").is("the comment")

Translates to the following dialect specific expressions:

Aurora Postgres, Firebird, Hana, Postgres, Sybase, Teradata, Trino, Vertica, YugabyteDB

COMMENT ON VIEW view IS 'the comment'

BigQuery

ALTER VIEW view SET OPTIONS (DESCRIPTION = 'the comment')

ClickHouse

ALTER TABLE view MODIFY COMMENT 'the comment'

DB2, DuckDB, H2, HSQLDB, Oracle

COMMENT ON TABLE view IS 'the comment'

Snowflake

ALTER VIEW view SET COMMENT = 'the comment'

SQLServer

BEGIN TRY
  DECLARE @u varchar(max) = schema_name();
  EXEC sp_addextendedproperty 'MS_Description', 'the comment', 'schema', @u, 'view', 'view', DEFAULT, DEFAULT
END TRY
BEGIN CATCH
  EXEC sp_updateextendedproperty 'MS_Description', 'the comment', 'schema', @u, 'view', 'view', DEFAULT, DEFAULT
END CATCH

ASE, Access, Aurora MySQL, CockroachDB, Derby, Exasol, Informix, MariaDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.2.3. COMMENT ON COLUMN

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

This statement is used to comment on a column.

Dialect support

This example using jOOQ:

commentOnColumn(name("table", "column")).is("the comment")

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DB2, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Oracle, Postgres, Sybase, Teradata, Trino, Vertica, YugabyteDB

COMMENT ON COLUMN table.column IS 'the comment'

ClickHouse

ALTER TABLE table COMMENT COLUMN column 'the comment'

Snowflake

ALTER TABLE table ALTER COLUMN column COMMENT 'the comment'

SQLServer

BEGIN TRY
  DECLARE @u varchar(max) = schema_name();
  EXEC sp_addextendedproperty 'MS_Description', 'the comment', 'schema', @u, 'table', 'table', 'column', 'column'
END TRY
BEGIN CATCH
  EXEC sp_updateextendedproperty 'MS_Description', 'the comment', 'schema', @u, 'table', 'table', 'column', 'column'
END CATCH

ASE, Access, Aurora MySQL, BigQuery, Derby, Informix, MariaDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.3. 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.

 

3.6.3.1. CREATE DATABASE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The CREATE DATABASE statement is used to create a new database (catalog).

// Create a database
create.createDatabase("new_database").execute();

Dialect support

This example using jOOQ:

createDatabase("d")

Translates to the following dialect specific expressions:

Aurora Postgres, ClickHouse, CockroachDB, MariaDB, MemSQL, MySQL, Postgres, SQLDataWarehouse, SQLServer, YugabyteDB

CREATE DATABASE d

ASE, Access, Aurora MySQL, BigQuery, DB2, Derby, DuckDB, Exasol, Firebird, H2, HSQLDB, Hana, Informix, Oracle, Redshift, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.3.2. CREATE DOMAIN

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The CREATE DOMAIN statement allows for creating DOMAIN types for use as data types in table columns.

Depending on the dialect, a DOMAIN combines the following features:

  • A qualified name
  • A base data type
  • A DEFAULT value
  • A NOT NULL constraint
  • A COLLATION
  • A set of CHECK constraints

Domains can be created in jOOQ using:

// Create a domain on a base type
create.createDomain("d1").as(INTEGER).execute();

// Create a domain on a base type and add a DEFAULT expression
create.createDomain("d2").as(INTEGER).default_(1).execute();

// Create a domain on a base type and add a CHECK constraint
create.createDomain("d3").as(INTEGER).constraints(check(value(INTEGER).gt(0))).execute();

Dialect support

This example using jOOQ:

createDomain("d").as(INTEGER)

Translates to the following dialect specific expressions:

Aurora Postgres, H2, HSQLDB, Postgres, YugabyteDB

CREATE DOMAIN d AS int

Firebird

CREATE DOMAIN d AS integer

Oracle

CREATE DOMAIN d AS number(10)

SQLServer

CREATE TYPE d FROM int

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, DB2, Derby, DuckDB, Exasol, Hana, Informix, MariaDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Snowflake, Sybase, Teradata, Trino, Vertica

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.3.3. CREATE FUNCTION

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The CREATE FUNCTION statement allows for creating stored functions in your catalog.

A stored function, as opposed to a stored procedure, can be used in SQL statements as a scalar column expression, specifically a user defined function, or as a table expression, specifically a table valued functions.

 

3.6.3.3.1. Scalar functions

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The most common type of user defined function is a scalar function, i.e. a function that returns a single scalar value. Such functions can be used in the SELECT clause, the WHERE clause, the GROUP BY clause, the HAVING clause, the ORDER BY clause, and elsewhere, where column expressions can be used.

A simple example for creating such a function is:

// Create a function that always return 1
create.createFunction("one")
      .returns(INTEGER)
      .as(return_(1))
      .execute();

// Create a function that returns the sum of its inputs
Parameter<Integer> i1 = in("i1", INTEGER);
Parameter<Integer> i2 = in("i2", INTEGER);

create.createFunction("my_sum")
      .parameters(i1, i2)
      .returns(INTEGER)
      .as(return_(i1.plus(i2)))
      .execute();

Once you've created the above functions, you can either run code generation to get a type safe stub for calling them, or use plain SQL (specifically, DSL.function()) from within a SELECT statement:

// Call the previously created functions with generated code:
create.select(one(), mySum(1, 2)).fetchOne();

// ...or with plain SQL
create.select(
  function(name("one"), INTEGER),
  function(name("my_sum"), INTEGER, val(1), val(2))
).fetchOne();

Both yielding:

+-----+--------+
| ONE | MY_SUM |
+-----+--------+
|   1 |      3 |
+-----+--------+
 

3.6.3.3.2. CREATE OR REPLACE FUNCTION

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

In most cases, you will want to CREATE OR REPLACE a function, not CREATE [ OR FAIL ] when the function already exists. For this, just use the auxiliary OR REPLACE clause, which can be emulated by jOOQ via an additional DROP FUNCTION statement if this syntax is not available in your dialect.

// Create a function that always return 1
create.createOrReplaceFunction("one")
      .returns(INTEGER)
      .as(return_(1))
      .execute();
 

3.6.3.3.3. SQL data access characteristics

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Some dialects require the explicit specification of a few characteristics of a function, defining what kind of content a function is allowed (and expected) to have. These act both as contracts for your development (similar to the java.lang.FunctionalInterface annotation), as well as hints to the database regarding whether a function is expected to have side-effects (and thus maybe cannot be used in a SELECT statement), or whether it depends on data, or is purely deterministic (see also the DETERMINISTIC characteristic). The SQL data access characteristics include:

  • NO SQL
  • CONTAINS SQL
  • READS SQL DATA
  • MODIFIES SQL DATA

While the semantics seem pretty clear, please refer to your database manual for the details, as there may be subtle differences, e.g. regarding what particular procedural statement constitues "SQL".

If a characteristic is not supported by your dialect, you can still specify it, and jOOQ will simply ignore it in generated SQL.

create.createFunction("f1").returns(INTEGER).noSQL().as(return_(1)).execute();
create.createFunction("f2").returns(INTEGER).containsSQL().as(return_(select(val(1)))).execute();
create.createFunction("f3").returns(INTEGER).readsSQLData().as(return_(selectCount().from(BOOK))).execute();
create.createFunction("f4").returns(INTEGER).modifiesSQLData().as(
  insertInto(LOGS).columns(LOGS.TEXT).values("Function F4 was called"),
  return_(1)
).execute();
 

3.6.3.3.4. DETERMINISTIC characteristic

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Some dialects require the explicit specification of a few characteristics of a function. The DETERMINISTIC characteristic can be used to tell the database that contents of a function are guaranteed by the user to be "deterministic" (or "IMMUTABLE" in PostgreSQL), meaning that the result of a function is purely defined by the function arguments (never by data or session values or the current time or random number generators, etc.) such that a function expression can be replaced by the result value at the call site. Such a function is also said to be side-effect free, and pure. Some dialects (e.g. Oracle) allow for using DETERMINISTIC functions in function based indexes.

If DETERMINISTIC is not supported by your dialect, you can still specify it, and jOOQ will simply ignore it in generated SQL.

create.createFunction("f1").returns(INTEGER).deterministic().as(return_(1)).execute();
create.createFunction("f2").returns(INTEGER).notDeterministic().as(return_(rand())).execute();
 

3.6.3.3.5. ON NULL INPUT characteristic

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

This characteristic both determines a contract for use by optimisers, as well as influences the behaviour of a function.

Most built-in SQL functions return NULL as soon as any of the arguments are NULL. For example SUBSTRING:

create.select(
  substring("abc"             , 2)                 .as("s1"),
  substring("abc"             , val(null, INTEGER)).as("s2"),
  substring(val(null, VARCHAR), 2)                 .as("s3"),
  substring(val(null, VARCHAR), val(null, INTEGER)).as("s4"),
).fetchOne();

Yielding:

+----+--------+--------+--------+
| s1 | s2     | s3     | s4     |
+----+--------+--------+--------+
| bc | {null} | {null} | {null} |
+----+--------+--------+--------+

While it is easy to implement this manually, it is both convenient, and helpful for optimisers, to just use the characteristic to achieve this standard behaviour, and possibly even to prevent calling the function, preventing the overhead from the context switches, etc.

If not natively supported by your dialect, jOOQ will simply wrap your function body in a IF statement checking for argument value nullability.

Parameter<Integer> i1 = in("i1", INTEGER);
Parameter<Integer> i2 = in("i2", INTEGER);

// The function always returns NULL if any argument value is NULL
create.createFunction("my_sum")
      .parameters(i1, i2)
      .returns(INTEGER)
      .returnsNullOnNullInput()
      .as(return_(i1.plus(i2)))
      .execute();

// The function may not return NULL if any argument value is NULL
create.createFunction("my_null_safe_sum")
      .parameters(i1, i2)
      .returns(INTEGER)
      .calledOnNullInput()
      .as(return_(coalesce(i1, 0).plus(coalesce(i2, 0))))
      .execute();
 

3.6.3.4. 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();

CREATE UNIQUE INDEX

In many dialects, there is a possibility of creating a unique index, which acts like a constraint (see ALTER TABLE or CREATE TABLE), but is not really a constraint. Most dialects will create an index automatically to enforce a UNIQUE constraint, so using a constraint instead may seem a bit cleaner. A UNIQUE INDEX is created like this:

// Create an index on a single column
create.createUniqueIndex("index").on("table", "column").execute();

// Create an index on several columns
create.createUniqueIndex("index").on("table", "column1", "column2").execute();

Sorted indexes

In most dialects, indexes have their columns sorted ascendingly by default. If you wish to create an index with a differing sort order, you can do so by providing the order explicitly:

// Create a sorted index on several columns
create.createIndex("index").on(
  table(name("table")),
  field(name("column1")).asc(),
  field(name("column2")).desc()
).execute();

Covering indexes (with INCLUDE clause)

A few dialects support an INCLUDE clause when creating an index. This can be useful to create covering indexes. These are indexes that "cover" the needs of an entire query, such that no secondary lookup needs to be done in a heap table or clustered index, after finding only parts of the projection in the index data structure. The data from the columns of the INCLUDE clause will be located only in the index leaf nodes (useful for projections), not in the index tree structure (useful for searches), which reduces index maintenance overhead, and index size.

If a dialect does not support this clause, jOOQ will simply add the INCLUDE columns into the ordinary index column list.

// Create a covering index with included columns
create.createIndex("index").on("table", "search_column").include("projection_column").execute();

Partial indexes (with WHERE clause)

A few dialects support a WHERE clause when creating an index. This is very useful to drastically reduce the size of an index, and thus index maintenance, if only parts of the data of a column need to be included in the index.

// Create a partial index
create.createIndex("index").on("table", "column").where(field(name("column")).gt(0)).execute();
 

3.6.3.5. CREATE PROCEDURE

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The CREATE PROCEDURE statement allows for creating stored procedures in your catalog.

A stored procedure, as opposed to a stored function, is expected to have side effects of some sort, and can thus not be used in SQL statements.

A simple example for creating such a procedure is:

// Create a procedure that inserts a log message in a table
create.createProcedure("log")
      .as(insertInto(LOG).columns(LOG.TEXT).values("Log called"))
      .execute();

Once you've created the above procedure, you can either run code generation to get a type safe stub for calling them, or use CALL in an anonymous block, or directly:

// Call the previously created procedure with generated code:
log(configuration);

// ...or with the CALL statement in an anonymous block
create.begin(call(name("log"))).execute();

// ...or with the CALL statement directly
call(name("log")).execute();
 

3.6.3.5.1. CREATE OR REPLACE PROCEDURE

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

In most cases, you will want to CREATE OR REPLACE a procedure, not CREATE [ OR FAIL ] when the procedure already exists. For this, just use the auxiliary OR REPLACE clause, which can be emulated by jOOQ via an additional DROP PROCEDURE statement if this syntax is not available in your dialect.

// Create a procedure that inserts a log message in a table
create.createOrReplaceProcedure("log")
      .as(insertInto(LOG).columns(LOG.TEXT).values("Log called"))
      .execute();
 

3.6.3.5.2. SQL data access characteristics

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Some dialects require the explicit specification of a few characteristics of a procedure, defining what kind of content a procedure is allowed (and expected) to have. These act both as contracts for your development (similar to the java.lang.FunctionalInterface annotation), as well as hints to the database regarding whether a procedure is expected to have side-effects (and thus maybe cannot be used indirectly via a stored function in a SELECT statement), or whether it depends on data, or is purely deterministic. The SQL data access characteristics include:

While procedures are generally expected to yield side effects, it may be useful to use a procedure as a "function with quirky syntax" to be consumed by other functions, because it can return several OUT parameters, instead of just a single RETURN value, like function, hence the utility of these characteristics also in procedures.

  • NO SQL
  • CONTAINS SQL
  • READS SQL DATA
  • MODIFIES SQL DATA

While the semantics seem pretty clear, please refer to your database manual for the details, as there may be subtle differences, e.g. regarding what particular procedural statement constitues "SQL".

If a characteristic is not supported by your dialect, you can still specify it, and jOOQ will simply ignore it in generated SQL.

Parameter<Integer> o = out("o", INTEGER);

create.createProcedure("p1")
      .parameters(o)
      .noSQL()
      .as(o.set(1))
      .execute();

create.createProcedure("p2")
      .parameters(o)
      .containsSQL()
      .as(o.set(select(val(1))))
      .execute();

create.createProcedure("p3")
      .parameters(o)
      .readsSQLData()
      .as(o.set(selectCount().from(BOOK)))
      .execute();

create.createProcedure("p4")
      .parameters(o)
      .modifiesSQLData()
      .as(
        insertInto(LOGS).columns(LOGS.TEXT).values("Function F4 was called"),
        o.set(1)
      )
      .execute();
 

3.6.3.6. CREATE SCHEMA

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

The CREATE SCHEMA statement is used to create a new schema in the database catalog.

// Create a schema
create.createSchema("new_schema").execute();

Dialect support

This example using jOOQ:

createSchema("s")

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DB2, Derby, DuckDB, Exasol, H2, HSQLDB, Hana, MariaDB, MemSQL, MySQL, Postgres, Redshift, SQLDataWarehouse, SQLServer, Snowflake, Vertica, YugabyteDB

CREATE SCHEMA s

ClickHouse

CREATE DATABASE s

Oracle

CREATE USER s NO AUTHENTICATION QUOTA UNLIMITED ON USERS

ASE, Access, Aurora MySQL, BigQuery, Firebird, Informix, SQLite, Sybase, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.3.7. 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();

Dialect support

This example using jOOQ:

createSequence("s")

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DB2, DuckDB, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, Oracle, Postgres, Snowflake, Sybase, Vertica, YugabyteDB

CREATE SEQUENCE s

Derby, SQLServer

CREATE SEQUENCE s START WITH 1

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, Exasol, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.3.7.1. CREATE SEQUENCE IF NOT EXISTS

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

A popular subclause of DDL statements that jOOQ can usually emulate, is the IF NOT EXISTS clause:

Dialect support

This example using jOOQ:

createSequenceIfNotExists("s")

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DuckDB, H2, HSQLDB, Informix, MariaDB, Oracle, Postgres, Snowflake, Vertica, YugabyteDB

CREATE SEQUENCE IF NOT EXISTS s

DB2

BEGIN
  DECLARE CONTINUE HANDLER FOR SQLSTATE '42710' BEGIN END;
  EXECUTE IMMEDIATE '
    CREATE SEQUENCE s
  ';
END

Firebird

EXECUTE BLOCK
AS
BEGIN
  EXECUTE STATEMENT '
    CREATE SEQUENCE s
  ';
  WHEN sqlcode -607 DO
    BEGIN END
END

Hana

DO BEGIN
  DECLARE EXIT HANDLER FOR SQL_ERROR_CODE 324 BEGIN END;
  EXECUTE IMMEDIATE '
    CREATE SEQUENCE s
  ';
END;

SQLServer

BEGIN TRY
  CREATE SEQUENCE s START WITH 1
END TRY
BEGIN CATCH
  IF error_number() != 2714 THROW;
END CATCH

Sybase

BEGIN
  CREATE SEQUENCE s;
EXCEPTION
  WHEN others THEN
END;

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, Derby, Exasol, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.3.7.2. CREATE SEQUENCE .. CACHE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Some RDBMS support caching a number of sequence value in the database session in order to speed up concurrent access to the sequence resource.

// Cache a number of values for the sequence, typically on a per session basis.
create.createSequence("sequence").cache(200).execute();
create.createSequence("sequence").noCache().execute();

Dialect support

This example using jOOQ:

createSequence("s").cache(200)

Translates to the following dialect specific expressions:

Aurora Postgres, DB2, H2, Hana, Informix, MariaDB, Oracle, Postgres, Sybase, Vertica, YugabyteDB

CREATE SEQUENCE s CACHE 200

SQLServer

CREATE SEQUENCE s START WITH 1 CACHE 200

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, Derby, DuckDB, Exasol, Firebird, HSQLDB, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Snowflake, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.3.7.3. CREATE SEQUENCE .. CYCLE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Normally, a sequence starts at 1 and is increased to "infinity" (or rather, until it reaches its data type's MAXVALUE). In some cases, it may be useful to restart the sequence again at some MINVALUE. This can be achieved by using the CYCLE flag:

// Specify whether the sequence should cycle when it reaches the MAXVALUE
create.createSequence("sequence").cycle().execute();
create.createSequence("sequence").noCycle().execute();

Dialect support

This example using jOOQ:

createSequence("s").cycle()

Translates to the following dialect specific expressions:

Aurora Postgres, DB2, DuckDB, H2, HSQLDB, Hana, Informix, MariaDB, Oracle, Postgres, Sybase, Vertica, YugabyteDB

CREATE SEQUENCE s CYCLE

Derby, SQLServer

CREATE SEQUENCE s START WITH 1 CYCLE

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, CockroachDB, Exasol, Firebird, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Snowflake, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.3.7.4. CREATE SEQUENCE .. MINVALUE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

When using the CYCLE clause, it may be useful to specify a different MINVALUE than the default of 1:

// The MINVALUE from which the sequence should cycle if applicable
create.createSequence("sequence").minvalue(1).execute();
create.createSequence("sequence").noMinvalue().execute();

Dialect support

This example using jOOQ:

createSequence("s").minvalue(1)

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DB2, DuckDB, H2, HSQLDB, Hana, Informix, MariaDB, Oracle, Postgres, Sybase, Vertica, YugabyteDB

CREATE SEQUENCE s MINVALUE 1

Derby, SQLServer

CREATE SEQUENCE s START WITH 1 MINVALUE 1

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, Exasol, Firebird, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Snowflake, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.3.7.5. CREATE SEQUENCE .. MAXVALUE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

When using the CYCLE clause, it may be useful to specify a different MAXVALUE than the default of the sequence data type's maximum representable value:

// The MAXVALUE before which the sequence should cycle if applicable
create.createSequence("sequence").maxvalue(1000).execute();
create.createSequence("sequence").noMaxvalue().execute();

Dialect support

This example using jOOQ:

createSequence("s").maxvalue(10)

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DB2, DuckDB, H2, HSQLDB, Hana, Informix, MariaDB, Oracle, Postgres, Sybase, Vertica, YugabyteDB

CREATE SEQUENCE s MAXVALUE 10

Derby, SQLServer

CREATE SEQUENCE s START WITH 1 MAXVALUE 10

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, Exasol, Firebird, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Snowflake, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.3.7.6. CREATE SEQUENCE .. INCREMENT BY

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

In some cases, it may be useful to skip values in a sequence. This can be achieved using the INCREMENT BY clause

Dialect support

This example using jOOQ:

createSequence("s").incrementBy(10)

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DB2, DuckDB, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, Oracle, Postgres, Snowflake, Sybase, Vertica, YugabyteDB

CREATE SEQUENCE s INCREMENT BY 10

Derby, SQLServer

CREATE SEQUENCE s START WITH 1 INCREMENT BY 10

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, Exasol, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.3.7.7. CREATE SEQUENCE .. START WITH

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

In some cases, it may be useful to start a sequence with a different value than the default of 1, for example when a sequence is introduced on a table containing existing data.

Dialect support

This example using jOOQ:

createSequence("s").startWith(1000)

Translates to the following dialect specific expressions:

Aurora Postgres, CockroachDB, DB2, Derby, DuckDB, Firebird, H2, HSQLDB, Hana, Informix, MariaDB, Oracle, Postgres, SQLServer, Snowflake, Sybase, Vertica, YugabyteDB

CREATE SEQUENCE s START WITH 1000

ASE, Access, Aurora MySQL, BigQuery, ClickHouse, Exasol, MemSQL, MySQL, Redshift, SQLDataWarehouse, SQLite, Teradata, Trino

/* UNSUPPORTED */
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.3.8. CREATE TABLE

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Arguably the most used DDL statement is the CREATE TABLE statement.

The following subsections discuss various usages of CREATE TABLE, as well as the relevant bits of meta data that can be added to a table.

 

3.6.3.8.1. Columns

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

All tables contain at least one column (except for some esoteric cases in PostgreSQL), and all SQL dialects support creating such tables:

// Create a new table with a column
create.createTable("table")
      .column("col1", INTEGER)
      .execute();

Dialect support

This example using jOOQ:

createTable("table").column("col1", INTEGER)

Translates to the following dialect specific expressions:

Access, DB2, Firebird, Hana, Informix, Teradata

CREATE TABLE table (
  col1 integer
)

ASE, Sybase

CREATE TABLE table (
  col1 int NULL
)

Aurora MySQL, Aurora Postgres, Derby, DuckDB, Exasol, H2, HSQLDB, MariaDB, MemSQL, MySQL, Postgres, Redshift, SQLDataWarehouse, SQLServer, SQLite, Trino, Vertica, YugabyteDB

CREATE TABLE table (
  col1 int
)

BigQuery

CREATE TABLE table (
  col1 int64
)

ClickHouse

CREATE TABLE table (
  col1 Nullable(integer)
)
ENGINE Log()

CockroachDB

CREATE TABLE table (
  col1 int4
)

Oracle, Snowflake

CREATE TABLE table (
  col1 number(10)
)
Generated with jOOQ 3.20. Translate your own SQL on our website
 

3.6.3.8.2. Nullability

Applies to ✅ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

Nullability is a property of a data type, and as such can be attached to the data type using various methods. The default nullability is RDBMS specific, so if you want to be vendor agnostic about nullability in your DDL, better always state it explicitly, for example:

// Specify nullability on columns
create.createTable("table")
      .column("vendor_specific_default", INTEGER)
      .column("explicit_nullable", INTEGER.null_())
      .column("explicit_not_nullable", INTEGER.notNull())
      .execute();

Dialect support

This example using jOOQ:

createTable("table")
      .column("vendor_specific_default", INTEGER)
      .column("explicit_nullable", INTEGER.null_())
      .column("explicit_not_nullable", INTEGER.notNull())

Translates to the following dialect specific expressions:

Access, DB2, Hana, Informix, Teradata

CREATE TABLE table (
  vendor_specific_default integer,
  explicit_nullable integer NULL,
  explicit_not_nullable integer NOT NULL
)

ASE, Sybase

CREATE TABLE table (
  vendor_specific_default int NULL,
  explicit_nullable int NULL,
  explicit_not_nullable int NOT NULL
)