Wie erstelle ich eine SQL Server-Funktion, um mehrere Zeilen aus einer Unterabfrage in einem einzigen durch Trennzeichen getrennten Feld zu „verbinden“?[Duplikat]

StackOverflow https://stackoverflow.com/questions/6899

Frage

Auf diese Frage gibt es hier bereits eine Antwort:

Nehmen wir zur Veranschaulichung an, dass ich zwei Tabellen wie folgt habe:

VehicleID Name
1         Chuck
2         Larry

LocationID VehicleID City
1          1         New York
2          1         Seattle
3          1         Vancouver
4          2         Los Angeles
5          2         Houston

Ich möchte eine Abfrage schreiben, um die folgenden Ergebnisse zurückzugeben:

VehicleID Name    Locations
1         Chuck   New York, Seattle, Vancouver
2         Larry   Los Angeles, Houston

Ich weiß, dass dies mit serverseitigen Cursorn möglich ist, also:

DECLARE @VehicleID int
DECLARE @VehicleName varchar(100)
DECLARE @LocationCity varchar(100)
DECLARE @Locations varchar(4000)
DECLARE @Results TABLE
(
  VehicleID int
  Name varchar(100)
  Locations varchar(4000)
)

DECLARE VehiclesCursor CURSOR FOR
SELECT
  [VehicleID]
, [Name]
FROM [Vehicles]

OPEN VehiclesCursor

FETCH NEXT FROM VehiclesCursor INTO
  @VehicleID
, @VehicleName
WHILE @@FETCH_STATUS = 0
BEGIN

  SET @Locations = ''

  DECLARE LocationsCursor CURSOR FOR
  SELECT
    [City]
  FROM [Locations]
  WHERE [VehicleID] = @VehicleID

  OPEN LocationsCursor

  FETCH NEXT FROM LocationsCursor INTO
    @LocationCity
  WHILE @@FETCH_STATUS = 0
  BEGIN
    SET @Locations = @Locations + @LocationCity

    FETCH NEXT FROM LocationsCursor INTO
      @LocationCity
  END
  CLOSE LocationsCursor
  DEALLOCATE LocationsCursor

  INSERT INTO @Results (VehicleID, Name, Locations) SELECT @VehicleID, @Name, @Locations

END     
CLOSE VehiclesCursor
DEALLOCATE VehiclesCursor

SELECT * FROM @Results

Wie Sie sehen, erfordert dies jedoch sehr viel Code.Was ich möchte, ist eine generische Funktion, die es mir ermöglichen würde, so etwas zu tun:

SELECT VehicleID
     , Name
     , JOIN(SELECT City FROM Locations WHERE VehicleID = Vehicles.VehicleID, ', ') AS Locations
FROM Vehicles

Ist das möglich?Oder etwas ähnliches?

War es hilfreich?

Lösung

Wenn Sie SQL Server 2005 verwenden, können Sie den Befehl FOR XML PATH verwenden.

SELECT [VehicleID]
     , [Name]
     , (STUFF((SELECT CAST(', ' + [City] AS VARCHAR(MAX)) 
         FROM [Location] 
         WHERE (VehicleID = Vehicle.VehicleID) 
         FOR XML PATH ('')), 1, 2, '')) AS Locations
FROM [Vehicle]

Es ist viel einfacher als die Verwendung eines Cursors und scheint ziemlich gut zu funktionieren.

Andere Tipps

Beachten Sie, dass Matts Code führt zu einem zusätzlichen Komma am Ende der Zeichenfolge;Wenn Sie COALESCE (oder auch ISNULL) verwenden, wie im Link in Lances Beitrag gezeigt, wird eine ähnliche Methode verwendet, Sie müssen jedoch kein zusätzliches Komma entfernen.Der Vollständigkeit halber hier der relevante Code aus Lances Link auf sqlteam.com:

DECLARE @EmployeeList varchar(100)
SELECT @EmployeeList = COALESCE(@EmployeeList + ', ', '') + 
    CAST(EmpUniqueID AS varchar(5))
FROM SalesCallsEmployees
WHERE SalCal_UniqueID = 1

Ich glaube nicht, dass es eine Möglichkeit gibt, dies innerhalb einer Abfrage zu tun, aber Sie können mit einer temporären Variablen solche Tricks spielen:

declare @s varchar(max)
set @s = ''
select @s = @s + City + ',' from Locations

select @s

Es ist definitiv weniger Code als das Gehen über einen Cursor und wahrscheinlich effizienter.

In einer einzelnen SQL-Abfrage, ohne die FOR XML-Klausel zu verwenden.
Ein gemeinsamer Tabellenausdruck wird verwendet, um die Ergebnisse rekursiv zu verketten.

-- rank locations by incrementing lexicographical order
WITH RankedLocations AS (
  SELECT
    VehicleID,
    City,
    ROW_NUMBER() OVER (
        PARTITION BY VehicleID 
        ORDER BY City
    ) Rank
  FROM
    Locations
),
-- concatenate locations using a recursive query
-- (Common Table Expression)
Concatenations AS (
  -- for each vehicle, select the first location
  SELECT
    VehicleID,
    CONVERT(nvarchar(MAX), City) Cities,
    Rank
  FROM
    RankedLocations
  WHERE
    Rank = 1

  -- then incrementally concatenate with the next location
  -- this will return intermediate concatenations that will be 
  -- filtered out later on
  UNION ALL

  SELECT
    c.VehicleID,
    (c.Cities + ', ' + l.City) Cities,
    l.Rank
  FROM
    Concatenations c -- this is a recursion!
    INNER JOIN RankedLocations l ON
        l.VehicleID = c.VehicleID 
        AND l.Rank = c.Rank + 1
),
-- rank concatenation results by decrementing length 
-- (rank 1 will always be for the longest concatenation)
RankedConcatenations AS (
  SELECT
    VehicleID,
    Cities,
    ROW_NUMBER() OVER (
        PARTITION BY VehicleID 
        ORDER BY Rank DESC
    ) Rank
  FROM 
    Concatenations
)
-- main query
SELECT
  v.VehicleID,
  v.Name,
  c.Cities
FROM
  Vehicles v
  INNER JOIN RankedConcatenations c ON 
    c.VehicleID = v.VehicleID 
    AND c.Rank = 1

Soweit ich sehen kann FOR XML (wie zuvor gepostet) ist die einzige Möglichkeit, dies zu tun, wenn Sie auch andere Spalten auswählen möchten (was meiner Meinung nach die meisten tun würden), wie es das OP tut.Benutzen COALESCE(@var... erlaubt keine Aufnahme anderer Spalten.

Aktualisieren:Dank an programmingsolutions.net Es gibt eine Möglichkeit, das „nachgestellte“ Komma zu entfernen.Indem Sie daraus ein führendes Komma machen und das verwenden STUFF Mit der MSSQL-Funktion können Sie das erste Zeichen (führendes Komma) wie folgt durch eine leere Zeichenfolge ersetzen:

stuff(
    (select ',' + Column 
     from Table
         inner where inner.Id = outer.Id 
     for xml path('')
), 1,1,'') as Values

In SQL Server 2005

SELECT Stuff(
  (SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE)
  .value('text()[1]','nvarchar(max)'),1,2,N'')

In SQL Server 2016

du kannst den ... benutzen FOR JSON-Syntax

d.h.

SELECT per.ID,
Emails = JSON_VALUE(
   REPLACE(
     (SELECT _ = em.Email FROM Email em WHERE em.Person = per.ID FOR JSON PATH)
    ,'"},{"_":"',', '),'$[0]._'
) 
FROM Person per

Und das Ergebnis wird sein

Id  Emails
1   abc@gmail.com
2   NULL
3   def@gmail.com, xyz@gmail.com

Dies funktioniert auch dann, wenn Ihre Daten ungültige XML-Zeichen enthalten

Die '"},{"„:“ ist sicher, denn wenn Ihre Daten „“},{“ enthalten":"', es wird mit Escapezeichen versehen in "},{\"_\":\"

Sie können „,“ durch ein beliebiges Zeichenfolgentrennzeichen ersetzen


Und in SQL Server 2017 Azure SQL-Datenbank

Sie können das Neue verwenden STRING_AGG-Funktion

Der folgende Code funktioniert für SQL Server 2000/2005/2008

CREATE FUNCTION fnConcatVehicleCities(@VehicleId SMALLINT)
RETURNS VARCHAR(1000) AS
BEGIN
  DECLARE @csvCities VARCHAR(1000)
  SELECT @csvCities = COALESCE(@csvCities + ', ', '') + COALESCE(City,'')
  FROM Vehicles 
  WHERE VehicleId = @VehicleId 
  return @csvCities
END

-- //Once the User defined function is created then run the below sql

SELECT VehicleID
     , dbo.fnConcatVehicleCities(VehicleId) AS Locations
FROM Vehicles
GROUP BY VehicleID

Ich habe eine Lösung gefunden, indem ich die folgende Funktion erstellt habe:

CREATE FUNCTION [dbo].[JoinTexts]
(
  @delimiter VARCHAR(20) ,
  @whereClause VARCHAR(1)
)
RETURNS VARCHAR(MAX)
AS 
BEGIN
    DECLARE @Texts VARCHAR(MAX)

    SELECT  @Texts = COALESCE(@Texts + @delimiter, '') + T.Texto
    FROM    SomeTable AS T
    WHERE   T.SomeOtherColumn = @whereClause

    RETURN @Texts
END
GO

Verwendung:

SELECT dbo.JoinTexts(' , ', 'Y')

VERSIONSHINWEIS:Für diese Lösung müssen Sie SQL Server 2005 oder höher mit einem Kompatibilitätsgrad von 90 oder höher verwenden.

Sieh dir das an MSDN-Artikel für das erste Beispiel für die Erstellung einer benutzerdefinierten Aggregatfunktion, die eine Reihe von Zeichenfolgenwerten aus einer Spalte in einer Tabelle verkettet.

Meine bescheidene Empfehlung wäre, das angehängte Komma wegzulassen, damit Sie, falls vorhanden, Ihr eigenes Ad-hoc-Trennzeichen verwenden können.

Bezogen auf die C#-Version von Beispiel 1:

change:  this.intermediateResult.Append(value.Value).Append(',');
    to:  this.intermediateResult.Append(value.Value);

Und

change:  output = this.intermediateResult.ToString(0, this.intermediateResult.Length - 1);
    to:  output = this.intermediateResult.ToString();

Auf diese Weise können Sie bei der Verwendung Ihres benutzerdefinierten Aggregats wählen, ob Sie Ihr eigenes oder gar kein Trennzeichen verwenden möchten, z. B.:

SELECT dbo.CONCATENATE(column1 + '|') from table1

NOTIZ: Seien Sie vorsichtig mit der Menge der Daten, die Sie in Ihrem Aggregat verarbeiten möchten.Wenn Sie versuchen, Tausende von Zeilen oder viele sehr große Datentypen zu verketten, erhalten Sie möglicherweise eine .NET Framework-Fehlermeldung mit der Meldung „Der Puffer reicht nicht aus.“

Bei den anderen Antworten muss die Person, die die Antwort liest, die Fahrzeugtabelle kennen und die Fahrzeugtabelle und Daten erstellen, um eine Lösung zu testen.

Unten sehen Sie ein Beispiel, das die SQL Server-Tabelle „Information_Schema.Columns“ verwendet.Durch den Einsatz dieser Lösung müssen keine Tabellen erstellt oder Daten hinzugefügt werden.In diesem Beispiel wird eine durch Kommas getrennte Liste mit Spaltennamen für alle Tabellen in der Datenbank erstellt.

SELECT
    Table_Name
    ,STUFF((
        SELECT ',' + Column_Name
        FROM INFORMATION_SCHEMA.Columns Columns
        WHERE Tables.Table_Name = Columns.Table_Name
        ORDER BY Column_Name
        FOR XML PATH ('')), 1, 1, ''
    )Columns
FROM INFORMATION_SCHEMA.Columns Tables
GROUP BY TABLE_NAME 

Muns Antwort hat bei mir nicht funktioniert, also habe ich einige Änderungen an dieser Antwort vorgenommen, damit sie funktioniert.Hoffe, das hilft jemandem.Verwendung von SQL Server 2012:

SELECT [VehicleID]
     , [Name]
     , STUFF((SELECT DISTINCT ',' + CONVERT(VARCHAR,City) 
         FROM [Location] 
         WHERE (VehicleID = Vehicle.VehicleID) 
         FOR XML PATH ('')), 1, 2, '') AS Locations
FROM [Vehicle]

Probieren Sie diese Abfrage aus

SELECT v.VehicleId, v.Name, ll.LocationList
FROM Vehicles v 
LEFT JOIN 
    (SELECT 
     DISTINCT
        VehicleId,
        REPLACE(
            REPLACE(
                REPLACE(
                    (
                        SELECT City as c 
                        FROM Locations x 
                        WHERE x.VehicleID = l.VehicleID FOR XML PATH('')
                    ),    
                    '</c><c>',', '
                 ),
             '<c>',''
            ),
        '</c>', ''
        ) AS LocationList
    FROM Locations l
) ll ON ll.VehicleId = v.VehicleId

Wenn Sie SQL Server 2005 ausführen, können Sie eine schreiben benutzerdefinierte CLR-Aggregatfunktion um damit umzugehen.

C#-Version:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Text;
using Microsoft.SqlServer.Server;
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(Format.UserDefined,MaxByteSize=8000)]
public class CSV:IBinarySerialize
{
    private StringBuilder Result;
    public void Init() {
        this.Result = new StringBuilder();
    }

    public void Accumulate(SqlString Value) {
        if (Value.IsNull) return;
        this.Result.Append(Value.Value).Append(",");
    }
    public void Merge(CSV Group) {
        this.Result.Append(Group.Result);
    }
    public SqlString Terminate() {
        return new SqlString(this.Result.ToString());
    }
    public void Read(System.IO.BinaryReader r) {
        this.Result = new StringBuilder(r.ReadString());
    }
    public void Write(System.IO.BinaryWriter w) {
        w.Write(this.Result.ToString());
    }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top