2017-01-24 5 views
3

J'ai une table qui contient des chemins de dossiers. J'ai besoin de trouver tous les "trous" entre ces dossiers dans la hiérarchie. Je veux dire que, si la table contient ces 3 dossiers:SQL: Recherche des dossiers de hiérarchie manquants (chemins) dans une table

'A' 
'A\B\C' 
'A\B\C\D\E\F\G' 

Je dois trouver les dossiers manquants suivants dans la hiérarchie:

'A\B' 
'A\B\C\D' 
'A\B\C\D\E' 
'A\B\C\D\E\F' 

Ce tableau contient plus de 250 000 enregistrements des dossiers , donc nous cherchons le moyen le plus efficace, sinon le script sera bloqué pendant longtemps, temps que nous n'avons pas.

Commentaire: Je n'ai pas la liste de tous les dossiers. Ce que j'ai sont les dossiers "root" et les dossiers "leafs" dont j'ai besoin pour trouver les "trous" entre eux dans la hiérarchie.

Deuxième commentaire: La boîte de table contient plus d'une hiérarchie et nous avons besoin de trouver les « lacunes » dans toutes les hiérarchies. D'ailleurs, il existe 2 colonnes int: "DirID" et "BaseDirID". La colonne "DirID" est la colonne id dans notre tableau. Le "BaseDirID" contient l'ID du premier dossier dans la hiérarchie. Ainsi, tous les dossiers (chemins) de la même hiérarchie partagent la même valeur dans cette colonne. Les données d'échantillon par exemple:

Example sample data

DirID BaseDirID DisplayPath 
1 1 'A' 
2 1 'A\B\C' 
3 1 'A\B\C\D\E' 
4 4 'U' 
5 4 'U\V\W' 
6 4 'U\V\W\X\Y' 

Nous avons donc besoin de trouver les données suivantes:

Expected Results

BaseDirID DisplayPath 
1 'A\B' 
1 'A\B\C\D' 
4 'U\V' 
4 'U\V\W\X' 

Merci à l'avance.

+0

Ceci est quelque chose que je ferais en dehors de sql. tout langage de programmation fera probablement un meilleur travail que t-sql dans ce cas. –

Répondre

2

Voici une approche à l'aide Recursive CTE et la fonction string fendu

;WITH existing_hierachies 
    AS (SELECT DirID, 
       BaseDirID, 
       DisplayPath 
     FROM (VALUES (1,1,'A'), 
         (2,1,'A\B\C'), 
         (3,1,'A\B\C\D\E'), 
         (4,4,'U'), 
         (5,4,'U\V\W'), 
         (6,4,'U\V\W\X\Y')) tc (DirID, BaseDirID, DisplayPath)), 
    folders_list 
    AS (SELECT ItemNumber, 
       item fol, 
       BaseDirID 
     FROM (SELECT row_number()over(partition by BaseDirID order by Len(DisplayPath) DESC)rn,* 
       FROM existing_hierachies) a 
       CROSS apply dbo.[Delimitedsplit8k](DisplayPath, '\') 
       Where Rn = 1), 
    rec_cte 
    AS (SELECT *, 
       Cast(fol AS VARCHAR(4000))AS hierar 
     FROM folders_list 
     WHERE ItemNumber = 1 
     UNION ALL 
     SELECT d.*, 
       Cast(rc.hierar + '\' + d.fol AS VARCHAR(4000)) 
     FROM rec_cte rc 
       JOIN folders_list d 
        ON rc.BaseDirID = d.BaseDirID 
        AND d.ItemNumber = rc.ItemNumber + 1) 
SELECT rc.BaseDirID, 
     rc.hierar AS Missing_Hierarchies 
FROM rec_cte rc 
WHERE NOT EXISTS (SELECT 1 
        FROM existing_hierachies eh 
        WHERE eh.BaseDirID = rc.BaseDirID 
        AND eh.DisplayPath = rc.hierar) 
Order by rc.BaseDirID 

Résultat:

+-----------+---------------------+ 
| BaseDirID | Missing_Hierarchies | 
+-----------+---------------------+ 
|   1 | A\B     | 
|   1 | A\B\C\D    | 
|   4 | U\V     | 
|   4 | U\V\W\X    | 
+-----------+---------------------+ 

Éclate une chaîne code de fonction

CREATE FUNCTION [dbo].[DelimitedSplit8K] 
     (@pString VARCHAR(8000), @pDelimiter CHAR(1)) 
RETURNS TABLE WITH SCHEMABINDING AS 
RETURN 
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000... 
    -- enough to cover NVARCHAR(4000) 
    WITH E1(N) AS (
       SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
       SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
       SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 
       ),       --10E+1 or 10 rows 
     E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows 
     E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max 
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front 
        -- for both a performance gain and prevention of accidental "overruns" 
       SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4 
       ), 
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter) 
       SELECT 1 UNION ALL 
       SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter 
       ), 
cteLen(N1,L1) AS(--==== Return start and length (for use in substring) 
       SELECT s.N1, 
         ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000) 
        FROM cteStart s 
       ) 
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found. 
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1), 
     Item  = SUBSTRING(@pString, l.N1, l.L1) 
    FROM cteLen l 
; 
GO 

Renvoyé de http://www.sqlservercentral.com/articles/Tally+Table/72993/

+0

Je n'ai pas de liste de dossiers. Ce que j'ai sont les dossiers "root" et les dossiers "leafs" dont j'ai besoin pour trouver les "trous" entre eux dans la hiérarchie. –

+0

@NurielZrubavely - mis à jour ma réponse –

+0

Merci pour votre solution. De ce que j'ai vu son fonctionne seulement pour une hiérarchie, mais si notre table contient plus d'une hiérarchie, elle ne trouvera pas les "lacunes" pour les autres hiérarchies. Par exemple, si la table contient les chemins suivants: 'A' 'A \ B \ C' 'X' 'X \ Y \ Z' La solution actuelle ne nous reviendra que le premier " gap ": 'A \ B' mais pas le deuxième écart: 'X \ Y' Pouvez-vous s'il vous plaît suggérer une solution pour ce cas aussi? Merci encore, Nuriel –