2009-12-09 17 views
30

Ich versuche, einige Daten für einen Bericht zusammenzufassen und die Zeilenwerte einer der Tabellen zu verketten. Hier ist die grundlegende Tabellenstruktur:Verketten von Zeilenwerten T-SQL

Bewertungen

ReviewID 
ReviewDate 

Reviewer

ReviewerID 
ReviewID 
UserID 

Benutzer

UserID 
FName 
LName 

Dies ist eine M: M-Beziehung. Jeder Review kann viele Reviewer haben; Jeder Benutzer kann mit vielen Bewertungen verknüpft werden.

Grundsätzlich möchte ich nur Reviews.ReviewID, Reviews.ReviewDate und eine verkettete Zeichenfolge der FNames aller zugeordneten Benutzer für diese Überprüfung (durch Kommas getrennt) sehen.

Statt:

ReviewID---ReviewDate---User 
1----------12/1/2009----Bob 
1----------12/1/2009----Joe 
1----------12/1/2009----Frank 
2----------12/9/2009----Sue 
2----------12/9/2009----Alice 

Letzte:

ReviewID---ReviewDate----Users 
1----------12/1/2009-----Bob, Joe, Frank 
2----------12/9/2009-----Sue, Alice 

ich this Artikel beschreibt einige Möglichkeiten gefunden haben, dies zu tun, aber die meisten von ihnen scheinen nur mit einem Tisch zu beschäftigen, nicht mehrere ; Leider ist mein SQL-Fu nicht stark genug, um diese an meine Verhältnisse anzupassen. Ich bin besonders an dem Beispiel auf dieser Seite interessiert, das FOR XML PATH() verwendet, da es am saubersten und geradlinigsten aussieht.

SELECT p1.CategoryId, 
(SELECT ProductName + ', ' 
    FROM Northwind.dbo.Products p2 
    WHERE p2.CategoryId = p1.CategoryId 
    ORDER BY ProductName FOR XML PATH('') 
) AS Products 
FROM Northwind.dbo.Products p1 
GROUP BY CategoryId; 

Kann mir jemand dabei helfen? Jede Hilfe würde sehr geschätzt werden!

+0

Ähnlich http://stackoverflow.com/questions/122942/how-to-return-multiple -values-in-one-column-t-sql und http://stackoverflow.com/questions/451415/simulating-groupconcat-mysql-function-in-ms-sql-server-2005 – VolkerK

Antwort

32

Werfen Sie einen Blick auf diese

DECLARE @Reviews TABLE(
     ReviewID INT, 
     ReviewDate DATETIME 
) 

DECLARE @Reviewers TABLE(
     ReviewerID INT, 
     ReviewID INT, 
     UserID INT 
) 

DECLARE @Users TABLE(
     UserID INT, 
     FName VARCHAR(50), 
     LName VARCHAR(50) 
) 

INSERT INTO @Reviews SELECT 1, '12 Jan 2009' 
INSERT INTO @Reviews SELECT 2, '25 Jan 2009' 

INSERT INTO @Users SELECT 1, 'Bob', '' 
INSERT INTO @Users SELECT 2, 'Joe', '' 
INSERT INTO @Users SELECT 3, 'Frank', '' 
INSERT INTO @Users SELECT 4, 'Sue', '' 
INSERT INTO @Users SELECT 5, 'Alice', '' 

INSERT INTO @Reviewers SELECT 1, 1, 1 
INSERT INTO @Reviewers SELECT 2, 1, 2 
INSERT INTO @Reviewers SELECT 3, 1, 3 
INSERT INTO @Reviewers SELECT 4, 2, 4 
INSERT INTO @Reviewers SELECT 5, 2, 5 

SELECT *, 
     ( 
      SELECT u.FName + ',' 
      FROM @Users u INNER JOIN 
        @Reviewers rs ON u.UserID = rs.UserID 
      WHERE rs.ReviewID = r.ReviewID 
      FOR XML PATH('') 
     ) AS Products 
FROM @Reviews r 
+5

dies behandelt nicht ordnungsgemäß XML-Spezial Zeichen wie '>' und '&'. Wenn also die Daten "Frank & Bill" enthielten, würden Sie in der Ergebnismenge "Frank & Bill" erhalten. Es gibt eine nette Möglichkeit, damit umzugehen, siehe: http://stackoverflow.com/questions/5031204/does-t-sql-have-an-aggregate-function-to-concatenate-strings/5031297#5031297 –

3

A UDF eine ok Art und Weise zu lösen, das wäre.

