Créer une hiérarchie de plusieurs niveaux où chaque nœud a un nombre aléatoire d'enfants

16

J'ai besoin de créer des données de test qui impliquent une hiérarchie. Je pourrais le rendre facile et faire quelques CROSS JOINs, mais cela me donnerait une structure complètement uniforme / sans aucune variation. Cela semble non seulement ennuyeux, mais le manque de variation dans les données de test masque parfois des problèmes qui seraient autrement découverts. Donc, je veux générer une hiérarchie non uniforme qui suit ces règles:

  • 3 niveaux de profondeur
    • Le niveau 1 est aléatoire de 5 à 20 nœuds
    • Le niveau 2 est de 1 à 10 nœuds, aléatoire pour chaque nœud du niveau 1
    • Le niveau 3 est de 1 à 5 nœuds, aléatoire pour chaque nœud du niveau 2
  • Toutes les branches auront 3 niveaux de profondeur. L'uniformité en profondeur est acceptable à ce stade.
  • Il peut y avoir un chevauchement dans les noms des nœuds enfants à un niveau donné (c'est-à-dire que les noms des nœuds enfants n'ont pas besoin d'être uniques sur tous les nœuds du même niveau).
  • Le terme "aléatoire" est défini ici comme étant pseudo-aléatoire, et non pas uniquement aléatoire. Cela doit être mentionné car le terme "aléatoire" est souvent utilisé pour signifier "un ordre aléatoire d'un ensemble donné qui ne produit pas de doublons". J'accepte que random = random et si le nombre d'enfants pour chaque nœud du niveau 1 n'est que de 4, 7 et 8, même sur 20 nœuds au niveau 1 qui a une propagation potentielle de 1 à 10 enfants pour chacun de ces nœuds, alors c'est très bien, parce que c'est ce qui est aléatoire.
  • Même si cela peut être fait assez facilement avec des WHILEboucles imbriquées , la préférence est de trouver une approche basée sur un ensemble. De manière générale, la génération de données de test n'a pas les exigences d'efficacité que le code de production aurait, mais la recherche d'une approche basée sur un ensemble sera probablement plus éducative et aidera à l'avenir à trouver des approches basées sur un ensemble de problèmes. Les WHILEboucles ne sont donc pas exclues, mais ne peuvent être utilisées que si aucune approche basée sur un ensemble n'est possible.
  • Basé sur un ensemble = idéalement une seule requête, indépendamment des CTE, APPLY, etc. L'utilisation d'une table de nombres existante ou en ligne est donc très bien. L'utilisation d'une approche WHILE / CURSOR / procédurale ne fonctionnera pas. Je suppose que le transfert de portions des données dans des tables temporaires ou des variables de table est correct, tant que les opérations sont toutes basées sur un ensemble, pas de boucles. Cependant, cela étant dit, une approche à requête unique sera probablement préférée à plusieurs requêtes, à moins qu'il ne puisse être démontré que l'approche à requêtes multiples est en fait meilleure. Veuillez également garder à l'esprit que ce qui constitue "mieux" est généralement subjectif ;-). Veuillez également garder à l'esprit que l'utilisation de "généralement" dans la phrase précédente est également subjective.
  • N'importe quelle version et édition de SQL Server (2005 et plus récent, je suppose) fera l'affaire.
  • Uniquement T-SQL pur: rien de tout ça idiot SQLCLR !! Au moins en termes de génération des données. La création des répertoires et des fichiers se fera à l'aide de SQLCLR. Mais ici, je me concentre uniquement sur la génération des valeurs de ce qu'il faut créer.
  • Les TVF multi-instructions T-SQL sont considérées comme procédurales et non basées sur un ensemble, même si à l'extérieur elles masquent l'approche procédurale dans un ensemble. Il y a des moments où cela est tout à fait approprié. Ce n'est pas une de ces fois. Dans le même ordre d'idées, les fonctions scalaires T-SQL ne sont pas non plus autorisées, non seulement parce qu'elles sont également procédurales, mais l'Optimiseur de requête met parfois en cache leur valeur et la répète de sorte que la sortie ne soit pas comme prévu.
  • Les TVF T-SQL Inline (aka iTVF) sont okey-dokey car ils sont basés sur des ensembles, et sont en fait les mêmes que l'utilisation [ CROSS | OUTER ] APPLY, ce qui a été indiqué ci-dessus comme étant ok.
  • Les exécutions répétées de la ou des requêtes devraient produire des résultats essentiellement différents de l'exécution précédente.
  • Mise à jour de clarification 1: le jeu de résultats final doit être exprimé comme ayant une ligne pour chaque nœud distinct du niveau 3, avec le chemin complet commençant au niveau 1. Cela signifie que les valeurs Level1 et Level2 se répéteront nécessairement sur une ou plusieurs lignes, sauf dans le cas où il n'y a qu'un seul nœud Level2 contenant uniquement un seul nœud Level3.
  • Mise à jour de clarification 2: Il y a une préférence très forte pour chaque nœud ayant un nom ou une étiquette, et pas seulement un numéro. Cela permettra aux données de test résultantes d'être plus significatives et réalistes.

Je ne sais pas si ces informations supplémentaires sont importantes, mais juste au cas où cela aiderait à avoir un certain contexte, les données du test se rapportent à ma réponse à cette question:

Importer des fichiers XML dans SQL Server 2012

Bien que cela ne soit pas pertinent à ce stade, l'objectif final de la génération de cette hiérarchie est de créer une structure de répertoires pour tester les méthodes récursives du système de fichiers. Les niveaux 1 et 2 seront des répertoires et le niveau 3 finira par être le nom du fichier. J'ai cherché autour (ici et via les Google) et je n'ai trouvé qu'une seule référence pour générer une hiérarchie aléatoire:

Linux: créer une hiérarchie aléatoire de répertoires / fichiers

Cette question (sur StackOverflow) est en fait assez proche en termes de résultat souhaité car cela cherche également à créer une structure de répertoires pour les tests. Mais cette question (et les réponses) se concentrent sur les scripts shell Linux / Unix et pas tellement sur le monde basé sur les ensembles dans lequel nous vivons.

Maintenant, je sais comment générer des données aléatoires, et je le fais déjà pour créer le contenu des fichiers afin qu'ils puissent également afficher des variations. La partie délicate ici est que le nombre d'éléments dans chaque ensemble est aléatoire, pas un champ particulier. Et , le nombre d'éléments au sein de chaque nœud doit être aléatoire à partir d'autres nœuds aux mêmes niveaux.

Exemple de hiérarchie

     Level 1
              Level 3
|---- A
|     |-- 1
|     |   |--- I
|     |
|     |-- 2
|         |--- III
|         |--- VI
|         |--- VII
|         |--- IX
|
|---- B
|     |-- 87
|         |--- AAA
|         |--- DDD
|
|---- C
      |-- ASDF
      |   |--- 11
      |   |--- 22
      |   |--- 33
      |
      |-- QWERTY
      |   |--- beft
      |
      |-- ROYGBP
          |--- Poi
          |--- Moi
          |--- Soy
          |--- Joy
          |--- Roy

Exemple d'ensemble de résultats décrivant la hiérarchie ci-dessus

Level 1    Level 2    Level 3
A          1          I
A          2          III
A          2          VI
A          2          VII
A          2          IX
B          87         AAA
B          87         DDD
C          ASDF       11
C          ASDF       22
C          ASDF       33
C          QWERTY     beft
C          ROYGBP     Poi
C          ROYGBP     Moi
C          ROYGBP     Soy
C          ROYGBP     Joy
C          ROYGBP     Roy
Solomon Rutzky
la source

Réponses:

9

( Remarque de l'OP: la solution préférée est le 4ème / dernier bloc de code)

XML me semble être le choix évident de la structure de données à utiliser ici.

with N as
(
  select T.N
  from (values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),
              (12),(13),(14),(15),(16),(17),(18),(19),(20)) as T(N)
)

select top(5 + abs(checksum(newid())) % 15)
  N1.N as '@Value',
  (
  select top(1 + abs(checksum(newid())) % 10)
    N2.N as '@Value',
    (
    select top(1 + abs(checksum(newid())) % 5)
      N3.N as '@Value'
    from N as N3
    where N2.N > 0
    for xml path('Level3'), type
    )
  from N as N2
  where N1.N > 0
  for xml path('Level2'), type
  )
from N as N1
for xml path('Level1'), root('Root');

L'astuce pour que SQL Server utilise des valeurs différentes top()pour chaque nœud est de corréler les sous-requêtes. N1.N > 0et N2.N > 0.

Aplatir le XML:

declare @X xml;

with N as
(
  select T.N
  from (values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),
              (12),(13),(14),(15),(16),(17),(18),(19),(20)) as T(N)
)
select @X  = (
             select top(5 + abs(checksum(newid())) % 15)
               N1.N as '@Value',
               (
               select top(1 + abs(checksum(newid())) % 10)
                 N2.N as '@Value',
                 (
                 select top(1 + abs(checksum(newid())) % 5)
                   N3.N as '@Value'
                 from N as N3
                 where N2.N > 0
                 for xml path('Level3'), type
                 )
               from N as N2
               where N1.N > 0
               for xml path('Level2'), type
               )
             from N as N1
             for xml path('Level1')
             );


