6
votes

Alternatives à une CTE récursive à l'intérieur d'une sous-requête corrélée «existante»?

J'ai une situation où j'ai besoin de pouvoir voir si une personne donnée est dans une hiérarchie utilisateur / gestionnaire.

Je dois pouvoir le faire pour un ensemble d'utilisateurs contre un ensemble de règles (DON 't vous soucier de cela, mais juste pour lui donner un peu de contexte)

Idéalement, j'aimerais utiliser un CTE récursif dans une sous-requête corrélée sur la clause WHERE.

Mais cela pose beaucoup de problèmes ..

  1. Je ne pense pas que vous puissiez utiliser un CTE dans une sous-requête du tout.
  2. Je suis sur SQL 2005 avec le mode de compatibilité 80 - donc je ne peux donc pas utiliser cross s'appliquer ... donc pas de paramètres de colonne dans UDF pour moi: - (

    Je pense qu'est-ce que j'essaie de faire est: xxx

    est là quand même de faire la définition d'ancrage dynamique en mode de compatibilité 80? Ou une approche alternative?


5 commentaires

Et comment la partie d'ancrage ne suffit-elle pas suffisante pour vous dans le prédicat existant? Je ne peux pas voir le point de recouvrer davantage de l'ancre si la partie récursive n'est pas corrélée à la requête extérieure, mais seule la partie d'ancrage est. Qu'est-ce que je rate?


Salut @andriym merci de jeter un coup d'œil. Le problème est que la déclaration "avec" ne peut pas être à l'intérieur d'une sous-requête du tout, je ne peux donc pas lier à une requête extérieure (mon code ci-dessus n'est que pseudo-code - désolé je devrais de mention que). La bonne nouvelle est que je pense que je suis sur une solution, je le posterai une fois que j'ai fini.


Oui, je pense avoir ce droit la première fois. Vous voulez quelque chose comme une requête récursive à l'intérieur Existe , mais avec , utilisé pour la construction de requêtes récursives, ne peut pas être utilisé dans les sous-requêtes, et vous posez donc cette question. Est-ce correct? Supposons que vous ayez autorisé à avoir avec dans les sous-requêtes et ont donc pu construire une sous-requête récursive corrélée. Pourquoi auriez-vous besoin d'avoir une récursion si ce n'est que la partie d'ancrage qui est corrélée à la requête extérieure? Si l'ancrage a renvoyé des lignes, cela suffirait au prédicat existant pour retourner true , et similaire pour false .


Ainsi, la ligne de fond est que vous n'avez probablement pas besoin de récursions et, par conséquent Vous n'avez pas besoin d'utiliser avec . Cela signifie, à son tour, vous pouvez avoir votre sous-requête corrélée sous la forme d'un simple Sélectionnez [UserID], [ManagementId] à partir de [Utilisateurs] où [Managerid] = [Règles]. [Publierdedbyuserid] . (Ou SELECT * à partir de [Utilisateurs] où [Managerid] = [Règles]. [Diffeddedbyuserid] , car la liste des colonnes réelle n'a pas d'importance pour le prédicat existant.)


Merci pour les commentaires @andriym, je vois maintenant ce que vous voulez dire. Mon exemple essayait de reproduire une question que j'ai avec une plus grande requête. Regardez-le maintenant, je ne pense pas que mon exemple est très bon. Ce que j'essaie d'atteindre est de déterminer si un utilisateur est inférieur à un gestionnaire dans une hiérarchie donnée ... Je vais essayer de mettre à jour ma question avec un exemple plus précis. Je dois admettre que je trouve cela délicat d'expliquer ce que je suis après, car le problème est assez épique. Merci encore pour l'aide, je vais essayer de le mettre à jour bientôt.


3 Réponses :


1
votes

Combien de niveaux de hiérarchie peut-il y avoir dans la table code> code>? Je m'attendrais à ce qu'il soit raisonnablement bas. Je me demande s'il est assez faible d'essayer plusieurs tests code> imbriqués code>, comme celui-ci:

… /* your main query here */
WHERE …
  AND EXISTS (
    SELECT *
    FROM [Users] u1
    WHERE [UserID] = @UserID
      AND (
        [ManagerId] = [Rules].[RuleAddedByUserId]
        OR EXISTS (
          SELECT *
          FROM [Users] u2
          WHERE [UserID] = u1.[ManagerID]
            AND (
              [ManagerId] = [Rules].[RuleAddedByUserId]
              OR EXISTS (
                SELECT *
                FROM [Users] u3
                WHERE [UserID] = u2.[ManagerID]
                  AND (
                    [ManagerId] = [Rules].[RuleAddedByUserId]
                    OR EXISTS ( … /* and so on, until you've covered
                                     all possible levels */
                    )
                  )
              )
            )
        )
      )
  )


