J’ai dans mon application des Domaines qui peuvent avoir des Domaines enfants de façon récursive.
Un Domaine peut être lié à une ou plusieurs applications (relation NN).
Je voudrais, pour chaque domaine, faire un lien virtuel qui liste toutes les applications de TOUS ses domaines enfants (pas que celui du dessous). J’ai tenté de faire une requête SQL récursive mais quand je la teste dans DB access j’ai l’erreur suivante
Error: A result was returned when none was expected.
Ma requête
WITH RECURSIVE child_doms AS (
SELECT
row_id,
rci_dom_dom_id
FROM
rci_domain
WHERE
row_id = 1
UNION
SELECT
dom.row_id,
dom.rci_dom_dom_id
FROM
rci_domain dom
INNER JOIN child_doms cdom ON cdom.row_id= dom.rci_dom_dom_id
) SELECT * FROM child_doms;
L’accès DB Access permet d’exécuter des select simples ou des mises à jour directes (insert, update, delete). Il ne doit pas gérer ce genre de syntaxe spécifique à PostgreSQL.
Pour tester ta requête, il faut passer par un autre accès, via psql ou autre client PostgreSQL, ou alors faire commencer ta requête par un select * from (...sous requête...).
Au delà de ça, il faudra voir comment cette search-spec peut être placée dans un lien virtuel sous forme de “exists” ou “in” (intégrée au where du select des objets fils).
nous avons déjà rencontré cette problématique de récursivité et nous l’avons résolue ainsi (attention, requête construite sur un SGBD MySQL 5.6) → Si vous utilisez un autre SGBD qui n’implémente pas les mécanismes FIND_IN_SET / GROUP_CONCAT, ce n’est peut-être pas transposable (et adaptable pour aller chercher une liste d’applications liées en lieu et place de la liste hiérarchique des domaines).
SELECT t.* FROM `bcsi_spec_busn_domain` t
WHERE FIND_IN_SET(t.row_id, (
SELECT GROUP_CONCAT(NodeId SEPARATOR ',')
FROM (
SELECT @RootId NodeId
UNION
SELECT @RootId := (
SELECT GROUP_CONCAT(`row_id` SEPARATOR ',')
FROM `bcsi_spec_busn_domain`
WHERE FIND_IN_SET(`wkf_busnspecdomain_busnspecdomain_id`, @RootId)
) NodeId
FROM `bcsi_spec_busn_domain` JOIN (SELECT @RootId := 28) temp1
) temp2
));
La table de relation incluant la relation réflexive est bcsi_spec_busn_domain.
La fk de la relation est wkf_busnspecdomain_busnspecdomain_id.
Le point de départ du scan récursif est initialisé dans la variable @RootId := 28 (28 est le row_id de bcsi_spec_busn_domain à partir duquel nous parcourons la relation).
Voici l’EXPLAIN de cette requête (je ne suis pas DBA donc mon expertise est relativement limitée mais il me semble que c’est optimisé “à l’os”) :
Autre point, la même construction nous permet de border la saisie de cette relation pour éviter les boucles infinies via l’ajout d’un searchSpec dans le hook initRefSelect de l’objet concerné:
searchSpec = "t.row_id not in (...liste des id générée par le SELECT GROUP_CONCAT(`row_id` SEPARATOR ',') ... ) temp2 ...)"
Pour éviter les boucles infinies dans ce genre d’arbre récursif (et mettre le thread à 100% de CPU, rendre tomcat inaccessible…), avoir codé le initRefSelect est une bonne idée mais insuffisante si on importe des données sans passer par la UI : il y a un risque de création de cycle.
Il est nécessaire de coder un test bloquant de détection de cycle : recherche si un objet parent est égal à lui-même dans un hook postValidate ou preUpdate.
Exemple pour les groupes Simplicité qui ont une notion d’héritage père/fils. L’algo ne dépend pas de la base (simple boucle de select de l’id père jusqu’à la racine) :
public List<String> postValidate() {
List<String> msgs = new ArrayList<>();
// Infinite loop in inheritance ?
ObjectField parentId = getField("grp_parent_id");
if (!parentId.isEmpty() && parentId.hasChanged()) {
String rowId = getRowId();
String id = parentId.getValue();
while (id.length()>0) {
if (id.equals(rowId)) {
msgs = Message.concat(msgs, Message.formatSimpleError("Inifinite loop detected in group inheritance."));
id = "";
}
else {
id = getGrant().simpleQuery("select grp_parent_id from m_group where row_id="+id);
}
}
}
return msgs;
}
Merci beaucoup pour vos retours, en encapsulant mon RECURSIVE dans un SELECT j’ai pu tester ma requête dans le DB Access.
J’ai créé le lien virtuel dont j’avais besoin avec ma requête récursive et une clause IN, ça fonctionne parfaitement !