select L1.X.value('@Value', 'varchar(10)')+'\'+
       L2.X.value('@Value', 'varchar(10)')+'\'+
       L3.X.value('@Value', 'varchar(10)')
from @X.nodes('/Level1') as L1(X)
  cross apply L1.X.nodes('Level2') as L2(X)
  cross apply L2.X.nodes('Level3') as L3(X);

Et une version totalement vide de XML.

with N as
(
  select T.N
  from (values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),
              (12),(13),(14),(15),(16),(17),(18),(19),(20)) as T(N)
)
select cast(N1.N as varchar(10))+'\'+
       cast(N2.N as varchar(10))+'\'+
       cast(N3.N as varchar(10))
from (
     select top(5 + abs(checksum(newid())) % 15)
       N.N
     from N
     ) as N1
  cross apply
     (
     select top(1 + abs(checksum(newid())) % 10)
       N.N
     from N
     where N1.N > 0
     ) as N2
  cross apply
     (
     select top(1 + abs(checksum(newid())) % 5)
       N.N
     from N
     where N2.N > 0
     ) as N3;

La corrélation N1.N > 0et N2.N > 0est toujours importante.

Une version utilisant une table avec 20 noms à utiliser au lieu de simples entiers.

declare @Elements table
(
  Name nvarchar(50) not null
);

insert into @Elements(Name)
select top(20) C.name 
from sys.columns as C
group by C.name;

select N1.Name + N'\' + N2.Name + N'\' + N3.Name
from (
     select top(5 + abs(checksum(newid())) % 15)
       E.Name
     from @Elements as E
     ) as N1
  cross apply
     (
     select top(1 + abs(checksum(newid())) % 10)
       E.Name
     from @Elements as E
     where N1.Name > ''
     ) as N2
  cross apply
     (
     select top(1 + abs(checksum(newid())) % 5)
       E.Name
     from @Elements as E
     where N2.Name > ''
     ) as N3;
