Lien virtuel sur un objet récursif / réflexif

Bonjour,

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;

Version=4.0.P25
BuiltOn=2021-03-24 00:20 (revision 0ab578dff484419fee9205b41be7f805473ce9a4)

Merci d’avance pour votre aide !
Emmanuelle

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

1 Like

Bonjour Emmanuel, François,

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  ...)"
2 Likes

Merci Bruno pour ce partage.

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

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 !

1 Like

This topic was automatically closed 60 minutes after the last reply. New replies are no longer allowed.