1 commentaires

Merci pour l'aide, malheureusement, je ne peux pas garantir le niveau de nidification et je dois le faire travailler dans n'importe quel scénario de nidification, mais merci pour le poste.



3
votes

Une façon de le faire serait de créer un CTE récursif qui a, pour chaque utilisateur, une ligne pour chaque ancêtre de cet utilisateur dans l'arbre. Ensuite, vous pouvez utiliser le CTE pour filtrer pour les ancêtres. Par exemple, avec cet arbre:

CREATE TABLE Users(
    UserId int NOT NULL PRIMARY KEY,
    Name nvarchar(25),
    ManagerId int
);
GO

INSERT INTO Users (UserId, Name, ManagerId)
SELECT 1, 'Bob', NULL UNION ALL
SELECT 2, 'Steve', 1 UNION ALL
SELECT 3, 'Chris', 2 UNION ALL
SELECT 4, 'Alice', 1 UNION ALL
SELECT 5, 'Roger', 4 UNION ALL
SELECT 6, 'Tony', 5;
GO

WITH all_ancestors AS (
    SELECT
        u.UserId,
        u.Name,
        u.ManagerId AS AncestorId,
        1 AS level
    FROM
        Users AS u
    UNION ALL
    SELECT
        alla.UserId,
        alla.Name,
        u.ManagerId AS AncestorId,
        alla.level + 1
    FROM
            all_ancestors AS alla
        INNER JOIN
            Users AS u
        ON
            alla.AncestorId = u.UserId
)
SELECT
    u.*
FROM
        Users AS u
    INNER JOIN
        all_ancestors AS a
    ON
        u.UserId = a.UserId
WHERE
    a.AncestorId = 4; -- Alice
GO

DROP TABLE Users;
GO


2 commentaires

Merci pour l'aide Cheran, ce qui vous intéresse un peu l'approche que j'ai prise. +1 De moi, je posterai ma solution dans quelques minutes.


Je cherchais quelque chose de similaire, mais mes pensées ont finalement pris un tour différent et je n'étais pas satisfait de ce que j'ai trouvé alors. Votre solution est vraiment belle.



0
votes

Je suis arrivé là-bas finalement! Merci les gars pour l'aide.

Voici un extrait de la SQL que je travaille sur. P>

Je devais juste changer ma pensée, au lieu de voir si un utilisateur existe sous un gestionnaire dans le lieu où clause. J'avais besoin de considérer le CTE un pré-filtre et de construire tous les détails dont j'avais besoin avant la main, puis faites mon filtrage normal après les déclarations existantes (NB que je n'ai pas inclus celles de la brièveté). P>

RulesUserHierarchy(UserId, ManagerId, PushRuleId, OnlyForSubOrdinates) -- Gets only subordinates for rules created by managers. And all users for those created by admin.
AS
(
    --Anchor Definition
    SELECT
         [Users].[UserId]
        ,[Users].[ManagerId]
        ,[RulesAnchor].[PushRuleId]
        ,[RulesAnchor].[OnlyForSubOrdinates]
    FROM [Users]
        CROSS JOIN [Rules] [RulesAnchor] --assume every user is doing every rule at this point (because the recursive statement has to be the first statement), we'll filter later.
    WHERE (([OnlyForSubOrdinates]) = 0 OR ([OnlyForSubOrdinates] = 1 AND [UserId] = [RulesAnchor].[AddedByUserId]))
    UNION ALL

    --Recursive Member definiation
    SELECT
         [Users].[UserId]
        ,[Users].[ManagerId]
        ,[RulesUserHierarchy].[PushRuleId]
        ,[RulesUserHierarchy].[OnlyForSubOrdinates]
    FROM [Users]
        INNER JOIN [RulesUserHierarchy]
            ON [Users].[ManagerId] = [RulesUserHierarchy].[UserId] --recursive hook 
            AND [RulesUserHierarchy].[OnlyForSubOrdinates] = 1 -- no point recursing if it's for everyone, as the anchor will pull back everything for us.
    WHERE [Users].[UserId] <> [Users].[ManagerId] --don't recurse if the anchor definition matches itself (to avoid an infinate loop).
)       
-- simple statement to test recursion above, will be filtering the inclusions here (e.g. the other mega exists statements)
SELECT [UserId], [ManagerId], [PushRuleId], [OnlyForSubOrdinates] FROM [RulesUserHierarchy]


0 commentaires