Definieren Sie einfach eine T-SQL-Funktion (UDF), die einen int-Parameter (Produkt-ID) verwendet und eine Zeichenfolge (Verkettung der mit dem Produkt verknüpften Namen) zurückgibt. Wenn der Name Ihrer Methode GetProductNames lautet, sieht Ihre Abfrage möglicherweise so aus :

SELECT p1.CategoryId, dbo.GetProductNames(p1.CategoryId) 
FROM Northwind.dbo.Products p1 
GROUP BY CategoryId 
+0

und wie würde dass UDF aussehen? Ich denke nicht, dass das wirklich nötig wäre. –

+2

@ marc: wahr. Ein UDF ist nur eine von hundert Möglichkeiten, um dieses Problem zu lösen. Ich dachte, es wäre eine gute Lösung, um eine SQL n00b zu präsentieren. –

+0

Das Problem mit einem UDF ist, dass es entweder Dynamic SQL verwenden oder pro Abfragetyp vorhanden sein muss. SQL Server unterstützt benutzerdefinierte CLR AGGREGATE-Funktionen .. auf Kosten der Verwendung einer zusätzlichen CLR-Assembly .. –

6

Es gibt 3 Möglichkeiten, wie ich Daten aufrollen kann, wie Sie beschrieben haben, 1.verwenden Sie einen Cursor, verwenden Sie eine UDF oder verwenden Sie die a Benutzerdefiniertes Aggregat (in .NET CLR geschrieben).
Der Cursor und UDF sind ziemlich langsam. (ungefähr 0,1 Sekunden pro Reihe). Das benutzerdefinierte CLR-Aggregat ist überraschend schnell. (ca. 0,001 s pro Zeile)

Microsoft liefert den Code (um genau das zu tun, was Sie wollen) als Teil des SDK für SQL 2005. Wenn Sie es installiert haben, sollten Sie in der Lage sein, den Code in diesem Ordner zu finden: C: \ Programme \ Microsoft SQL Server \ 90 \ Beispiele \ Engine \ Programmability \ CLR \ StringUtilities. Möglicherweise möchten Sie auch diesen Artikel in MSDN.Er spricht über die benutzerdefinierte Aggregat installieren und aktivieren sie: http://msdn.microsoft.com/en-us/library/ms161551(SQL.90).aspx

Nachdem Sie das benutzerdefinierte Aggregat kompilieren und installieren, sollten Sie in der Lage, so fragen:

SELECT Reviews.ReviewID, ReviewDate, dbo.StringUtilities.Concat(FName) AS [User] 
FROM Reviews INNER JOIN Reviewers ON Reviews.ReviewID = Reviewers.ReviewID 
    INNER JOIN Users ON Reviews.UserID = Users.UserID 
GROUP BY ReviewID, ReviewDate; 

und ein Ergebnis eingestellt, wie Sie zeigte (siehe oben)

+1

+1 .. leider erfordert es Unordnung herum mit dem CLR-Zeug. (Es wäre schön, wenn ein UDF das Ziel einer benutzerdefinierten AGGREGATE-Funktion sein könnte: - /) –

3

Versuchen Sie folgendes:

Declare @Revs Table 
(RevId int Priimary Key Not Null, 
    RevDt DateTime Null, 
    users varChar(1000) default '') 

Insert @Revs (RevId, RevDt) 
Select Distinct ReviewId, ReviewDate 
From Reviews 
Declare @UId Integer 
Set @Uid = 0 
While Exists (Select * From Users 
       Where UserID > @Uid) 
Begin 
    Update @Revs Set 
     users = users + u.fName + ', ' 
    From @Revs R 
     Join Reviewers uR On ur.ReviewId = R.RId 
     Join users u On u.UserId = uR.UserId 
    Where uR.UserId = @UId 
    Select @Uid = Min(UserId) 
    From users 
    Where UserId > @UId 
    End 
    Select * From @Revs 
3
Select R.ReviewID, ReviewDate 
, (Select FName + ', ' 
    from Users 
    where UserID = R.UserID 
    order by FName FOR XML PATH(') 
) as [Users] 
from Reviews 
inner join Reviewers AS R 
    On Reviews.ReviewID = R.ReviewID 
Group By R.ReviewID, ReviewDate; 
2

Erstellen Sie eine temporäre Tabelle, in der die Daten gespeichert werden. Verwenden Sie dann die FOR XML PATH-Methode. Die äußere Abfrage wird benötigt, um das letzte Komma von der Liste zu entfernen.

CREATE TABLE #ReviewInfo (
ReviewId INT, 
ReviewDate DATETIME, 
Reviewer VARCHAR(1000)) 

INSERT INTO #ReviewInfo (ReviewId, ReviewDate, Reviewer) 
SELECT r.ReviewId, r.ReviewDate, u.FName 
FROM Reviews r 
JOIN Reviewers rs ON r.ReviewId = rs.ReviewId 
JOIN Users u ON u.UserId = rs.UserId 

