IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Développement SQL Server Discussion :

CTE récursive : Exclure une ligne précédemment sélectionnée


Sujet :

Développement SQL Server

  1. #1
    Expert confirmé
    Avatar de Kropernic
    Homme Profil pro
    Analyste / Programmeur / DBA
    Inscrit en
    Juillet 2006
    Messages
    3 932
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Belgique

    Informations professionnelles :
    Activité : Analyste / Programmeur / DBA
    Secteur : Distribution

    Informations forums :
    Inscription : Juillet 2006
    Messages : 3 932
    Points : 4 239
    Points
    4 239
    Par défaut CTE récursive : Exclure une ligne précédemment sélectionnée
    Hello,

    On m'a posé une colle ce matin. Au départ, je pensais que ça allait être simple mais en fait, en traitant le problème, pas tant que ça...

    Dans une table contenant des éléments d'un réseau (conduites de distribution d'eau par exemple), il y a notamment des colonnes IN_FID et OUT_FID qui contiennent les identifiants des éléments auxquels l'élément est connecté.

    Voici un exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
     
    WITH A (ID, IN_FID, OUT_FID) AS (  SELECT 1, 1234, 9876   UNION ALL
      SELECT 2, 2345, 8765   UNION ALL
      SELECT 3, 3545, 1234   UNION ALL
      SELECT 4, 6574, 3545 
    ),
    T (ID, IN_FID, OUT_FID, LVL) AS (
      SELECT  ID, IN_FID, OUT_FID, 1
      FROM    A
      WHERE   IN_FID = 1234
      UNION ALL
      SELECT  A.ID, A.IN_FID, A.OUT_FID, T.LVL + 1
      FROM    A 
                INNER JOIN T
    				ON  A.IN_FID <> 1234
    				AND	A.ID <> T.ID
    				AND (
    					A.IN_FID = T.IN_FID OR
    					A.IN_FID = T.OUT_FID OR
    					A.OUT_FID = T.IN_FID OR
    					A.OUT_FID = T.OUT_FID
    					)
    )
     
     
    SELECT * FROM T
    Particularité aussi, les connexions se font n'importe comment. Ce n'est pas toujours OUT_FID qui pointe vers IN_FID. Ca peut être l'inverse comme ça peut pointer vers la même colonne aussi (ça dépend du sens dans lequel l'utilisateur dessine l'élément du réseau qu'on m'a dit).

    Mais avec cette requête, on a bien sûr un cycle. Il faudrait en fait pouvoir exclure une ligne qui a déjà été prise dans T.

    Enfin c'est mon analyse du problème. Il y a peut-être moyen de mieux faire.

    Perso, je sèche... Auriez-vous une idée ?
    Kropernic

  2. #2
    Modérateur

    Profil pro
    dba
    Inscrit en
    Janvier 2010
    Messages
    5 643
    Détails du profil
    Informations personnelles :
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : dba

    Informations forums :
    Inscription : Janvier 2010
    Messages : 5 643
    Points : 13 092
    Points
    13 092
    Par défaut
    Bonjour,

    Pour éviter les cycles dans une requête récursive, je ne connais pas d'autres solution que de stocker le "chemin" afin de vérifier à chaque récursion si on n'est pas déjà passé.

    donc quelque chose comme ceci par exemple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
     
    WITH A (ID, IN_FID, OUT_FID) AS (  SELECT 1, 1234, 9876   UNION ALL
      SELECT 2, 2345, 8765   UNION ALL
      SELECT 3, 3545, 1234   UNION ALL
      SELECT 4, 6574, 3545 
    ),
    T (ID, IN_FID, OUT_FID, LVL, chemin) AS (
      SELECT  ID, IN_FID, OUT_FID, 1, CAST(CONCAT('[',ID, ']') AS VARCHAR(MAX))
      FROM    A
      WHERE   IN_FID = 1234
      UNION ALL
      SELECT  A.ID, A.IN_FID, A.OUT_FID, T.LVL + 1, CAST(CONCAT(T.chemin,'[',A.ID, ']') AS varchar(max))
      FROM    A 
                INNER JOIN T
    				ON  A.IN_FID <> 1234
    				AND	A.ID <> T.ID
    				AND (
    					A.IN_FID = T.IN_FID OR
    					A.IN_FID = T.OUT_FID OR
    					A.OUT_FID = T.IN_FID OR
    					A.OUT_FID = T.OUT_FID
    					)
    	WHERE t.chemin NOT LIKE CONCAT('%[', A.ID, ']%')
    )
     
     
    SELECT * FROM T

  3. #3
    Expert confirmé
    Avatar de Kropernic
    Homme Profil pro
    Analyste / Programmeur / DBA
    Inscrit en
    Juillet 2006
    Messages
    3 932
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Belgique

    Informations professionnelles :
    Activité : Analyste / Programmeur / DBA
    Secteur : Distribution

    Informations forums :
    Inscription : Juillet 2006
    Messages : 3 932
    Points : 4 239
    Points
    4 239
    Par défaut
    Nice !

    Je ne connaissais pas encore l'astuce du "chemin".

    Ca fait plaisir de "revoir les mêmes têtes" qu'à l'époque où je fréquentais ce forum plus régulièrement

    Un grand merci !
    Kropernic

  4. #4
    Expert confirmé
    Homme Profil pro
    Inscrit en
    Septembre 2006
    Messages
    2 937
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Septembre 2006
    Messages : 2 937
    Points : 4 358
    Points
    4 358
    Par défaut
    Autre méthode pour détecter un cycle :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    with data as (    select 1 as parent_id, 2 as child_id from dual
        union all
        select 2 as parent_id, 3 as child_id from dual
        union all
        select 3 as parent_id, 4 as child_id from dual
        union all
        select 3 as parent_id, 9 as child_id from dual
        union all
        select 4 as parent_id, 5 as child_id from dual
        union all
        select 5 as parent_id, 6 as child_id from dual
        union all
        select 6 as parent_id, 7 as child_id from dual
        union all
        select 7 as parent_id, 8 as child_id from dual
        union all
        select 8 as parent_id, 1 as child_id from dual
    ),
    cte(lvl, pid, cid, gcid, is_cycle) as (
        select 1 as lvl, d.parent_id, d.child_id, cd.child_id as gcid, 0 as is_cycle from data d
            join data cd on cd.parent_id = d.child_id
            where d.parent_id = 1
        union all
        select lvl+1, cd.parent_id, cd.child_id, cccd.child_id, case when cd.child_id = cccd.child_id then 1 else 0 end
        from cte c
            join data cd on c.cid = cd.parent_id
            join data ccd on c.gcid = ccd.parent_id
            join data cccd on ccd.child_id = cccd.parent_id
        where c.cid <> c.gcid
    )
    select * from cte ;
    LVL PID CID GCID IS_CYCLE
    1 1 2 3 0
    2 2 3 5 0
    3 3 9 7 0
    3 3 4 7 0
    4 4 5 1 0
    5 5 6 3 0
    6 6 7 5 0
    7 7 8 7 0
    8 8 1 1 1

    (méthode de D. Knuth pour détecter les cycles dans une liste chaînée)

  5. #5
    Modérateur
    Avatar de escartefigue
    Homme Profil pro
    bourreau
    Inscrit en
    Mars 2010
    Messages
    10 134
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Loir et Cher (Centre)

    Informations professionnelles :
    Activité : bourreau
    Secteur : Finance

    Informations forums :
    Inscription : Mars 2010
    Messages : 10 134
    Points : 38 555
    Points
    38 555
    Billets dans le blog
    9
    Par défaut
    Citation Envoyé par Kropernic Voir le message
    Nice !

    Je ne connaissais pas encore l'astuce du "chemin".

    Ca fait plaisir de "revoir les mêmes têtes" qu'à l'époque où je fréquentais ce forum plus régulièrement

    Un grand merci !
    Ce plaisir aurait du être plus démonstratif en attribuant un vote positif à la réponse fournie, surtout quand celle-ci est pertinente
    Ah la la cette ingratitude...

  6. #6
    Rédacteur

    Avatar de SQLpro
    Homme Profil pro
    Expert bases de données / SQL / MS SQL Server / Postgresql
    Inscrit en
    Mai 2002
    Messages
    21 768
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Expert bases de données / SQL / MS SQL Server / Postgresql
    Secteur : Conseil

    Informations forums :
    Inscription : Mai 2002
    Messages : 21 768
    Points : 52 571
    Points
    52 571
    Billets dans le blog
    5
    Par défaut
    Citation Envoyé par escartefigue Voir le message
    Ce plaisir aurait du être plus démonstratif en attribuant un vote positif à la réponse fournie, surtout quand celle-ci est pertinente
    Ah la la cette ingratitude...

    Notre ami Kropernic, que j'ai bien connu à Bruxelles est Belge ! Peut être ceci explique cela !!!
    Frédéric Brouard - SQLpro - ARCHITECTE DE DONNÉES - expert SGBDR et langage SQL
    Le site sur les SGBD relationnels et le langage SQL: http://sqlpro.developpez.com/
    Blog SQL, SQL Server, SGBDR : http://blog.developpez.com/sqlpro
    Expert Microsoft SQL Server - M.V.P. (Most valuable Professional) MS Corp.
    Entreprise SQL SPOT : modélisation, conseils, audit, optimisation, formation...
    * * * * * Expertise SQL Server : http://mssqlserver.fr/ * * * * *

  7. #7
    Modérateur

    Profil pro
    dba
    Inscrit en
    Janvier 2010
    Messages
    5 643
    Détails du profil
    Informations personnelles :
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : dba

    Informations forums :
    Inscription : Janvier 2010
    Messages : 5 643
    Points : 13 092
    Points
    13 092
    Par défaut
    Citation Envoyé par JeitEmgie Voir le message
    (méthode de D. Knuth pour détecter les cycles dans une liste chaînée)
    Je pense qu'il y a une erreur d'implémentation de la méthode en question (il n'y pas de recherche de longueur de cycle)

    Si vous supprimez la dernière ligne 8-->1 (plus de cycle), le résultat est faux
    Il est faux également si vous modifiez la même ligne pour reprendre en "milieu de chemin" (par exemple 8-->5)

    Il y a aussi probablement un erreur avec la requête que j'ai proposée : le choix des crochets est assez malheureux, car interprété par le LIKE comme caractère d'échappement.
    Il faudrait soit utiliser autre chose (comme des parenthèses par exemple), soit spécifier un autre caractère d’échappement pour le like ( LIKE ... ESCAPE '\').

  8. #8
    Expert confirmé
    Homme Profil pro
    Inscrit en
    Septembre 2006
    Messages
    2 937
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Septembre 2006
    Messages : 2 937
    Points : 4 358
    Points
    4 358
    Par défaut
    Non, mais j’aurais dû être plus clair sur le but de ce query qui n’est que de détecter la présence d’un cycle, et non de remplacer le query original.

    Normalement on met la condition du CASE dans un WHERE sur le CTE, et pas de résultat == pas de cycle.
    J’ai ajouté des champs inutiles dans le CTE pour illustrer comment fonctionne la technique originale de D. Knuth qui consiste à se balader avec la « petite-fille » (gcid == grand-child id) en montrant tous les résultats en cas de cycle.
    C’était juste une illustration d’une technique venant d’un raisonnement « procédural » en termes purement relationnels par opposition à « l’astuce » de la recherche dans une string contenant le chemin parcouru.

  9. #9
    Expert confirmé
    Avatar de Kropernic
    Homme Profil pro
    Analyste / Programmeur / DBA
    Inscrit en
    Juillet 2006
    Messages
    3 932
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Belgique

    Informations professionnelles :
    Activité : Analyste / Programmeur / DBA
    Secteur : Distribution

    Informations forums :
    Inscription : Juillet 2006
    Messages : 3 932
    Points : 4 239
    Points
    4 239
    Par défaut
    Je repasse voir l'implémentation de la détection de cycle et je vois qu'on parle de moi^^

    Citation Envoyé par escartefigue Voir le message
    Ce plaisir aurait du être plus démonstratif en attribuant un vote positif à la réponse fournie, surtout quand celle-ci est pertinente
    Ah la la cette ingratitude...
    C'est en effet un oubli et c'est corrigé.

    Citation Envoyé par SQLpro Voir le message
    Notre ami Kropernic, que j'ai bien connu à Bruxelles est Belge ! Peut être ceci explique cela !!!
    Kropernic

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [WD23] Retour sur la ligne précédemment sélectionnée dans une table
    Par bernisch dans le forum WinDev
    Réponses: 13
    Dernier message: 27/09/2018, 13h24
  2. DataGridView: tester si une ligne est sélectionnée
    Par Jinkas dans le forum Windows Forms
    Réponses: 9
    Dernier message: 08/07/2013, 18h30
  3. Réponses: 2
    Dernier message: 14/08/2011, 22h39
  4. Tri JTable : exclure une ligne
    Par SheikYerbouti dans le forum Composants
    Réponses: 26
    Dernier message: 12/10/2010, 16h37
  5. Réponses: 13
    Dernier message: 12/07/2005, 10h14

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo