2009-06-19 5 views
2

J'ai besoin d'un peu d'aide pour construire une requête qui me permettra de filtrer les données suivantes.Filtrer les enfants dans une table avec parentid

Table: MyTree 
Id ParentId Visible 
===================== 
1 null  0 
2 1   1 
3 2   1 
4 3   1 
5 null  1 
6 5   1 

Je pense que le résultat suivant de la requête:

Id ParentId Visible 
===================== 
5 null  1 
6 5   1 

C'est, tous les enfants du nœud caché ne doivent pas être renvoyés. De plus, la profondeur d'une hiérarchie n'est pas limitée. Maintenant, ne répondez pas "juste mettre 2, 3 & 4 à visible = 0" pour des raisons non-obviuos ce n'est pas possible ... Comme je suis en train de réparer un horrible "système hérité".

Je pensais à quelque chose comme:

SELECT * 
FROM MyTree m1 
JOIN MyTree m2 ON m1.ParentId = m2.Id 
WHERE m1.Visible = 1 
AND (m1.ParentId IS NULL OR m2.Id IS NOT NULL) 

Désolé pour les erreurs syntaxiques

Mais ce ne filtre que le premier niveau, non? J'espère que vous pouvez aider.

Modifier: Terminé le titre, whoops. Le serveur est un nouveau serveur MSSQL 2008 flambant neuf mais la base de données fonctionne en mode de compatibilité 2000.

+1

Quelle version de SQL Server? –

+0

@JohannesH - Je ne suis pas sûr de comprendre ce que vous essayez d'accomplir. –

+0

La réponse que je pense dépendra plutôt si vous êtes sur SQL Server 2005+ (qui peut faire des CTE récursifs) ou SQL Server 2000 (qui ne peut pas) – AakashM

Répondre

2

Je suis d'accord avec @ accent de Quassnoi sur CTEs récursive (dans SQL Server 2005 ou plus tard) mais je pense que la logique est différent de répondre à la question initiale:

WITH visall(id, parentid, visible) AS 
    (SELECT id, parentid, visible 
    FROM mytree 
    WHERE parentid IS NULL 
     UNION ALL 
    SELECT m.id, m.parentid, m.visible & visall.visible AS visible 
    FROM visall 
    JOIN mytree m 
     ON m.parentid = visall.id 
    ) 
SELECT * 
FROM visall 
WHERE visall.visible = 1 

Une façon sans doute plus optimisé pour exprimer la même logique devrait être d'avoir les contrôles visibles dans la WHERE autant que possible - arrêter récursion le long invisible « sous-arbres » dès que possible. À savoir:

WITH visall(id, parentid, visible) AS 
    (SELECT id, parentid, visible 
    FROM mytree 
    WHERE parentid IS NULL AND visible = 1 
     UNION ALL 
    SELECT m.id, m.parentid, m.visible 
    FROM visall 
    JOIN mytree m 
     ON m.parentid = visall.id 
    WHERE m.visible = 1 
    ) 
SELECT * 
FROM visall 

Comme d'habitude avec les problèmes de performance, l'analyse comparative des deux versions sur des données réalistes est nécessaire de décider en toute sérénité (il contribue également à vérifier qu'ils font en effet produire des résultats identiques ;-) - comme les optimiseurs de moteurs DB parfois faire des choses étranges pour des raisons étranges ;-).

+0

@Alex: Je pense que vous êtes le plus proche de ce que je recherchais. Donc accepté votre réponse. – JohannesH

+0

Votre solution traversera les nœuds invisibles, puis les filtrera. N'est-il pas préférable de filtrer les noeuds invisibles directement dans le CTE, pour sauver quelques étapes de récursivité? – Quassnoi

+0

@JohannesH, heureux d'aider. Oui, @Quassnoi, je suis sûr que mon code peut être amélioré (déplacez les contrôles visibles à WHERE au lieu de SELECT dans les deux branches de l'aide shd récursif - lemme edit pour ajouter du code optimisé de cette façon), mais spécial-casing "id = 5 "n'est pas ce que le demandeur avait en tête, juste un exemple. –

2

En SQL Server 2005+:

WITH q (id, parentid, visible) AS 
     (
     SELECT id, parentid, visible 
     FROM mytree 
     WHERE id = 5 
     UNION ALL 
     SELECT m.id, m.parentid, m.visible 
     FROM q 
     JOIN mytree m 
     ON  m.parentid = q.id 
     WHERE q.visible = 1 
     ) 
SELECT * 
FROM q 
-1

Je ne pense pas que ce que vous avez besoin est possible à partir d'une seule requête. Cela ressemble plus à quelque chose à faire du code et encore il faudra plusieurs requêtes à DB.

Si vous avez vraiment besoin de le faire à partir de SQL, je pense que votre meilleur pari serait d'utiliser un curseur et de construire une table avec des ID cachés. Si les données ne changent pas souvent, vous pouvez conserver cette table 'temporaire' comme une sorte de cache.

Modifier: Je reconnais mon erreur (pour SQL 2005) et aussi appris quelque chose de nouveau aujourd'hui :)

+0

Je suis entièrement d'accord que cela devrait être fait dans le code de l'application, mais malheureusement, je n'ai pas la source. Suce de maintenir l'héritage. ;) – JohannesH

1

Je pense que Quassnoi était proche de ce que le questionneur veut, mais pas tout à fait. Je pense que c'est ce que le questionneur recherche (SQL Server 2005+):

WITH q (id) AS 
     (
     SELECT id 
     FROM mytree 
     WHERE parentid is null and visible=1 
     UNION ALL 
     SELECT m.id 
     FROM q 
     JOIN mytree m 
     ON  m.parentid = q.id 
     WHERE q.visible = 1 
     ) 
SELECT * 
FROM q 

expressions de table commune sont parfaits pour ce genre de travail.

Questions connexes