Mikael Eriksson
la source
1
J'aime mieux la nouvelle version. C'est presque la même chose que j'ai inventée lors de ma première tentative, mais pour une raison quelconque, je n'ai pas pu faire TOP(n)fonctionner correctement dans les 2 CROSS APPLYs. Je ne sais pas ce que j'ai fait différemment / incorrectement depuis que je me suis débarrassé de ce code une fois que j'ai fait fonctionner autre chose. Je posterai cela bientôt, maintenant que vous avez fourni cette mise à jour. Et j'ai nettoyé la plupart de mes commentaires ci-dessus.
Solomon Rutzky
Je viens de poster ma version. Les principales différences sont: 1) comme je n'ai pas pu faire fonctionner le TOP (n), je suis allé chercher des néléments via une condition WHERE, et 2) j'ai le namecomposant qui est plus contrôlé que la randomisation des noms de répertoires et / ou de fichiers .
Solomon Rutzky
Désolé d'être absent depuis si longtemps, mais j'ai été très occupé. Pourtant, j'y ai pensé et je ne peux pas choisir entre ma réponse et votre version non XML. J'aime la simplicité et la flexibilité de la vôtre, mais j'ai besoin de pouvoir renvoyer des noms à utiliser pour créer une structure de dossiers, qui est la mienne. Ensuite, j'ai réalisé que Vlad avait mis à jour le sien pour avoir une table de recherche et s'y joindre pour donner la sortie idéale. Donc, s'il n'est pas inapproprié de demander, pourriez-vous mettre à jour la vôtre pour inclure la même recherche? Ensuite, les 3 réponses donneraient un résultat équivalent (idéal pour comparer les 3), et j'accepterais la vôtre. Est-ce que ça va?
Solomon Rutzky
1
@srutzky J'ai mis à jour la réponse. Il y a un certain temps, j'espère que j'ai bien compris et ce que vous cherchiez. Vous pouvez bien sûr ajouter une colonne de niveau @Elemetspour obtenir un ensemble de noms différent pour chaque niveau à choisir.
Mikael Eriksson
1
@srutzky pas de soucis. Je suis content que la réponse vous ait été utile.
Mikael Eriksson du
6

C'était intéressant.

Mon objectif était de générer un nombre donné de niveaux avec un nombre aléatoire de lignes enfants pour chaque niveau dans une structure hiérarchique correctement liée. Une fois que cette structure est prête, il est facile d'y ajouter des informations supplémentaires comme les noms de fichiers et de dossiers.

Donc, je voulais générer une table classique pour stocker un arbre:

ID int NOT NULL
ParentID int NULL
Lvl int NOT NULL

Puisque nous avons affaire à la récursivité, le CTE récursif semble un choix naturel.

J'aurai besoin d'un tableau de chiffres . Les chiffres du tableau doivent commencer à partir de 1. Il devrait y avoir au moins 20 chiffres dans le tableau: MAX(LvlMax).

CREATE TABLE [dbo].[Numbers](
    [Number] [int] NOT NULL,
CONSTRAINT [PK_Numbers] PRIMARY KEY CLUSTERED 
(
    [Number] ASC
));

INSERT INTO Numbers(Number)
SELECT TOP(1000)
    ROW_NUMBER() OVER(ORDER BY S.object_id)  AS Number
FROM
    sys.all_objects AS S
ORDER BY Number;

Les paramètres de génération des données doivent être stockés dans une table:

DECLARE @Intervals TABLE (Lvl int, LvlMin int, LvlMax int);
INSERT INTO @Intervals (Lvl, LvlMin, LvlMax) VALUES
(1, 5, 20),
(2, 1, 10),
(3, 1, 5);

Notez que la requête est assez flexible et tous les paramètres sont séparés en un seul endroit. Vous pouvez ajouter plus de niveaux si nécessaire, ajoutez simplement une ligne supplémentaire de paramètres.

Pour rendre une telle génération dynamique possible, j'ai dû me souvenir du nombre aléatoire de lignes pour le niveau suivant, j'ai donc une colonne supplémentaire ChildRowCount.

La génération d' unique IDs est également quelque peu délicate. J'ai codé en dur la limite de 100 lignes enfants pour 1 ligne parent pour garantir que IDscela ne se répète pas. C'est de cela qu'il POWER(100, CTE.Lvl)s'agit. En conséquence, il existe de grandes lacunes IDs. Ce nombre pourrait être un MAX(LvlMax), mais j'ai mis la constante 100 dans la requête de simplicité. Le nombre de niveaux n'est pas codé en dur, mais est déterminé par @Intervals.

Cette formule

CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5

génère un nombre à virgule flottante aléatoire dans la plage [0..1), qui est ensuite mis à l'échelle à l'intervalle requis.

La logique de requête est simple. C'est récursif. La première étape génère un ensemble de lignes du premier niveau. Le nombre de lignes est déterminé par un nombre aléatoire dans TOP. De plus, pour chaque ligne, un nombre aléatoire distinct de lignes enfants est stocké dans ChildRowCount.

La partie récursive utilise CROSS APPLYpour générer un nombre donné de lignes enfants pour chaque ligne parent. J'ai dû utiliser à la WHERE Numbers.Number <= CTE.ChildRowCountplace de TOP(CTE.ChildRowCount), car TOPn'est pas autorisé dans la partie récursive de CTE. Je ne connaissais pas cette limitation de SQL Server auparavant.

WHERE CTE.ChildRowCount IS NOT NULL arrête la récursivité.

SQL Fiddle

