MULTISET值构造函数
适用于 ✅ 开源版 ✅ 专业版 ✅ 企业版
MULTISET 值构造器是 jOOQ 和标准 SQL 最强大的特性之一。它允许将非标量子查询 的结果收集到具有 MULTISET 语义的单个嵌套集合值中(序数未在元素上定义,但 jOOQ 尝试在投影 MULTISET 时保持 ORDER BY 产生的顺序)。
例如,让我们查找
- 所有作者。
- 该作者的书籍出版的语言。
- 该作者的书籍可用的书店。
这可以在单个查询中完成
SELECT
AUTHOR.FIRST_NAME,
AUTHOR.LAST_NAME,
MULTISET(
SELECT DISTINCT
LANGUAGE.CD
LANGUAGE.DESCRIPTION
FROM BOOK
JOIN LANGUAGE ON BOOK.LANGUAGE_ID = LANGUAGE.ID
WHERE BOOK.AUTHOR_ID = AUTHOR.ID
) AS BOOKS,
MULTISET(
SELECT DISTINCT BOOK_TO_BOOK_STORE.BOOK_STORE_NAME
FROM BOOK_TO_BOOK_STORE
JOIN BOOK ON BOOK_TO_BOOK_STORE.BOOK_ID = BOOK.ID
WHERE BOOK.AUTHOR_ID = AUTHOR.ID
) AS BOOK_STORES
FROM AUTHOR
ORDER BY AUTHOR.ID
var result = create.select(
AUTHOR.FIRST_NAME,
AUTHOR.LAST_NAME,
multiset(
selectDistinct(
BOOK.language().CD,
BOOK.language().DESCRIPTION)
.from(BOOK)
.where(BOOK.AUTHOR_ID.eq(AUTHOR.ID))
).as("books"),
multiset(
selectDistinct(BOOK_TO_BOOK_STORE.BOOK_STORE_NAME)
.from(BOOK_TO_BOOK_STORE)
.where(BOOK_TO_BOOK_STORE.tBook().AUTHOR_ID.eq(AUTHOR.ID))
).as("book_stores"))
.from(AUTHOR)
.orderBy(AUTHOR.ID)
.fetch();
请注意 Java 10 的 var 关键字在这里是如何发挥作用的。通常不希望表示 jOOQ 中嵌套记录或集合产生的类型。上面的 var result 推断为
Result<Record4<
String, // AUTHOR.FIRST_NAME
String, // AUTHOR.LAST_NAME
Result<Record2<
String, // LANGUAGE.CD
String // LANGUAGE.DESCRIPTION
>>, // books
Result<Record1<
String // BOOK_TO_BOOK_STORE.BOOK_STORE_NAME
>> // book_stores
>> result = ...
另请注意,在很多情况下,使用RecordMapper 在将集合嵌套到 DTO 树时非常有用,特别是与临时转换器结合使用时。
以上查询的结果可能如下所示
+----------+---------+-----------------------------+--------------------------------------------------+
|first_name|last_name|books |book_stores |
+----------+---------+-----------------------------+--------------------------------------------------+
|George |Orwell |[(en, English)] |[(Ex Libris), (Orell Füssli)] |
|Paulo |Coelho |[(de, Deutsch), (pt, {null})]|[(Buchhandlung im Volkshaus), (Ex Libris), (Ore...|
+----------+---------+-----------------------------+--------------------------------------------------+
或者,当导出为 JSON 时(或者,直接使用JSON_ARRAYAGG)
[
{
"first_name": "George",
"last_name": "Orwell",
"books": [
{
"cd": "en",
"description": "English"
}
],
"book_stores": [
{ "book_store_name": "Ex Libris" },
{ "book_store_name": "Orell Füssli" }
]
},
{
"first_name": "Paulo",
"last_name": "Coelho",
"books": [
{
"cd": "de",
"description": "Deutsch"
},
{
"cd": "pt",
"description": null
}
],
"book_stores": [
{ "book_store_name": "Buchhandlung im Volkshaus" },
{ "book_store_name": "Ex Libris" },
{ "book_store_name": "Orell Füssli" }
]
}
]
<result>
<record>
<first_name>George</first_name>
<last_name>Orwell</last_name>
<books>
<result>
<record>
<cd>en</cd>
<description>English</description>
</record>
</result>
</books>
<book_stores>
<result>
<record>
<book_store_name>Ex Libris</book_store_name>
</record>
<record>
<book_store_name>Orell Füssli</book_store_name>
</record>
</result>
</book_stores>
</record>
<record>
<first_name>Paulo</first_name>
<last_name>Coelho</last_name>
<books>
<result>
<record>
<cd>de</cd>
<description>Deutsch</description>
</record>
<record>
<cd>pt</cd>
<description/>
</record>
</result>
</books>
<book_stores>
<result>
<record>
<book_store_name>Buchhandlung im Volkshaus</book_store_name>
</record>
<record>
<book_store_name>Ex Libris</book_store_name>
</record>
<record>
<book_store_name>Orell Füssli</book_store_name>
</record>
</result>
</book_stores>
</record>
</result>
实现
坏消息是,几乎没有任何方言原生支持 MULTISET 构造器(例如,Informix 或 Oracle 支持)。在所有其他方言中,必须使用 SQL/JSON 或 SQL/XML 模拟它。以上查询在 PostgreSQL 中可能如下所示
SELECT
AUTHOR.FIRST_NAME,
AUTHOR.LAST_NAME,
(
SELECT COALESCE(
JSONB_AGG(JSONB_BUILD_ARRAY(V0, V1)),
JSONB_BUILD_ARRAY()
)
FROM (
SELECT DISTINCT
ALIAS_86077489.CD AS V0,
ALIAS_86077489.DESCRIPTION AS V1
FROM BOOK
JOIN LANGUAGE AS ALIAS_86077489
ON BOOK.LANGUAGE_ID = ALIAS_86077489.ID
WHERE BOOK.AUTHOR_ID = AUTHOR.ID
) AS T
) AS BOOKS,
(
SELECT COALESCE(
JSONB_AGG(JSONB_BUILD_ARRAY(V0)),
JSONB_BUILD_ARRAY()
)
FROM (
SELECT DISTINCT BOOK_TO_BOOK_STORE.BOOK_STORE_NAME AS V0
FROM BOOK_TO_BOOK_STORE
JOIN BOOK AS ALIAS_129518614
ON BOOK_TO_BOOK_STORE.BOOK_ID = ALIAS_129518614.ID
WHERE ALIAS_129518614.AUTHOR_ID = AUTHOR.ID
) AS T
) AS BOOK_STORES
FROM AUTHOR
ORDER BY AUTHOR.ID
您可能会注意到,这会产生与手动创建略有不同的 JSON 结构。它生成数组的数组,在格式化的结果中看起来像这样
+----------+---------+---------------------------------+----------------------------------------------------------------+ |first_name|last_name|books |book_stores | +----------+---------+---------------------------------+----------------------------------------------------------------+ |George |Orwell |[["en", "English"]] |[["Ex Libris"], ["Orell Füssli"]] | |Paulo |Coelho |[["de", "Deutsch"], ["pt", null]]|[["Buchhandlung im Volkshaus"], ["Ex Libris"], ["Orell Füssli"]]| +----------+---------+---------------------------------+----------------------------------------------------------------+
好处是
- 数组比 JSON 中的对象占用更少的空间,因此序列化格式更优化
- 数组不关心重复的列名,这可能会导致各种 JSON 解析器出现问题(即使 JSON 支持它)
- 数组元素具有明确定义的顺序,对象键没有,并且索引查找比名称查找更快
生成的 JSON 或 XML 文档将被解析并映射到 jOOQ org.jooq.Result 和 org.jooq.Record 层次结构。
默认情况下,使用“最佳”序列化格式(JSON、XML 或将来的 ARRAY),但您可以使用Settings.emulateMultiset覆盖它,它提供以下值
-
DEFAULT:让 jOOQ 决定如何序列化嵌套集合 -
XML:使用 XML 序列化嵌套集合 -
JSON:使用 JSON 序列化嵌套集合 -
JSONB:使用 JSONB 序列化嵌套集合 -
NATIVE:生成原生语法
方言支持
此示例使用 jOOQ
multiset(select(BOOK.ID, BOOK.TITLE).from(BOOK))
翻译成以下特定方言的表达式
Aurora Postgres, CockroachDB, Postgres, YugabyteDB
(
SELECT coalesce(
jsonb_agg(jsonb_build_array(v0, v1)),
jsonb_build_array()
)
FROM (
SELECT
BOOK.ID v0,
BOOK.TITLE v1
FROM BOOK
) t
)
BigQuery, Informix
MULTISET( SELECT BOOK.ID, BOOK.TITLE FROM BOOK )
DB2
(
SELECT xmlelement(
NAME result,
xmlagg(xmlelement(
NAME record,
xmlelement(NAME v0, BOOK.ID),
xmlelement(
NAME v1,
xmlattributes(
CASE
WHEN BOOK.TITLE IS NULL THEN 'true'
END AS "xsi:nil"
),
BOOK.TITLE
)
))
)
FROM BOOK
)
DuckDB
ARRAY(
SELECT t
FROM (
SELECT BOOK.ID, BOOK.TITLE
FROM BOOK
) t
)
H2
(
SELECT coalesce(
json_arrayagg(json_array(BOOK.ID, BOOK.TITLE NULL ON NULL)),
json_array(NULL ON NULL)
)
FROM BOOK
)
MariaDB
(
SELECT coalesce(
json_merge_preserve(
'[]',
concat(
'[',
group_concat(json_array(BOOK.ID, BOOK.TITLE) SEPARATOR ','),
']'
)
),
json_array()
)
FROM BOOK
)
MySQL
(
SELECT coalesce(
json_merge_preserve(
'[]',
concat(
'[',
group_concat(json_array(t.v0, t.v1) SEPARATOR ','),
']'
)
),
json_array()
)
FROM (
SELECT
BOOK.ID v0,
BOOK.TITLE v1
FROM BOOK
) t
)
Oracle
(
SELECT coalesce(
json_arrayagg(json_array(t.v0, t.v1 NULL ON NULL RETURNING clob) FORMAT JSON RETURNING clob),
json_array(RETURNING clob)
)
FROM (
SELECT
BOOK.ID v0,
BOOK.TITLE v1
FROM BOOK
) t
)
Snowflake
(
SELECT coalesce(
array_agg(array_construct(coalesce(
to_variant(BOOK.ID),
parse_json('null')
), coalesce(
to_variant(BOOK.TITLE),
parse_json('null')
))) WITHIN GROUP (ORDER BY NULL),
array_construct()
)
FROM BOOK
)
SQLite
(
SELECT coalesce(
json_group_array(json_array(t.v0, t.v1)),
json_array()
)
FROM (
SELECT
BOOK.ID v0,
BOOK.TITLE v1
FROM BOOK
) t
)
SQLServer
coalesce(
(
SELECT
v0 v0,
v1 v1
FROM (
SELECT BOOK.ID, BOOK.TITLE
FROM BOOK
) t (v0, v1)
FOR XML RAW ('record'), ELEMENTS XSINIL, BINARY BASE64, TYPE, ROOT ('result')
),
'<result/>'
)
Teradata
(
SELECT xmlelement(
NAME "result",
xmlagg(xmlelement(
NAME record,
xmlelement(NAME v0, t.v0),
xmlelement(
NAME v1,
xmlattributes(
CASE
WHEN t.v1 IS NULL THEN 'true'
END AS nil
),
t.v1
)
))
)
FROM (
SELECT
BOOK.ID v0,
BOOK.TITLE v1
FROM BOOK
) t
)
Trino
(
SELECT coalesce(
cast(array_agg(CAST(ARRAY[
CAST(t.v0 AS json),
CAST(t.v1 AS json)
] AS json)) AS json),
CAST(ARRAY[] AS json)
)
FROM (
SELECT
BOOK.ID v0,
BOOK.TITLE v1
FROM BOOK
) t
)
ASE, Access, Aurora MySQL, ClickHouse, Databricks, Derby, Exasol, Firebird, HSQLDB, Hana, MemSQL, Redshift, SQLDataWarehouse, Sybase, Vertica
/* UNSUPPORTED */
使用 jOOQ 3.21 生成。早期 jOOQ 版本的支持可能有所不同。 在我们的网站上翻译您自己的 SQL
反馈
您对此页面有任何反馈吗? 我们很乐意听取您的意见!