Hoe om 'n SQL Server funksie om "aan te sluit" verskeie rye van 'n subquery in 'n enkele Delimited veld te skep? [dupliseer]

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

Vra

    

Hierdie vraag het reeds 'n antwoord hier:

         

Om te illustreer, veronderstel dat ek twee tafels soos volg:

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

Ek wil 'n navraag aan die volgende resultate terugkeer skryf:

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

Ek weet dat dit gedoen kan word met behulp van die bediener kant cursors, dit wil sê:

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

Maar soos jy kan sien, dit verg 'n groot deel van die kode. Wat ek wil graag 'n generiese funksie wat jou sal toelaat om my om so iets te doen:

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

Is dit moontlik? Of iets soortgelyks?

Was dit nuttig?

Oplossing

As jy met behulp van SQL Server 2005, kan jy gebruik maak van die OM XML PATH opdrag.

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]

Dit is 'n baie makliker as die gebruik van 'n muis, en lyk redelik goed werk.

Ander wenke

Let daarop dat 'n href <= "https://stackoverflow.com/questions/6899/how-to-create-a-sql-server-function-to-join-multiple-rows-from-a-subquery- in # 6961 "> Matt se kode sal lei tot 'n ekstra komma aan die einde van die string; gebruik van saamvloei (of ISNULL vir die saak) soos aangedui in die skakel in Lance se post gebruik 'n soortgelyke metode, maar jou nie verlaat met 'n ekstra komma te verwyder. Ter wille van volledigheid, hier is die relevante kode van skakel Lance se sqlteam.com:

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

Ek weet nie belive daar is 'n manier om dit te doen binne een navraag, maar jy kan truuks speel soos hierdie met 'n tydelike veranderlike:

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

select @s

Dit is beslis minder kode as loop oor 'n wyser, en waarskynlik meer doeltreffend te maak.

In 'n enkele SQL navraag, sonder die gebruik van die OM XML klousule.
'N Gemeenskaplike Table Expression word gebruik om rekursief die resultate koppel.

-- 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

Van wat ek FOR XML kan sien (soos vroeër gepos) is die enigste manier om dit te doen as jy wil ook kies ander kolomme (wat ek sou raai die meeste sou) as die OP doen. Die gebruik van COALESCE(@var... nie toelaat dat die insluiting van ander kolomme.

Update: Te danke aan programmingsolutions.net daar is 'n manier om die "sleep" komma verwyder. Deur dit in 'n leidende komma en die gebruik van die STUFF funksie van MSSQL jy kan die eerste karakter (voorste komma) te vervang met 'n leë string soos hieronder:

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

Jy kan die gebruik VIR into sintaksis

d.w.z.

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

En die gevolg sal word

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

Dit sal selfs jou data werk bevat ongeldig XML karakters

die "}, {" ":" 'is veilig, want as jy data bevat "}, {" ":"', dit sal ontsnap word om "}, {\" _ \ ": \"

Jy kan vervang "," met 'n string separator


En in SQL Server 2017, blou SQL databasis

Jy kan die nuwe STRING_AGG gebruik funksie

Die onderstaande kode will work for 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

Ek het 'n oplossing gevind word deur die skep van die volgende funksie:

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

Gebruik:

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

VERSION NOTA: Jy moet met behulp van SQL Server 2005 of groter met Verenigbaarheid Vlak stel om 90 of meer vir hierdie oplossing

.

Sien hierdie MSDN artikel vir die eerste voorbeeld van die skep van 'n gebruiker-gedefinieerde totaal funksie wat 'n stel van string waardes geneem uit 'n kolom in 'n tafel Heg.

My nederige aanbeveling sou wees om uit te laat die bygevoeg komma sodat jy jou eie ad hoc delimiter kan gebruik, indien enige.

Met verwysing na die C # weergawe van Voorbeeld 1:

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

en

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

Op dié manier wanneer jy jou persoonlike totaal gebruik, kan jy kies om jou eie delimiter, of glad nie gebruik, soos:

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

Nota: Wees versigtig oor die bedrag van die data wat jy probeer om te verwerk in jou totaal. As jy probeer om duisende rye of baie baie groot data tipes koppel jy kan 'n NET Framework fout boodskap kry "[t] hy buffer is onvoldoende."

Met die ander antwoorde, die persoon lees die antwoord moet bewus wees van die tafel voertuig en skep die voertuig tafel en data om 'n oplossing te toets.

Hier is 'n voorbeeld wat SQL Server "Information_Schema.Columns" tafel gebruik. Deur die gebruik van hierdie oplossing, moet geen tafels geskep moet word of data bygevoeg. Hierdie voorbeeld skep 'n komme geskeide lys van kolom name vir al die tafels in die databasis.

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 

Mun se antwoord het nie gewerk nie vir my so ek 'n paar veranderinge aan die antwoord gemaak om dit te kry om te werk. Hoop dit help iemand. Die gebruik van 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]

Probeer hierdie navraag

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

As jy SQL Server 2005 loop, kan jy 'n persoonlike CLR totaal funksie om dit te hanteer.

C # weergawe:

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());
    }
}
Gelisensieer onder: CC-BY-SA met toeskrywing
Nie verbonde aan StackOverflow
scroll top