2017-09-20 12 views
0

Je recherche la meilleure solution pour créer une chaîne JSON imbriquée directement à partir de (T) SQL avec une requête SQL dynamique.JSON imbriqué avec requête dynamique

Dans SQL Server 2016, il est facile de créer une chaîne JSON plat avec une déclaration comme celle-ci:

SELECT * 
FROM tblTableName 
FOR JSON AUTO 

Si vous avez besoin d'un résultat imbriqué plus complexe, vous pouvez utiliser un simple routine récursive comme:

CREATE FUNCTION [dbo].[NestedJSON](@Id uniqueidentifier) 
RETURNS NVARCHAR(MAX) 
AS 
BEGIN 
    DECLARE @Json NVARCHAR(MAX) = '{}'; 

    IF @Id Is NULL 
     SET @Json = 
      (SELECT *, JSON_QUERY(dbo.NestedJSON(Id)) AS child 
      FROM dbo.tblTableName 
      WHERE IDParent is NULL 
      FOR JSON AUTO); 
    ELSE 
     SET @Json = 
      (SELECT *, JSON_QUERY(dbo.NestedJSON(Id)) AS child 
      FROM dbo.tblTableName 
      WHERE IDParent = @Id 
      FOR JSON AUTO); 
    RETURN @Json 
END 

Id is the ID of each record in tblTableName

IDParent is the parent Id of the record in tblTableName

Cette fonction récursive ne fonctionne que si la requête SQL est fixe.

Dans ma situation, j'ai beaucoup de requêtes avec une structure imbriquée. Pour prendre en charge toutes les nombreuses requêtes SQL imbriquées, j'ai essayé de modifier la fonction NestedJSON ci-dessus, mais il est évident qu'il n'est pas permis d'utiliser le SQL dynamique dans une fonction. J'ai essayé des options comme:

IF @Id Is NULL 
     Set @SQL = 'SELECT @Json=(SELECT ' + @FieldList + ' ,JSON_QUERY(dbo.MenuNested(' + @Id + ')) AS Child FROM ' + @TheTables + ' WHERE IDParent is NULL FOR JSON AUTO)' 
    ELSE 
     Set @SQL = 'SELECT @Json=(SELECT ' + @FieldList + ' ,JSON_QUERY(dbo.MenuNested(' + @Id + ')) AS Child FROM ' + @TheTables + ' WHERE IDParent = ' + @Id + ' FOR JSON AUTO)' 

    Exec(@SQL) 
    --or 
    execute sp_executesql @SQL; 

Mais toutes les modifications ont abouti à la même erreur: « Seules les fonctions et des procédures stockées étendues peuvent être exécutées à l'intérieur d'une fonction. »

J'appelle le serveur SQL à partir de vb.net afin que je puisse créer une fonction supplémentaire pour arborer le JSON imbriqué, mais c'est la dernière option pour moi. Je pense que la solution la plus rapide et la plus propre est de faire l'imbrication complète en (T) SQL.

Alors, y a-t-il quelqu'un qui peut m'aider à créer une solution capable de prendre en charge le langage dynamique SQL et de renvoyer un fichier JSON imbriqué?

Merci toute aide est appréciée.

Arno

+0

Vous pouvez utiliser la procédure stockée au lieu de la fonction – sepupic

+0

Merci pour le conseil. Je ne pensais pas encore à une proc distante. Une procédure à distance est-elle aussi rapide qu'une fonction? – Arno

+0

Si votre sp fait les mêmes choses, il aura le même temps d'exécution. La différence est la façon dont vous recevez le résultat: le résultat de funcion peut être utilisé directement dans les requêtes, le résultat sp doit être sauvegardé avant de pouvoir l'utiliser – sepupic

Répondre

0

J'ai finalement résolu le problème.Pour ceux qui sont intéressés ici est la solution:

La première étape consiste à créer un nouveau type de table qui peut être utilisé dans la routine récursive:

CREATE TYPE [dbo].[JSONCTETableType] AS TABLE(
    [Sequence] [uniqueidentifier] NULL, 
    [ParentSequence] [uniqueidentifier] NULL, 
    [JSON] [nvarchar](max) NULL, 
    [IndentLevel] [int] NULL, 
    [WBS] [nvarchar](512) NULL, 
    [ChildCount] [int] NULL 
) 

Dans la deuxième étape, j'ai changé la fonction récursive d'origine de utiliser cette définition de la table:

