Les questions qui pourraient être un peu plus difficiles à résoudre en utilisant le SQL ordinaire sont des questions du genre :
Quels films ont les mêmes acteurs qu’un film X donné ?
Comme toujours, nous utilisons la base de données sakila pour cet exemple. Quel serait un moyen possible de résoudre ce problème avec SQL (par exemple, PostgreSQL, pour être précis) ? La requête suivante donne un aperçu des acteurs par film :
SELECT
film_id,
array_agg(actor_id ORDER BY actor_id) actors
FROM film_actor
GROUP BY film_id
Il produit quelque chose comme ceci :
|film_id|actors | |-------|----------------------------------| |1 |{1,10,20,30,40,53,108,162,188,198}| |2 |{19,85,90,160} | |3 |{2,19,24,64,123} | |4 |{41,81,88,147,162} | |5 |{51,59,103,181,200} | |6 |{21,23,62,108,137,169,197} | |... |... |
Notez qu’en SQL, les tableaux se comportent comme des listes, c’est-à-dire qu’ils conservent leur ordre, donc l’ordre explicite du tableau est important pour pouvoir comparer les acteurs entre eux. Maintenant, nous voulons trouver tous les films qui partagent le même jeu d’acteurs, à partir de ce qui précède :
WITH t AS (
-- Previous query
SELECT
film_id,
array_agg(actor_id ORDER BY actor_id) actors
FROM film_actor
GROUP BY film_id
)
SELECT
array_agg(film_id ORDER BY film_id) AS films,
actors
FROM t
GROUP BY actors
ORDER BY count(*) DESC, films
Le résultat est maintenant :
|films |actors | |--------|----------------------------------| |{97,556}|{65} | |{1} |{1,10,20,30,40,53,108,162,188,198}| |{2} |{19,85,90,160} | |{3} |{2,19,24,64,123} | |{4} |{41,81,88,147,162} | |{5} |{51,59,103,181,200} | |{6} |{21,23,62,108,137,169,197} | |... |... |
Ainsi, comme nous pouvons le voir, il n’y a que 2 films qui partagent le même ensemble d’acteurs, et ces films sont FILM_ID IN (97, 556)
. (La base de données Sakila est un peu ennuyeuse car les ensembles de données sont générés).
Utilisation des comparaisons MULTISET
Bien que ce qui précède soit déjà assez cool, dans cet article, j’aimerais présenter une fonctionnalité moins connue du jOOQ 3.15 MULTISET
support, à savoir le fait qu’ils peuvent être comparés entre eux.
Et comme c’est la nature de la norme SQL MULTISET
l’ordre n’est pas pertinent, nous n’avons donc pas à ajouter d’éléments explicites ORDER BY
clause pour une telle comparaison. En fait, ce n’est pas 100% hors de propos. Toi peut commander un MULTISET
à des fins de projection, la commande sera donc maintenue par jOOQ. Mais lorsque vous les utilisez dans des prédicats, jOOQ remplacera votre ORDER BY
clause.
En utilisant jOOQ, on peut écrire :
ctx.select(FILM.FILM_ID, FILM.TITLE)
.from(FILM)
.where(
multiset(
select(FILM_ACTOR.ACTOR_ID)
.from(FILM_ACTOR)
.where(FILM_ACTOR.FILM_ID.eq(FILM.FILM_ID))
).eq(multiset(
select(FILM_ACTOR.ACTOR_ID)
.from(FILM_ACTOR)
.where(FILM_ACTOR.FILM_ID.eq(97L))
))
)
.orderBy(FILM_ID)
.fetch();
C’est un peu moins efficace qu’une requête du formulaire précédent car il accède au FILM_ACTOR
table de deux sous-requêtes, même si une seule d’entre elles est corrélée. Utilisation de la valeur par défaut JSONB
émulation, la requête suivante est générée :
SELECT film.film_id, film.title
FROM film
WHERE (
SELECT coalesce(
jsonb_agg(jsonb_build_array(v0) ORDER BY t.v0),
jsonb_build_array()
)
FROM (
SELECT film_actor.actor_id AS v0
FROM film_actor
WHERE film_actor.film_id = film.film_id
) AS t
) = (
SELECT coalesce(
jsonb_agg(jsonb_build_array(v0) ORDER BY t.v0),
jsonb_build_array()
)
FROM (
SELECT film_actor.actor_id AS v0
FROM film_actor
WHERE film_actor.film_id = 97
) AS t
)
ORDER BY film.film_id
j’ai promis que non ORDER BY
clause était nécessaire pour MULTISET
, et cela est toujours vrai pour le code jOOQ. Cependant, dans les coulisses, jOOQ doit ordonner les tableaux JSON par leur contenu pour s’assurer que deux MULTISET
les valeurs sont les mêmes, quel que soit leur ordre.
Le résultat est les deux mêmes identifiants que le résultat précédent :
+-------+--------------+ |film_id|title | +-------+--------------+ | 97|BRIDE INTRIGUE| | 556|MALTESE HOPE | +-------+--------------+
Comparer MULTISET_AGG, à la place
Si vous préférez utiliser des jointures et GROUP BY
générer l’acteur du film MULTISET
, vous pouvez également le faire avec jOOQ. Cette fois, nous utilisons :
- Jointures implicites pour simplifier l’accès aux
FILM.TITLE
depuisFILM_ACTOR
- UN
MULTISET
prédicat dans leHAVING
clause, en utilisantMULTISET_AGG
Voici la version jOOQ :
ctx.select(FILM_ACTOR.FILM_ID, FILM_ACTOR.film().TITLE)
.from(FILM_ACTOR)
.groupBy(FILM_ACTOR.FILM_ID, FILM_ACTOR.film().TITLE)
.having(multisetAgg(FILM_ACTOR.ACTOR_ID).eq(multiset(
select(FILM_ACTOR.ACTOR_ID)
.from(FILM_ACTOR)
.where(FILM_ACTOR.FILM_ID.eq(97L))
)))
.orderBy(FILM_ACTOR.FILM_ID)
.fetch();
Le support SQL généré ressemble à ceci :
SELECT film_actor.film_id, alias_75379701.title
FROM film_actor
JOIN film AS alias_75379701
ON film_actor.film_id = alias_75379701.film_id
GROUP BY film_actor.film_id, alias_75379701.title
HAVING jsonb_agg(
jsonb_build_array(film_actor.actor_id) ORDER BY film_actor.actor_id
) = (
SELECT coalesce(
jsonb_agg(jsonb_build_array(v0) ORDER BY t.v0),
jsonb_build_array()
)
FROM (
SELECT film_actor.actor_id AS v0
FROM film_actor
WHERE film_actor.film_id = 97
) AS t
)
ORDER BY film_actor.film_id
Remarquez comment la jointure implicite est développée automatiquement, alors que la HAVING
le prédicat utilise à nouveau l’habituel JSONB
émulation pour MULTISET
et MULTISET_AGG
.
Alternatives
Dans les exemples ci-dessus, nous avons comparé MULTISET
expressions qui projettent des colonnes uniques, en d’autres termes, Result<Record1<Long>>
types de collection imbriqués. Rien ne vous empêche d’ajouter plus de colonnes à l’équation. jOOQ s’assurera toujours que votre type de requête vérifie et que le SQL généré est correct.
Une alternative à l’utilisation MULTISET
utiliserait ARRAY_AGG
et ARRAY
(maintenant il faut ORDER BY
explicitement, encore une fois). Avec jOOQ :
ctx.select(FILM_ACTOR.FILM_ID, FILM_ACTOR.film().TITLE)
.from(FILM_ACTOR)
.groupBy(FILM_ACTOR.FILM_ID, FILM_ACTOR.film().TITLE)
.having(arrayAgg(FILM_ACTOR.ACTOR_ID)
.orderBy(FILM_ACTOR.ACTOR_ID).eq(array(
select(FILM_ACTOR.ACTOR_ID)
.from(FILM_ACTOR)
.where(FILM_ACTOR.FILM_ID.eq(97L))
.orderBy(FILM_ACTOR.ACTOR_ID)
)))
.orderBy(FILM_ACTOR.FILM_ID)
.fetch();
Avec SQL :
SELECT film_actor.film_id, film.title
FROM film_actor
JOIN film
ON film_actor.film_id = film.film_id
GROUP BY film_actor.film_id, film.title
HAVING array_agg(film_actor.actor_id ORDER BY film_actor.actor_id) =
ARRAY (
SELECT film_actor.actor_id
FROM film_actor
WHERE film_actor.film_id = 97
ORDER BY film_actor.actor_id
)
ORDER BY film_actor.film_id