SELECT ReviewId, ReviewDate, LEFT(Users, LEN(Users)-1) 
FROM (
SELECT ReviewId, ReviewDate, 
(
    SELECT Reviewer + ', ' 
    FROM #ReviewInfo ri2 
    WHERE ri2.ReviewId = ri1.ReviewId 
    ORDER BY Reviewer 
    FOR XML PATH('') 
) AS Users 
FROM #ReviewInfo ri1 
GROUP BY ReviewId, ReviewDate 
) a 

DROP TABLE #ReviewInfo 
20

Stellt sich heraus, es einen noch einfacheren Weg, dies zu tun, die keine UDF erfordern:

select replace(replace(replace((cast((
     select distinct columnName as X 
     from tableName 
     for xml path('')) as varchar(max))), 
    '</X><X>', ', '),'<X>', ''),'</X>','') 
+1

Dies ist der sauberste Weg, den ich gesehen habe, der keine UDF erfordert. Und es war sehr schnell in meinem Fall. Vielen Dank! – AaronShockley

+0

Ich habe überall nach diesem Schnipsel aus reinem Gold gesucht. Vielen Dank. –

10

Hatte ähnliches Problem und fand 15 Minuten lang mit dem Code eine süße Lösung nach dem Spiel

declare @result varchar(1000) 
select @result = COALESCE(@result+','+A.col1, A.col1) 
       FROM ( select col1 
         from [table] 
       ) A 
select @result 

Berechnungsergebnis als Wert1, Wert2, Wert3, value4

Genießen;)

+0

Gut gemacht - sehr ordentlich! @Sesame, ich würde empfehlen, die akzeptierte Antwort zu ändern! –

+2

Dies wird von Microsoft nicht unterstützt und kann zu unerwarteten Ergebnissen führen.Siehe https://www.simple-talk.com/sql/t-sql-programming/concatenating-row-values-in-transact-sql/ oder mein Blog http://marc.durdin.net/2015/07/ Verketten-Zeichenfolgen-in-SQL-Server-oder-undefined-Behaviour-by-Design/für weitere Diskussion. –

5
select p1.Availability ,COUNT(*), 
(select name+',' from AdventureWorks2008.Production.Location p2 where 
p1.Availability=p2.Availability for XML path(''),type).value('.','varchar(max)') 
as Name from AdventureWorks2008.Production.Location p1 group by Availability 

Ergebnis

Availability COUNT  Name 
--------------------------------------------------------------------------------- 
0.00 7 Tool Crib,Sheet Metal Racks,Paint Shop,Paint Storage,Metal 
        Storage,Miscellaneous Storage,Finished Goods Storage, 
80.00 1 Specialized Paint, 
96.00 1 Frame Forming, 
108.00 1 Frame Welding, 
120.00 4 Debur and Polish,Paint,Subassembly,Final Assembly, 
2
select 
     p1.Availability, 
     COUNT(*), 
     (
      select name+',' 
      from AdventureWorks2008.Production.Location p2 
      where p1.Availability=p2.Availability 
      for XML path(''),type 
    ).value('.','varchar(max)') as Name 
from AdventureWorks2008.Production.Location p1 
group by Availability 
4

SqlServer 2017 hat nun STRING_AGG, die mehrere Zeichenfolgen in eine unter Verwendung eines gegebenen Separators aggregiert.

0

Wenn die Anzahl der Elemente ist klein diese ROW_NUMBER getan werden kann() OVER PARTITION BY:

declare @t table (col1 int, col2 varchar) 
insert into @t VALUES (1,'A') 
insert into @t VALUES (1,'B') 
insert into @t VALUES (1,'C') 
insert into @t VALUES (1,'D') 
insert into @t VALUES (1,'E') 
insert into @t VALUES (2,'X') 
insert into @t VALUES (3,'Y') 

select col1, 
    MAX(CASE seq WHEN 1 THEN  col2 ELSE '' END) + 
    MAX(CASE seq WHEN 2 THEN ', ' + col2 ELSE '' END) + 
    MAX(CASE seq WHEN 3 THEN ', ' + col2 ELSE '' END) + 
    MAX(CASE seq WHEN 4 THEN ', ' + col2 ELSE '' END) + 
    MAX(CASE seq WHEN 5 THEN ',...' ELSE '' END) 
    as col2 
from (
    select col1, col2, ROW_NUMBER() OVER (PARTITION BY col1 ORDER BY col2) seq 
    from @t 
    group by col1, col2 
) x 
group by col1 
Verwandte Themen