-- ============================================= 
-- Author:  Arno Voerman 
-- Create date: 2017, September 
-- ============================================= 
CREATE FUNCTION [dbo].[TREEIFY] 
(
    @ParentId uniqueidentifier, @JSONTABLE JSONCTETableType READONLY 
) 
RETURNS NVARCHAR(MAX) 
AS 
BEGIN 
    DECLARE @Json NVARCHAR(MAX); 

    If @ParentId Is NULL 
     Begin 
      SET @Json = (
       Select JSON + 
        Case When ChildCount > 0 
         THEN ',"children":[' + dbo.treeIfy(Sequence,@JSONTABLE) + ']}' 
         ELSE ',"leaf":true}' 
        END + ',' AS 'data()' 
       FROM @JSONTABLE 
       Where ParentSequence Is Null 
       FOR XML PATH('') 
      ) 
     END 
    Else 
     Begin 
      SET @Json = (
       Select JSON + 
        Case When ChildCount > 0 
         THEN ',"children":[' + dbo.treeIfy(Sequence,@JSONTABLE) + ']}' 
         ELSE ',"leaf":true}' 
        END + ',' AS 'data()' 
       FROM @JSONTABLE 
       Where ParentSequence = @ParentId 
       FOR XML PATH('') 
      ) 
     End 
    RETURN @Json 
END 

Pour obtenir le résultat JSON-imbriqué de la requête la requête résultat doit être transformé en JSONCTETableType. Cela se fait dans le CTE suivant qui génère également JSON et des données supplémentaires qui a besoin de mon programme:

DECLARE @JSONTT as JSONCTETableType; 
WITH JSONCTETable AS (
    SELECT Sequence, IDParentSequence As ParentSequence, 
      (SELECT * FROM YourTable Where Sequence=anchor.Sequence FOR JSON AUTO, WITHOUT_ARRAY_WRAPPER) As JSON, 
      0 as IndentLevel, 
      Cast(ROW_NUMBER() OVER(ORDER BY mnu_Order ASC) As NVARCHAR(512)) AS WBS 
    FROM YourTable AS anchor 
    WHERE IDParentSequence is null 
UNION ALL ------------------------------------------------------------------- 

SELECT recur.Sequence, recur.IDParentSequence As ParentSequence, 
     (SELECT * FROM YourTable Where Sequence=recur.Sequence FOR JSON AUTO, WITHOUT_ARRAY_WRAPPER) As JSON, 
     cte.IndentLevel + 1, 
     Cast(cte.WBS + '.' + Cast(ROW_NUMBER() OVER(ORDER BY mnu_Order ASC) As NVARCHAR(10)) AS NVARCHAR(512)) AS WBS 
FROM YourTable AS recur 
     INNER JOIN JSONCTETable AS cte ON cte.Sequence = recur.IDParentSequence 

)INSERT INTO @JSONTT (Sequence, ParentSequence, JSON, IndentLevel, WBS, ChildCount) 
SELECT *, (Select count(*) From JSONCTETable tmpCTE Where tmpCTE.ParentSequence=JSONCTETable.Sequence) as ChildCount 
FROM JSONCTETable 
ORDER BY WBS; 

--Remove last character ("}") of all JSON. Needed to simplify the add "CHILD" 
Update @JSONTT SET JSON = stuff(JSON,len(JSON),1,''); 

La dernière étape est simple:

Select '[' + dbo.treeIfy(Null,@JSONTT) + ']' as data, (Select count(Sequence) From @JSONTT) as totRows; 

Bonne chance!

Arno

0

Merci sepupic pour la réponse.

Je transforme la fonction du poste initial dans une procédure stockée, mais je suis stuggeling dans la conversion de cette partie:

SET @Json = 
    (SELECT *, JSON_QUERY(dbo.NestedJSON(Id)) AS child 
    FROM dbo.tblTableName 
    WHERE IDParent is NULL 
    FOR JSON AUTO); 

Une procédure stockée ne peut pas être utilisé au sein même définition de la requête parce qu'elle ne fonctionne pas renvoie la valeur de retour de la même manière. Comme il ne peut pas être utilisé dans la requête elle-même, je ne peux pas passer le paramètre Id (dbo.NestedJSON (Id)). La seule solution à laquelle je pense est de boucler tous les résultats de la requête et d'obtenir chaque ID un par un, puis de l'exécuter.

Une autre suggestion?

Arno