21

Question: Je souhaite écrire une fonction d'agrégation personnalisée qui concatène une chaîne sur un groupe.Fonction d'agrégation personnalisée (concat) dans SQL Server

Alors que je peux faire une

SELECT SUM(FIELD1) as f1, MYCONCAT(FIELD2) as f2 
FROM TABLE_XY 
GROUP BY FIELD1, FIELD2 

Tout ce que je trouve est des fonctions d'agrégation de CRL SQL, mais je dois SQL, sans CLR.



Edit: 1
La requête devrait ressembler à ceci:

SELECT SUM(FIELD1) as f1, MYCONCAT(FIELD2) as f2 
    FROM TABLE_XY 
    GROUP BY FIELD0 



Edit 2:
Il est vrai qu'il est impossible sans CLR.
Cependant, la réponse de sous-sélection par astander peut être modifiée afin de ne pas encoder de caractères spéciaux.

Le changement subtil pour est d'ajouter ceci après "POUR XML PATH": ,

TYPE 
       ).value('.[1]', 'nvarchar(MAX)') 

Voici quelques exemples

DECLARE @tT table([A] varchar(200), [B] varchar(200)); 

INSERT INTO @tT VALUES ('T_A', 'C_A'); 
INSERT INTO @tT VALUES ('T_A', 'C_B'); 
INSERT INTO @tT VALUES ('T_B', 'C_A'); 
INSERT INTO @tT VALUES ('T_C', 'C_A'); 
INSERT INTO @tT VALUES ('T_C', 'C_B'); 
INSERT INTO @tT VALUES ('T_C', 'C_C'); 

SELECT 
     A AS [A] 
     , 
     ( 
      STUFF 
      ( 
        ( 
          SELECT DISTINCT 
            ', ' + tempT.B AS wtf 
          FROM @tT AS tempT 
          WHERE (1=1) 
          --AND tempT.TT_Status = 1 
          AND tempT.A = myT.A 
          ORDER BY wtf 
          FOR XML PATH, TYPE 
        ).value('.[1]', 'nvarchar(MAX)') 
        , 1, 2, '' 
      ) 
    ) AS [B] 
FROM @tT AS myT 
GROUP BY A 





SELECT 
     ( 
      SELECT 
        ',äöü<>' + RM_NR AS [text()] 
      FROM T_Room 
      WHERE RM_Status = 1 
      ORDER BY RM_NR 
      FOR XML PATH('') 

    ) AS XmlEncodedNoNothing 


     , 
     SUBSTRING 
     (
      (
        SELECT 
         ',äöü<>' + RM_NR AS [data()] 
        FROM T_Room 
        WHERE RM_Status = 1 
        ORDER BY RM_NR 
        FOR XML PATH('') 
      ) 
      ,2 
      ,10000 
    ) AS XmlEncodedSubstring 


     , 
     ( 
      STUFF 
      ( 
        ( 
         SELECT ',äöü<>' + RM_NR + CHAR(10) 
         FROM T_Room 
         WHERE RM_Status = 1 
         ORDER BY RM_NR 
         FOR XML PATH, TYPE 
       ).value('.[1]', 'nvarchar(MAX)') 
        , 1, 1, '' 
      ) 
    ) AS XmlDecodedStuffInsteadSubstring 
+2

Dans le cas de votre code d'exemple, il y aura seulement une valeur pour FIELD2 de toute façon (GROUP BY) de sorte que vous ne pas besoin de la fonction. Je suppose que votre exemple est faux. – sqlvogel

+0

Ahahaha, bon - bon sang, tu as raison. Field0 serait un UID (group by), field1 et field2 ne devraient pas être dans la clause de groupe ... –

Répondre

11

Vous ne pouvez pas écrire des agrégats personnalisés en dehors du CLR.

Le seul type de fonctions que vous pouvez écrire en langage T-SQL pur sont les fonctions scalaires et les valeurs de table.

Comparez les pages de CREATE AGGREGATE, qui répertorie uniquement les options de style CLR, avec CREATE FUNCTION, qui affiche les options T-SQL et CLR.

+0

Le problème est que je ne peux pas donner une fonction CLR à un client pour l'installation, surtout pas dans une zone de sécurité restreinte. .. Mais de toute façon, «pas possible» répond à ma question. –

+2

@Quandary Juste une suggestion: Si vous dites à un admin db qu'il doit activer clr dans sql et charger votre assembly alors bien sûr il va dire "Oh, c'est un risque de sécurité". Mais si votre application fait tout ce travail et dit simplement "L'application a besoin de ces droits" (ou mieux encore, il suffit de l'installer dans un installateur pour que seul le programme d'installation ait besoin de ces droits). Peut-être pas une option pour vous (et je me rends compte que c'est une question vieille d'un an), mais je pensais que je voudrais jeter cette pensée là-bas. –

+0

@Brandon Moore: ;-) Oui, vous avez identifié avec succès le problème entre les lignes - suggérant la politique à la rescousse. Cependant, je doute fort que n'importe quel admin de DB suffisamment intelligent donnerait à n'importe quelle application le droit de modifier les paramètres à l'échelle de DB ... –

13

Regardez quelque chose comme. Ce n'est pas une fonction agrégée. Si vous souhaitez implémenter votre propre fonction d'agrégat, il faudra que ce soit CLR ...