WITH
CTE
AS
(
    SELECT 
        TOP(CAST(
            (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 
            (
                1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = 1)
                  - (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 1)
            )
            + (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 1)
            AS int))
        Numbers.Number AS ID
        ,NULL AS ParentID
        ,1 AS Lvl
        ,CAST(
            (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 
            (
                1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = 2)
                  - (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 2)
            )
            + (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 2)
            AS int) AS ChildRowCount
    FROM Numbers
    ORDER BY Numbers.Number

    UNION ALL

    SELECT
        CA.Number + CTE.ID * POWER(100, CTE.Lvl) AS ID
        ,CTE.ID AS ParentID
        ,CTE.Lvl + 1 AS Lvl
        ,CA.ChildRowCount
    FROM
        CTE
        CROSS APPLY
        (
            SELECT
                Numbers.Number
                ,CAST(
                    (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 
                    (
                    1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
                      - (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
                    )
                    + (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
                    AS int) AS ChildRowCount
            FROM Numbers
            WHERE Numbers.Number <= CTE.ChildRowCount
        ) AS CA
    WHERE
        CTE.ChildRowCount IS NOT NULL
)
SELECT *
FROM CTE
ORDER BY Lvl, ParentID, ID;

Résultat (il peut y avoir jusqu'à 20 + 20 * 10 + 200 * 5 = 1220 lignes si vous êtes chanceux)

+---------+----------+-----+-------------------+
|   ID    | ParentID | Lvl | ChildRowCount     |
+---------+----------+-----+-------------------+
|       1 | NULL     |   1 | 3                 |
|       2 | NULL     |   1 | 1                 |
|       3 | NULL     |   1 | 6                 |
|       4 | NULL     |   1 | 5                 |
|       5 | NULL     |   1 | 3                 |
|       6 | NULL     |   1 | 7                 |
|       7 | NULL     |   1 | 1                 |
|       8 | NULL     |   1 | 6                 |
|     101 | 1        |   2 | 3                 |
|     102 | 1        |   2 | 5                 |
|     103 | 1        |   2 | 1                 |
|     201 | 2        |   2 | 5                 |
|     301 | 3        |   2 | 4                 |
|     302 | 3        |   2 | 5                 |
|     303 | 3        |   2 | 1                 |
|     304 | 3        |   2 | 2                 |
|     305 | 3        |   2 | 4                 |
|     306 | 3        |   2 | 3                 |
|     401 | 4        |   2 | 3                 |
|     402 | 4        |   2 | 1                 |
|     403 | 4        |   2 | 2                 |
|     404 | 4        |   2 | 2                 |
|     405 | 4        |   2 | 4                 |
|     501 | 5        |   2 | 1                 |
|     502 | 5        |   2 | 3                 |
|     503 | 5        |   2 | 5                 |
|     601 | 6        |   2 | 2                 |
|     602 | 6        |   2 | 5                 |
|     603 | 6        |   2 | 3                 |
|     604 | 6        |   2 | 3                 |
|     605 | 6        |   2 | 4                 |
|     606 | 6        |   2 | 5                 |
|     607 | 6        |   2 | 4                 |
|     701 | 7        |   2 | 2                 |
|     801 | 8        |   2 | 2                 |
|     802 | 8        |   2 | 3                 |
|     803 | 8        |   2 | 3                 |
|     804 | 8        |   2 | 3                 |
|     805 | 8        |   2 | 5                 |
|     806 | 8        |   2 | 2                 |
| 1010001 | 101      |   3 | NULL              |
| 1010002 | 101      |   3 | NULL              |
| 1010003 | 101      |   3 | NULL              |
| 1020001 | 102      |   3 | NULL              |
| 1020002 | 102      |   3 | NULL              |
| 1020003 | 102      |   3 | NULL              |
| 1020004 | 102      |   3 | NULL              |
| 1020005 | 102      |   3 | NULL              |
| 1030001 | 103      |   3 | NULL              |
| 2010001 | 201      |   3 | NULL              |
| 2010002 | 201      |   3 | NULL              |
| 2010003 | 201      |   3 | NULL              |
| 2010004 | 201      |   3 | NULL              |
| 2010005 | 201      |   3 | NULL              |
| 3010001 | 301      |   3 | NULL              |
| 3010002 | 301      |   3 | NULL              |
| 3010003 | 301      |   3 | NULL              |
| 3010004 | 301      |   3 | NULL              |
| 3020001 | 302      |   3 | NULL              |
| 3020002 | 302      |   3 | NULL              |
| 3020003 | 302      |   3 | NULL              |
| 3020004 | 302      |   3 | NULL              |
| 3020005 | 302      |   3 | NULL              |
| 3030001 | 303      |   3 | NULL              |
| 3040001 | 304      |   3 | NULL              |
| 3040002 | 304      |   3 | NULL              |
| 3050001 | 305      |   3 | NULL              |
| 3050002 | 305      |   3 | NULL              |
| 3050003 | 305      |   3 | NULL              |
| 3050004 | 305      |   3 | NULL              |
| 3060001 | 306      |   3 | NULL              |
| 3060002 | 306      |   3 | NULL              |
| 3060003 | 306      |   3 | NULL              |
| 4010001 | 401      |   3 | NULL              |
| 4010002 | 401      |   3 | NULL              |
| 4010003 | 401      |   3 | NULL              |
| 4020001 | 402      |   3 | NULL              |
| 4030001 | 403      |   3 | NULL              |
| 4030002 | 403      |   3 | NULL              |
| 4040001 | 404      |   3 | NULL              |
| 4040002 | 404      |   3 | NULL              |
| 4050001 | 405      |   3 | NULL              |
| 4050002 | 405      |   3 | NULL              |
| 4050003 | 405      |   3 | NULL              |
| 4050004 | 405      |   3 | NULL              |
| 5010001 | 501      |   3 | NULL              |
| 5020001 | 502      |   3 | NULL              |
| 5020002 | 502      |   3 | NULL              |
| 5020003 | 502      |   3 | NULL              |
| 5030001 | 503      |   3 | NULL              |
| 5030002 | 503      |   3 | NULL              |
| 5030003 | 503      |   3 | NULL              |
| 5030004 | 503      |   3 | NULL              |
| 5030005 | 503      |   3 | NULL              |
| 6010001 | 601      |   3 | NULL              |
| 6010002 | 601      |   3 | NULL              |
| 6020001 | 602      |   3 | NULL              |
| 6020002 | 602      |   3 | NULL              |
| 6020003 | 602      |   3 | NULL              |
| 6020004 | 602      |   3 | NULL              |
| 6020005 | 602      |   3 | NULL              |
| 6030001 | 603      |   3 | NULL              |
| 6030002 | 603      |   3 | NULL              |
| 6030003 | 603      |   3 | NULL              |
| 6040001 | 604      |   3 | NULL              |
| 6040002 | 604      |   3 | NULL              |
| 6040003 | 604      |   3 | NULL              |
| 6050001 | 605      |   3 | NULL              |
| 6050002 | 605      |   3 | NULL              |
| 6050003 | 605      |   3 | NULL              |
| 6050004 | 605      |   3 | NULL              |
| 6060001 | 606      |   3 | NULL              |
| 6060002 | 606      |   3 | NULL              |
| 6060003 | 606      |   3 | NULL              |
| 6060004 | 606      |   3 | NULL              |
| 6060005 | 606      |   3 | NULL              |
| 6070001 | 607      |   3 | NULL              |
| 6070002 | 607      |   3 | NULL              |
| 6070003 | 607      |   3 | NULL              |
| 6070004 | 607      |   3 | NULL              |
| 7010001 | 701      |   3 | NULL              |
| 7010002 | 701      |   3 | NULL              |
| 8010001 | 801      |   3 | NULL              |
| 8010002 | 801      |   3 | NULL              |
| 8020001 | 802      |   3 | NULL              |
| 8020002 | 802      |   3 | NULL              |
| 8020003 | 802      |   3 | NULL              |
| 8030001 | 803      |   3 | NULL              |
| 8030002 | 803      |   3 | NULL              |
| 8030003 | 803      |   3 | NULL              |
| 8040001 | 804      |   3 | NULL              |
| 8040002 | 804      |   3 | NULL              |
| 8040003 | 804      |   3 | NULL              |
| 8050001 | 805      |   3 | NULL              |
| 8050002 | 805      |   3 | NULL              |
| 8050003 | 805      |   3 | NULL              |
| 8050004 | 805      |   3 | NULL              |
| 8050005 | 805      |   3 | NULL              |
| 8060001 | 806      |   3 | NULL              |
| 8060002 | 806      |   3 | NULL              |
+---------+----------+-----+-------------------+

Génération d'un chemin complet au lieu d'une hiérarchie liée

Si nous ne sommes intéressés que par les Nniveaux de chemin complets , nous pouvons omettre IDet ParentIDquitter le CTE. Si nous avons une liste de noms possibles dans le tableau supplémentaire Names, il est facile de les choisir dans ce tableau en CTE. Le Namestableau doit avoir suffisamment de lignes pour chaque niveau: 20 pour le niveau 1, 10 pour le niveau 2, 5 pour le niveau 3; 20 + 10 + 5 = 35 au total. Il n'est pas nécessaire d'avoir différents ensembles de lignes pour chaque niveau, mais il est facile de le configurer correctement, alors je l'ai fait.

DECLARE @Names TABLE (Lvl int, Name nvarchar(4000), SeqNumber int);

-- First level: AAA, BBB, CCC, etc.
INSERT INTO @Names (Lvl, Name, SeqNumber)
SELECT 1, REPLICATE(CHAR(Number+64), 3) AS Name, Number AS SeqNumber
FROM Numbers
WHERE Number <= 20;

-- Second level: 001, 002, 003, etc.
INSERT INTO @Names (Lvl, Name, SeqNumber)
SELECT 2, REPLACE(STR(Number, 3), ' ', '0') AS Name, Number AS SeqNumber
FROM Numbers
WHERE Number <= 10;

-- Third level: I, II, III, IV, V
INSERT INTO @Names (Lvl, Name, SeqNumber) VALUES
(3, 'I',   1),
(3, 'II',  2),
(3, 'III', 3),
(3, 'IV',  4),
(3, 'V',   5);

SQL Fiddle Voici la requête finale. Je partage l' FullPathen FilePathet FileName.

WITH
CTE
AS
(
    SELECT 
        TOP(CAST(
            (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 
            (
                1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = 1)
                  - (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 1)
            )
            + (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 1)
            AS int))

        1 AS Lvl
        ,CAST(
            (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 
            (
                1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = 2)
                  - (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 2)
            )
            + (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 2)
            AS int) AS ChildRowCount
        ,N.Name AS FullPath
        ,N.Name AS [FilePath]
        ,CAST(N'' AS nvarchar(4000)) AS [FileName]
    FROM
        Numbers
        INNER JOIN @Names AS N ON 
            N.SeqNumber = Numbers.Number AND N.Lvl = 1
    ORDER BY Numbers.Number

    UNION ALL

    SELECT
        CTE.Lvl + 1 AS Lvl
        ,CA.ChildRowCount
        ,CTE.FullPath + '\' + CA.Name AS FullPath

        ,CASE WHEN CA.ChildRowCount IS NOT NULL 
            THEN CTE.FullPath + '\' + CA.Name
            ELSE CTE.FullPath END AS [FilePath]

        ,CASE WHEN CA.ChildRowCount IS NULL 
            THEN CA.Name
            ELSE N'' END AS [FileName]
    FROM
        CTE
        CROSS APPLY
        (
            SELECT
                Numbers.Number
                ,CAST(
                    (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 
                    (
                    1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
                      - (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
                    )
                    + (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
                    AS int) AS ChildRowCount
                ,N.Name
            FROM
                Numbers
                INNER JOIN @Names AS N ON 
                    N.SeqNumber = Numbers.Number AND N.Lvl = CTE.Lvl + 1
            WHERE Numbers.Number <= CTE.ChildRowCount
        ) AS CA
    WHERE
        CTE.ChildRowCount IS NOT NULL
)
SELECT
    CTE.FullPath
    ,CTE.[FilePath]
    ,CTE.[FileName]
FROM CTE
WHERE CTE.ChildRowCount IS NULL
ORDER BY FullPath;

Résultat

+-------------+----------+----------+
|  FullPath   | FilePath | FileName |
+-------------+----------+----------+
| AAA\001\I   | AAA\001  | I        |
| AAA\001\II  | AAA\001  | II       |
| AAA\002\I   | AAA\002  | I        |
| AAA\002\II  | AAA\002  | II       |
| AAA\002\III | AAA\002  | III      |
| AAA\002\IV  | AAA\002  | IV       |
| AAA\002\V   | AAA\002  | V        |
| AAA\003\I   | AAA\003  | I        |
| AAA\003\II  | AAA\003  | II       |
| AAA\003\III | AAA\003  | III      |
| AAA\004\I   | AAA\004  | I        |
| AAA\004\II  | AAA\004  | II       |
| AAA\004\III | AAA\004  | III      |
| AAA\004\IV  | AAA\004  | IV       |
| BBB\001\I   | BBB\001  | I        |
| BBB\001\II  | BBB\001  | II       |
| CCC\001\I   | CCC\001  | I        |
| CCC\001\II  | CCC\001  | II       |
| CCC\001\III | CCC\001  | III      |
| CCC\001\IV  | CCC\001  | IV       |
| CCC\001\V   | CCC\001  | V        |
| CCC\002\I   | CCC\002  | I        |
| CCC\003\I   | CCC\003  | I        |
| CCC\003\II  | CCC\003  | II       |
| CCC\004\I   | CCC\004  | I        |
| CCC\004\II  | CCC\004  | II       |
| CCC\005\I   | CCC\005  | I        |
| CCC\005\II  | CCC\005  | II       |
| CCC\005\III | CCC\005  | III      |
| CCC\006\I   | CCC\006  | I        |
| CCC\006\II  | CCC\006  | II       |
| CCC\006\III | CCC\006  | III      |
| CCC\006\IV  | CCC\006  | IV       |
| CCC\007\I   | CCC\007  | I        |
| CCC\007\II  | CCC\007  | II       |
| CCC\007\III | CCC\007  | III      |
| CCC\007\IV  | CCC\007  | IV       |
| CCC\008\I   | CCC\008  | I        |
| CCC\008\II  | CCC\008  | II       |
| CCC\008\III | CCC\008  | III      |
| CCC\009\I   | CCC\009  | I        |
| CCC\009\II  | CCC\009  | II       |
| CCC\009\III | CCC\009  | III      |
| CCC\009\IV  | CCC\009  | IV       |
| CCC\010\I   | CCC\010  | I        |
| CCC\010\II  | CCC\010  | II       |
| CCC\010\III | CCC\010  | III      |
| DDD\001\I   | DDD\001  | I        |
| DDD\001\II  | DDD\001  | II       |
| DDD\001\III | DDD\001  | III      |
| DDD\001\IV  | DDD\001  | IV       |
| DDD\002\I   | DDD\002  | I        |
| DDD\003\I   | DDD\003  | I        |
| DDD\003\II  | DDD\003  | II       |
| DDD\003\III | DDD\003  | III      |
| DDD\003\IV  | DDD\003  | IV       |
| DDD\004\I   | DDD\004  | I        |
| DDD\004\II  | DDD\004  | II       |
| DDD\004\III | DDD\004  | III      |
| DDD\005\I   | DDD\005  | I        |
| DDD\006\I   | DDD\006  | I        |
| DDD\006\II  | DDD\006  | II       |
| DDD\006\III | DDD\006  | III      |
| DDD\007\I   | DDD\007  | I        |
| DDD\007\II  | DDD\007  | II       |
| DDD\008\I   | DDD\008  | I        |
| DDD\008\II  | DDD\008  | II       |
| DDD\008\III | DDD\008  | III      |
| DDD\009\I   | DDD\009  | I        |
| DDD\009\II  | DDD\009  | II       |
| DDD\010\I   | DDD\010  | I        |
| DDD\010\II  | DDD\010  | II       |
| DDD\010\III | DDD\010  | III      |
| DDD\010\IV  | DDD\010  | IV       |
| DDD\010\V   | DDD\010  | V        |
| EEE\001\I   | EEE\001  | I        |
| EEE\001\II  | EEE\001  | II       |
| FFF\001\I   | FFF\001  | I        |
| FFF\002\I   | FFF\002  | I        |
| FFF\002\II  | FFF\002  | II       |
| FFF\003\I   | FFF\003  | I        |
| FFF\003\II  | FFF\003  | II       |
| FFF\003\III | FFF\003  | III      |
| FFF\003\IV  | FFF\003  | IV       |
| FFF\003\V   | FFF\003  | V        |
| FFF\004\I   | FFF\004  | I        |
| FFF\004\II  | FFF\004  | II       |
| FFF\004\III | FFF\004  | III      |
| FFF\004\IV  | FFF\004  | IV       |
| FFF\005\I   | FFF\005  | I        |
| FFF\006\I   | FFF\006  | I        |
| FFF\007\I   | FFF\007  | I        |
| FFF\007\II  | FFF\007  | II       |
| FFF\007\III | FFF\007  | III      |
| GGG\001\I   | GGG\001  | I        |
| GGG\001\II  | GGG\001  | II       |
| GGG\001\III | GGG\001  | III      |
| GGG\002\I   | GGG\002  | I        |
| GGG\003\I   | GGG\003  | I        |
| GGG\003\II  | GGG\003  | II       |
| GGG\003\III | GGG\003  | III      |
| GGG\004\I   | GGG\004  | I        |
| GGG\004\II  | GGG\004  | II       |
| HHH\001\I   | HHH\001  | I        |
| HHH\001\II  | HHH\001  | II       |
| HHH\001\III | HHH\001  | III      |
| HHH\002\I   | HHH\002  | I        |
| HHH\002\II  | HHH\002  | II       |
| HHH\002\III | HHH\002  | III      |
| HHH\002\IV  | HHH\002  | IV       |
| HHH\002\V   | HHH\002  | V        |
| HHH\003\I   | HHH\003  | I        |
| HHH\003\II  | HHH\003  | II       |
| HHH\003\III | HHH\003  | III      |
| HHH\003\IV  | HHH\003  | IV       |
| HHH\003\V   | HHH\003  | V        |
| HHH\004\I   | HHH\004  | I        |
| HHH\004\II  | HHH\004  | II       |
| HHH\004\III | HHH\004  | III      |
| HHH\004\IV  | HHH\004  | IV       |
| HHH\004\V   | HHH\004  | V        |
| HHH\005\I   | HHH\005  | I        |
| HHH\005\II  | HHH\005  | II       |
| HHH\005\III | HHH\005  | III      |
| HHH\005\IV  | HHH\005  | IV       |
| HHH\005\V   | HHH\005  | V        |
| HHH\006\I   | HHH\006  | I        |
| HHH\007\I   | HHH\007  | I        |
| HHH\007\II  | HHH\007  | II       |
| HHH\007\III | HHH\007  | III      |
| HHH\008\I   | HHH\008  | I        |
| HHH\008\II  | HHH\008  | II       |
| HHH\008\III | HHH\008  | III      |
| HHH\008\IV  | HHH\008  | IV       |
| HHH\008\V   | HHH\008  | V        |
+-------------+----------+----------+
Vladimir Baranov
la source
Approche intéressante :). Je l'aime. Par souci d'exhaustivité, pouvez-vous s'il vous plaît ajouter la requête pour remplir la table Numbers (à partir de SQL Fiddle), ou simplement l'inclure en ligne dans le CTE? Ensuite, il est plus facile pour quelqu'un de simplement copier et coller. Pour cette réponse, la sortie finale peut-elle être exprimée comme chaque ligne étant un chemin complet du niveau 1 vers le niveau 3 pour toutes les valeurs de niveau 3? Je pense que cela ne prendrait que 2 INNER JOINs en finale SELECT. Enfin, des noms / étiquettes peuvent-ils être attribués à chaque nœud afin qu'ils ne soient pas uniquement des numéros? Je mettrai à jour la question pour clarifier ces deux points.
Solomon Rutzky
D'où viennent ces noms / étiquettes? Dois-je avoir une table «Noms», qui a 20 lignes et choisir un nom? Ainsi, le même ensemble de noms apparaîtrait à chaque niveau. Ou chaque niveau devrait-il avoir son propre ensemble de noms?
Vladimir Baranov
Je pense que les noms peuvent provenir d'une table (temporaire, réelle ou variable) ou en ligne dans le cadre du CTE. Je les ai à l'origine placés dans le CTE mais les ai ensuite déplacés vers une table temporaire locale afin que la partie principale de la requête soit plus lisible ici. Je pense qu'avec la structure que vous avez, il serait assez facile d'avoir des niveaux séparés par niveau. Mais si ce n'était qu'un ensemble de 20 qui suffirait également, cela fournirait juste une variation légèrement moins importante des données de test. La seule vraie exigence est qu'aucun nom ne se répète dans un nœud car cela provoquerait une erreur lors de la création des répertoires ou des fichiers :).
Solomon Rutzky
1
@srutzky, j'ai ajouté une deuxième variante.
Vladimir Baranov
1
@srutzky, je me suis divisé FullPathen FilePathet FileName.
Vladimir Baranov,
4

Voici donc ce que j'ai trouvé. Dans le but de créer une structure de répertoires, je cherchais des "noms" utilisables pour les répertoires et fichiers. Parce que je n'ai pas pu obtenir le TOP(n)travail dans le CROSS APPLYs (je pense que j'ai essayé de corréler les requêtes en utilisant une valeur du parent comme ndans le TOP(n)mais alors ce n'était pas aléatoire), j'ai décidé de créer un type de "nombres" table qui permettrait à une condition INNER JOINor WHEREde produire un ensemble d' néléments simplement en randomisant un nombre et en le spécifiant comme WHERE table.Level = random_number. L'astuce est qu'il n'y a qu'une seule ligne pour le niveau 1, 2 lignes pour le niveau 2, 3 lignes pour le niveau 3, etc. Par conséquent, l'utilisation WHERE LevelID = 3me donnera 3 lignes, et chaque ligne a une valeur que je peux utiliser comme nom de répertoire.

INSTALLER

Cette partie a été initialement spécifiée en ligne, dans le cadre du CTE. Mais pour des raisons de lisibilité (afin que vous n'ayez pas besoin de faire défiler de nombreuses INSERTinstructions pour accéder aux quelques lignes de la vraie requête), je l'ai éclaté dans une table temporaire locale.

IF (OBJECT_ID(N'tempdb..#Elements') IS NULL)
BEGIN
  PRINT 'Creating #Elements table...';
  CREATE TABLE #Elements (
     ElementLevel TINYINT NOT NULL,
     LevelName NVARCHAR(50) NOT NULL
                         );

  PRINT 'Populating #Elements table...';
  INSERT INTO #Elements (ElementLevel, LevelName)
    SELECT tmp.[Level], tmp.[Name]
    FROM (
                  SELECT 1,  N'Ella'
       UNION ALL  SELECT 2,  N'Itchy'
       UNION ALL  SELECT 2,  N'Scratchy'
       UNION ALL  SELECT 3,  N'Moe'
       UNION ALL  SELECT 3,  N'Larry'
       UNION ALL  SELECT 3,  N'Curly'
       UNION ALL  SELECT 4,  N'Ian'
       UNION ALL  SELECT 4,  N'Stephen'
       UNION ALL  SELECT 4,  N'Peter'
       UNION ALL  SELECT 4,  N'Bernard'
       UNION ALL  SELECT 5,  N'Michigan'
       UNION ALL  SELECT 5,  N'Erie'
       UNION ALL  SELECT 5,  N'Huron'
       UNION ALL  SELECT 5,  N'Ontario'
       UNION ALL  SELECT 5,  N'Superior'
       UNION ALL  SELECT 6,  N'White'
       UNION ALL  SELECT 6,  N'Orange'
       UNION ALL  SELECT 6,  N'Blonde'
       UNION ALL  SELECT 6,  N'Pink'
       UNION ALL  SELECT 6,  N'Blue'
       UNION ALL  SELECT 6,  N'Brown'
       UNION ALL  SELECT 7,  N'Asia'
       UNION ALL  SELECT 7,  N'Africa'
       UNION ALL  SELECT 7,  N'North America'
       UNION ALL  SELECT 7,  N'South America'
       UNION ALL  SELECT 7,  N'Antarctica'
       UNION ALL  SELECT 7,  N'Europe'
       UNION ALL  SELECT 7,  N'Australia'
       UNION ALL  SELECT 8,  N'AA'
       UNION ALL  SELECT 8,  N'BB'
       UNION ALL  SELECT 8,  N'CC'
       UNION ALL  SELECT 8,  N'DD'
       UNION ALL  SELECT 8,  N'EE'
       UNION ALL  SELECT 8,  N'FF'
       UNION ALL  SELECT 8,  N'GG'
       UNION ALL  SELECT 8,  N'HH'
       UNION ALL  SELECT 9,  N'I'
       UNION ALL  SELECT 9,  N'II'
       UNION ALL  SELECT 9,  N'III'
       UNION ALL  SELECT 9,  N'IV'
       UNION ALL  SELECT 9,  N'V'
       UNION ALL  SELECT 9,  N'VI'
       UNION ALL  SELECT 9,  N'VII'
       UNION ALL  SELECT 9,  N'VIII'
       UNION ALL  SELECT 9,  N'IX'
       UNION ALL  SELECT 10, N'Million'
       UNION ALL  SELECT 10, N'Billion'
       UNION ALL  SELECT 10, N'Trillion'
       UNION ALL  SELECT 10, N'Quadrillion'
       UNION ALL  SELECT 10, N'Quintillion'
       UNION ALL  SELECT 10, N'Sestillion'
       UNION ALL  SELECT 10, N'Sextillion'
       UNION ALL  SELECT 10, N'Octillion'
       UNION ALL  SELECT 10, N'Nonillion'
       UNION ALL  SELECT 10, N'Decillion'
     ) tmp([Level], [Name]);
END;

Requête principale

Pour le niveau 1, je viens de saisir des [name]valeurs sys.objectscar il y a toujours beaucoup de lignes. Mais, si j'avais besoin de plus de contrôle sur les noms, je pouvais simplement étendre le #Elementstableau pour contenir des niveaux supplémentaires.

;WITH topdir(Level1, Randy) AS
(
    SELECT TOP ( (CONVERT(INT, CRYPT_GEN_RANDOM(1)) % 20) + 5 ) so.[name],
                ( (CONVERT(INT, CRYPT_GEN_RANDOM(1)) % 10) + 1 )
    FROM sys.objects so
    ORDER BY CRYPT_GEN_RANDOM(8) ASC
)
SELECT  td.Level1, tmp1.Level2, tmp2.Level3
FROM    topdir td
CROSS APPLY (SELECT help.LevelName, (CONVERT(INT, CRYPT_GEN_RANDOM(1)) % 5) + 1
            FROM #Elements help
            WHERE help.ElementLevel = td.Randy
            ) tmp1 (Level2, Bandy)
CROSS APPLY (SELECT help.LevelName
            FROM #Elements help
            WHERE help.ElementLevel = tmp1.Bandy
            ) tmp2 (Level3);

REQUÊTE ADAPTÉE POUR PRODUIRE LE CHEMIN, LE NOM ET LE CONTENU DE CHAQUE FICHIER

Afin de générer les chemins complets pour les fichiers et le contenu des fichiers, j'ai fait le SELECT principal du CTE juste un autre CTE et ajouté un nouveau SELECT principal qui a donné les sorties appropriées qui ont simplement besoin d'entrer dans les fichiers.

DECLARE @Template NVARCHAR(4000);
SET @Template = N'<?xml version="1.0" encoding="ISO-8859-1"?>
<ns0:P4131 xmlns:ns0="http://switching/xi">
<R000000>
    <R00000010>R000000</R00000010>
    <R00000020>I</R00000020>
    <R00000030>{{Tag30}}</R00000030>
    <R00000040>{{Tag40}}</R00000040>
    <R00000050>{{Tag50}}</R00000050>
    <R00000060>2</R00000060>
</R000000>
</ns0:P4131>
';


;WITH topdir(Level1, Thing1) AS
(
    SELECT TOP ( (CONVERT(INT, CRYPT_GEN_RANDOM(1)) % 20) + 5 ) so.[name],
                ( (CONVERT(INT, CRYPT_GEN_RANDOM(1)) % 10) + 1 )
    FROM sys.objects so
    ORDER BY CRYPT_GEN_RANDOM(8) ASC
), main AS
(
   SELECT  td.Level1, tmp1.Level2, tmp2.Level3,
           td.Level1 + N'\' + tmp1.Level2 AS [FullPath],
           RIGHT('000' + CONVERT(VARCHAR(10),
                          (CONVERT(INT, CRYPT_GEN_RANDOM(2)) % 9999) + 1), 4) AS [R30],
           RIGHT('000' + CONVERT(VARCHAR(10),
                          (CONVERT(INT, CRYPT_GEN_RANDOM(2)) % 500) + 100), 4) AS [R50],
           ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS [RowNum]
   FROM    topdir td
   CROSS APPLY (SELECT help.LevelName, (CONVERT(INT, CRYPT_GEN_RANDOM(1)) % 5) + 1
                FROM #Elements help
                WHERE help.ElementLevel = td.Thing1
               ) tmp1 (Level2, Thing2)
   CROSS APPLY (SELECT help.LevelName
                FROM #Elements help
                WHERE help.ElementLevel = tmp1.Thing2
               ) tmp2 (Level3)
)
SELECT  mn.FullPath,
        mn.Level3 + N'.xml' AS [FileName],
        REPLACE(
            REPLACE(
                REPLACE(
                    @Template,
                    N'{{Tag30}}',
                    mn.R30),
                N'{{Tag40}}',
                mn.RowNum),
            N'{{Tag50}}',
            mn.R50) AS [Contents]
FROM    main mn;

CRÉDIT SUPPLÉMENTAIRE

Bien que cela ne fasse pas partie des exigences énoncées dans la question, l'objectif (qui a été mentionné) était de créer des fichiers pour tester les fonctions récursives du système de fichiers avec. Alors, comment pouvons-nous prendre cet ensemble de résultats de noms de chemin, de noms de fichiers et de contenu de fichiers et faire quelque chose avec? Nous avons juste besoin de deux fonctions SQLCLR: une pour créer les dossiers et une pour créer les fichiers.

Afin de rendre ces données fonctionnelles, j'ai modifié le principal SELECTdu CTE montré directement ci-dessus comme suit:

SELECT  SQL#.File_CreateDirectory(
            N'C:\Stuff\TestXmlFiles\' + mn.FullPath) AS [CreateTheDirectory],
        SQL#.File_WriteFile(
            N'C:\Stuff\TestXmlFiles\' + mn.FullPath + N'\' + mn.Level3 + N'.xml',
            REPLACE(
                REPLACE(
                    REPLACE(
                        @Template,
                        N'{{Tag30}}',
                        mn.R30),
                    N'{{Tag40}}',
                    mn.RowNum),
                N'{{Tag50}}',
                mn.R50), -- @FileData
            0, -- @AppendData
            '' -- @FileEncoding
                            ) AS [WriteTheFile]
FROM    main mn;
Solomon Rutzky
la source