web-dev-qa-db-fra.com

T-SQL a-t-il une fonction d'agrégat pour concaténer des chaînes?

Doublons possibles:
fonction de type Implode dans SQL Server 2000?
Concaténer les valeurs de ligne T-SQL

J'ai une vue que j'interroge qui ressemble à ceci:

BuildingName    PollNumber
------------    ----------
Foo Centre      12        
Foo Centre      13
Foo Centre      14
Bar Hall        15
Bar Hall        16
Baz School      17

J'ai besoin d'écrire une requête qui regroupe BuildingNames et affiche une liste de PollNumbers comme ceci:

BuildingName    PollNumbers
------------    -----------
Foo Centre      12, 13, 14
Bar Hall        15, 16
Baz School      17

Comment puis-je faire cela dans T-SQL? Je préférerais ne pas recourir à une procédure stockée pour cela, car cela peut sembler excessif, mais je ne suis pas exactement un utilisateur de base de données. Il me semble que j'ai besoin d'une fonction d'agrégation telle que SUM () ou AVG (), mais je ne sais pas si T-SQL en a une. J'utilise SQL Server 2005.

71
Brant Bobby

pour SQL Server 2017 et versions ultérieures:

STRING_AGG ()

set nocount on;
declare @YourTable table (RowID int, HeaderValue int, ChildValue varchar(5))
insert into @YourTable VALUES (1,1,'CCC')
insert into @YourTable VALUES (2,2,'B<&>B')
insert into @YourTable VALUES (3,2,'AAA')
insert into @YourTable VALUES (4,3,'<br>')
insert into @YourTable VALUES (5,3,'A & Z')
set nocount off
SELECT
    t1.HeaderValue
        ,STUFF(
                   (SELECT
                        ', ' + t2.ChildValue
                        FROM @YourTable t2
                        WHERE t1.HeaderValue=t2.HeaderValue
                        ORDER BY t2.ChildValue
                        FOR XML PATH(''), TYPE
                   ).value('.','varchar(max)')
                   ,1,2, ''
              ) AS ChildValues
    FROM @YourTable t1
    GROUP BY t1.HeaderValue

SELECT
    HeaderValue, STRING_AGG(ChildValue,', ')
    FROM @YourTable
    GROUP BY HeaderValue

SORTIE:

HeaderValue 
----------- -------------
1           CCC
2           B<&>B, AAA
3           <br>, A & Z

(3 rows affected)

pour SQL Server 2005 et jusqu'en 2016, vous devez faire quelque chose comme ceci:

--Concatenation with FOR XML and eleminating control/encoded character expansion "& < >"
set nocount on;
declare @YourTable table (RowID int, HeaderValue int, ChildValue varchar(5))
insert into @YourTable VALUES (1,1,'CCC')
insert into @YourTable VALUES (2,2,'B<&>B')
insert into @YourTable VALUES (3,2,'AAA')
insert into @YourTable VALUES (4,3,'<br>')
insert into @YourTable VALUES (5,3,'A & Z')
set nocount off
SELECT
    t1.HeaderValue
        ,STUFF(
                   (SELECT
                        ', ' + t2.ChildValue
                        FROM @YourTable t2
                        WHERE t1.HeaderValue=t2.HeaderValue
                        ORDER BY t2.ChildValue
                        FOR XML PATH(''), TYPE
                   ).value('.','varchar(max)')
                   ,1,2, ''
              ) AS ChildValues
    FROM @YourTable t1
    GROUP BY t1.HeaderValue

SORTIE:

HeaderValue ChildValues
----------- -------------------
1           CCC
2           AAA, B<&>B
3           <br>, A & Z

(3 row(s) affected)

Aussi, faites attention, pas tous FOR XML PATH _ les concaténations gèrent correctement les caractères spéciaux XML, comme le montre l'exemple ci-dessus.

110
KM.

Il n'y a pas de fonction intégrée dans Sql Server, mais cela peut être obtenu en écrivant un agrégat défini par l'utilisateur. Cet article mentionne une telle fonction dans les exemples SQL Server: http://msdn.Microsoft.com/en-us/library/ms182741.aspx

A titre d'exemple, j'inclus le code d'un agrégat Concatenate. Pour l'utiliser, créez un projet de base de données dans Visual Studio, ajoutez le nouveau SqlAggregate et remplacez le code par l'exemple ci-dessous. Une fois déployé, vous devriez trouver un nouvel assemblage dans votre base de données et une fonction d'agrégat Concatenate

using System;
using System.Data.SqlTypes;
using System.IO;
using System.Text;
using Microsoft.SqlServer.Server;

[Serializable]
[SqlUserDefinedAggregate(Format.UserDefined, IsInvariantToNulls = true, IsInvariantToDuplicates = false, IsInvariantToOrder = false, MaxByteSize = 8000, Name = "Concatenate")]
public class Concatenate : IBinarySerialize
{
    private StringBuilder _intermediateResult;

    internal string IntermediateResult {
        get
        {
            return _intermediateResult.ToString();
        } 
    }

    public void Init()
    {
        _intermediateResult = new StringBuilder();
    }

    public void Accumulate(SqlString value)
    {
        if (value.IsNull) return;
        _intermediateResult.Append(value.Value);
    }

    public void Merge(Concatenate other)
    {
        if (null == other)
            return;

        _intermediateResult.Append(other._intermediateResult);
    }

    public SqlString Terminate()
    {
        var output = string.Empty;

        if (_intermediateResult != null && _intermediateResult.Length > 0)
            output = _intermediateResult.ToString(0, _intermediateResult.Length - 1);

        return new SqlString(output);
    }

    public void Read(BinaryReader reader)
    {
        if (reader == null) 
            throw new ArgumentNullException("reader");

        _intermediateResult = new StringBuilder(reader.ReadString());
    }

    public void Write(BinaryWriter writer)
    {
        if (writer == null) 
            throw new ArgumentNullException("writer");

        writer.Write(_intermediateResult.ToString());
    }
}

Pour l'utiliser, vous pouvez simplement écrire une requête agrégée:

create table test(
  id int identity(1,1) not null
    primary key
, class tinyint not null
, name nvarchar(120) not null )

insert into test values 
(1, N'This'),
(1, N'is'),
(1, N'just'),
(1, N'a'),
(1, N'test'),
(2, N','),
(3, N'do'),
(3, N'not'),
(3, N'be'),
(3, N'alarmed'),
(3, N','),
(3, N'this'),
(3, N'is'),
(3, N'just'),
(3, N'a'),
(3, N'test')


select dbo.Concatenate(name + ' ')
from test
group by class

drop table test

Le résultat de la requête est:

-- Output
-- ===================
-- This is just a test
-- ,
-- do not be alarmed , this is just a test

J'ai emballé la classe et l'ensemble en tant que script que vous pouvez trouver ici: https://Gist.github.com/FilipDeVos/5b7b4addea1812067b09

36
Filip De Vos