DECLARE @Table TABLE(
     ID INT, 
     Val VARCHAR(50) 
) 
INSERT INTO @Table (ID,Val) SELECT 1, 'A' 
INSERT INTO @Table (ID,Val) SELECT 1, 'B' 
INSERT INTO @Table (ID,Val) SELECT 1, 'C' 
INSERT INTO @Table (ID,Val) SELECT 2, 'B' 
INSERT INTO @Table (ID,Val) SELECT 2, 'C' 

--Concat 
SELECT t.ID, 
     SUM(t.ID), 
     stuff(
       (
        select ',' + t1.Val 
        from @Table t1 
        where t1.ID = t.ID 
        order by t1.Val 
        for xml path('') 
       ),1,1,'') Concats 
FROM @Table t 
GROUP BY t.ID 
+1

La variable table est donnée à titre d'exemple ... Remplacez-la par la table que vous souhaitez agréger. –

+0

@Quandary: Je ne comprends pas vraiment votre plainte. La table temporaire est juste pour montrer comment cela fonctionne. Je ne connais pas le chemin XML, mais pourquoi est-ce important si c'est illisible, non intuitif, etc. si vous avez une solution courte et simple pour votre problème? –

+0

+1 @astander Plus j'essaie votre solution, plus je l'aime. Cela fonctionne immédiatement aucune préparation requise. Et jusqu'à présent, je n'ai pas vu une solution CLR complète prête à l'emploi. (Ma première impression était que c'était lent, mais cela a été causé par un artefact) –

3

trouvé cette link autour concaténation qui couvre des méthodes telles que

valeurs Concaténation lorsque le nombre d'éléments ne sont pas connus

  • méthode CTE récursive
  • Les méthodes XML blackbox
  • Utilisation du Common Language Runtime
  • Scalar UDF avec récursion
  • Table valeur UDF avec une boucle while
  • SQL dynamique
  • L'approche du curseur

approches non fiables

  • Scalar UDF avec T- Extension de mise à jour SQL
  • UDF scalaire avec conca variable tention dans SELECT

Bien qu'il ne couvre pas les fonctions d'aggerate, il peut être utile d'utiliser la concaténation pour vous aider à résoudre votre problème.

+4

@Quandary - Les 2 meilleures façons d'effectuer cette tâche ** sont ** 'XML PATH' et' CLR AGGREGATES'. Si vous écartez ces deux cas, vous devrez vous contenter des solutions les plus mauvaises dans la liste ci-dessus. Il n'y a pas d'autres moyens. –

+0

@ Martin Smith: Enfin trouvé la bonne solution au problème. Voir édition 2. –

0

Vous pourriez faire quelque chose comme ce que j'ai fait ci-dessous pour créer une fonction de concaténation d'agrégat personnalisée en langage T-SQL pur. Évidemment, je suis parti avec un nom de table codé en dur et un groupe par colonne, mais cela devrait illustrer l'approche. Il y a probablement un moyen de rendre cette fonction vraiment générique en utilisant TSQL dynamique construit à partir de paramètres d'entrée.

/* 
User defined function to help perform concatenations as an aggregate function 
Based on AdventureWorks2008R2 SalesOrderDetail table 
*/ 

--select * from sales.SalesOrderDetail 

IF EXISTS (SELECT * 
     FROM sysobjects 
     WHERE name = N'fnConcatenate') 
    DROP FUNCTION fnConcatenate 
GO 

CREATE FUNCTION fnConcatenate 
(
     @GroupByValue int 
     )      
returnS varchar(8000) 
as 

BEGIN 


    DECLARE @SqlString varchar(8000) 
    Declare @TempStore varchar(25) 
    select @SqlString ='' 

    Declare @MyCursor as Cursor 
      SET @MyCursor = CURSOR FAST_FORWARD 
      FOR 
      Select ProductID 
      From sales.SalesOrderDetail where SalesOrderID = @GroupByValue 
      order by SalesOrderDetailID asc 


     OPEN @MyCursor 

     FETCH NEXT FROM @MyCursor 
     INTO @TempStore 

     WHILE @@FETCH_STATUS = 0 
     BEGIN 


      select @SqlString = ltrim(rtrim(@TempStore)) +',' + ltrim(rtrim(@SqlString)) 
      FETCH NEXT FROM @MyCursor INTO @TempStore 

     END 

CLOSE @MyCursor 
DEALLOCATE @MyCursor 

RETURN @SqlString 

END 
GO 


select SalesOrderID, Sum(OrderQty), COUNT(*) as DetailCount , dbo.fnConcatenate(salesOrderID) as ConCatenatedProductList 
from sales.SalesOrderDetail 
where salesOrderID= 56805 
group by SalesOrderID 
+0

Vous ne pouvez PAS utiliser SQL dynamique dans une fonction ... –

+0

@Quandary; Quel SQL dynamique? – AMissico

3

Cette solution fonctionne sans avoir besoin de déployer à partir du fichier Visual studio ou dll dans le serveur.

Copiez-collez et ça marche!

http://groupconcat.codeplex.com/

dbo.GROUP_CONCAT(VALUE) 
dbo.GROUP_CONCAT_D(VALUE), DELIMITER) 
dbo.GROUP_CONCAT_DS(VALUE , DELIMITER , SORT_ORDER) 
dbo.GROUP_CONCAT_S(VALUE , SORT_ORDER) 
+0

Cela nécessite toujours l'activation de CLR dans SQL Server et le déploiement d'une DLL. – thomas