85

I've got two tables:

TableA
------
ID,
Name

TableB
------
ID,
SomeColumn,
TableA_ID (FK for TableA)

The relationship is one row of TableA - many of TableB.

Now, I want to see a result like this:

ID     Name      SomeColumn

1.     ABC       X, Y, Z (these are three different rows)
2.     MNO       R, S

This won't work (multiple results in a subquery):

SELECT ID,
       Name, 
       (SELECT SomeColumn FROM TableB WHERE F_ID=TableA.ID)
FROM TableA

This is a trivial problem if I do the processing on the client side. But this will mean I will have to run X queries on every page, where X is the number of results of TableA.

Note that I can't simply do a GROUP BY or something similar, as it will return multiple results for rows of TableA.

I'm not sure if a UDF, utilizing COALESCE or something similar might work?

Mark
  • 2,041
  • 2
  • 18
  • 35
Donnie Thomas
  • 3,888
  • 8
  • 47
  • 70

10 Answers10

137

Even this will serve the purpose

Sample data

declare @t table(id int, name varchar(20),somecolumn varchar(MAX))
insert into @t
    select 1,'ABC','X' union all
    select 1,'ABC','Y' union all
    select 1,'ABC','Z' union all
    select 2,'MNO','R' union all
    select 2,'MNO','S'

Query:

SELECT ID,Name,
    STUFF((SELECT ',' + CAST(T2.SomeColumn AS VARCHAR(MAX))
     FROM @T T2 WHERE T1.id = T2.id AND T1.name = T2.name
     FOR XML PATH('')),1,1,'') SOMECOLUMN
FROM @T T1
GROUP BY id,Name

Output:

ID  Name    SomeColumn
1   ABC     X,Y,Z
2   MNO     R,S
Prahalad Gaggar
  • 11,389
  • 16
  • 53
  • 71
priyanka.sarkar
  • 25,766
  • 43
  • 127
  • 173
  • 13
    Nt sure why this hasn't been picked up as it solves the problem without requiring a user function. You can see the same idea expressed here http://codecorner.galanter.net/2009/06/25/t-sql-string-aggregate-in-sql-server/ which predates this answer and so might be the "original" – Paul D'Ambra May 19 '10 at 07:44
  • 1
    Same here, not sure why this isn't rated higher – Marcel May 28 '10 at 06:47
  • 1
    Hi priyanka, can you tell me if and why the "and t1.name = t2.name" clause is necessary here? – Koen Jul 07 '10 at 09:35
  • 2
    This is excellent. I was looking to optimise a UDF function as listed in the accepted answer which was killing my server. I went from a 102 second search down to less than 1. Execution plan comparison was 78%-22% but that doesn't relate to the execution time... – toxaq Sep 25 '11 at 00:31
  • Just a reminder that you'll need that leading ',' or else you'll end up with angle brackets in your output. – Tim Scarborough Jan 27 '12 at 18:49
  • 1
    Could you explain this query? Perhaps with links to MSDN also What does `FOR XML PATH('')` do? I've never seen that before – Luke T O'Brien Dec 14 '16 at 15:45
45

1. Create the UDF:

CREATE FUNCTION CombineValues
(
    @FK_ID INT -- The foreign key from TableA which is used 
               -- to fetch corresponding records
)
RETURNS VARCHAR(8000)
AS
BEGIN
DECLARE @SomeColumnList VARCHAR(8000);

SELECT @SomeColumnList =
    COALESCE(@SomeColumnList + ', ', '') + CAST(SomeColumn AS varchar(20)) 
FROM TableB C
WHERE C.FK_ID = @FK_ID;

RETURN 
(
    SELECT @SomeColumnList
)
END

2. Use in subquery:

SELECT ID, Name, dbo.CombineValues(FK_ID) FROM TableA

3. If you are using stored procedure you can do like this:

CREATE PROCEDURE GetCombinedValues
 @FK_ID int
As
BEGIN
DECLARE @SomeColumnList VARCHAR(800)
SELECT @SomeColumnList =
    COALESCE(@SomeColumnList + ', ', '') + CAST(SomeColumn AS varchar(20)) 
FROM TableB
WHERE FK_ID = @FK_ID 

Select *, @SomeColumnList as SelectedIds
    FROM 
        TableA
    WHERE 
        FK_ID = @FK_ID 
END
Saurabh Solanki
  • 2,146
  • 18
  • 31
Donnie Thomas
  • 3,888
  • 8
  • 47
  • 70
  • 1
    This still feels like a hack. I'm still using subqueries so, there's still a lot of extra processing going on. I'm sure a better solution exists (table restructuring, or another way of looking at the problem). – Donnie Thomas Sep 21 '08 at 18:13
  • 1
    I wouldn't call this a hack. It's more efficient than a cursor would be, and it lacks the overhead that would be necessary to create a temporary table with the data structured the way you want it. – Scott Lawrence Nov 18 '08 at 15:13
  • 1
    Shame the columns can't be parameters. As it stands you'll need to make a function for every child relationship! – John Paul Jones Mar 05 '09 at 12:02
  • 1
    That's ok - I need to combine only these particular columns. The rest are 'traditional' joins. – Donnie Thomas Mar 10 '09 at 16:22
  • I don't recall a best way to do this without this method. – aF. Nov 11 '11 at 15:01
  • Good stuff, thanks. I use this to work with the jquery tag-it library. – William Walseth Feb 16 '12 at 17:05
14

In MySQL there is a group_concat function that will return what you're asking for.

SELECT TableA.ID, TableA.Name, group_concat(TableB.SomeColumn) 
as SomColumnGroup FROM TableA LEFT JOIN TableB ON 
TableB.TableA_ID = TableA.ID
Jacob
  • 10,452
  • 5
  • 22
  • 11
  • 1
    This would've been perfect, if there was a similar function in SQL Server. As it stands, I'm using Ben's solution to hammer together what I want. – Donnie Thomas Sep 21 '08 at 16:42
11

I think you are on the right track with COALESCE. See here for an example of building a comma-delimited string:

http://www.sqlteam.com/article/using-coalesce-to-build-comma-delimited-string

Ben Hoffstein
  • 102,129
  • 8
  • 104
  • 120
  • 2
    Awesome! I had seen some links discussing COALESCE, but they involved creating UDFs with triggers. The link you've submitted has the key, with a single SELECT statement. I'm adding an answer with the correct solution, so that it's easier for others to find. Thanks! – Donnie Thomas Sep 21 '08 at 16:36
  • 1
    Hi Ben, I think the answer needs a bit more detail, namely how to create the UDF, etc. Once I figure this out, I'll add the solution as a community editable answer. Please feel free to edit it, after which I'll accept that as the answer. Sorry for the confusion. – Donnie Thomas Sep 21 '08 at 17:12
0

You may need to provide some more details for a more precise response.

Since your dataset seems kind of narrow, you might consider just using a row per result and performing the post-processing at the client.

So if you are really looking to make the server do the work return a result set like

ID       Name       SomeColumn
1        ABC        X
1        ABC        Y
1        ABC        Z
2        MNO        R
2        MNO        S

which of course is a simple INNER JOIN on ID

Once you have the resultset back at the client, maintain a variable called CurrentName and use that as a trigger when to stop collecting SomeColumn into the useful thing you want it to do.

Bill
  • 1,738
  • 10
  • 22
  • I thought of this, but wasn't very sure if this is an elegant solution - I'd like to have SQL Server return the properly constructed resultset, not something that'll need to be processed further. Would you require additional details? I've simplified the table structure, but I think you've got it. – Donnie Thomas Sep 21 '08 at 16:31
0

Assuming you only have WHERE clauses on table A create a stored procedure thus:

SELECT Id, Name From tableA WHERE ...

SELECT tableA.Id AS ParentId, Somecolumn 
FROM tableA INNER JOIN tableB on TableA.Id = TableB.F_Id 
WHERE ...

Then fill a DataSet ds with it. Then

ds.Relations.Add("foo", ds.Tables[0].Columns("Id"), ds.Tables[1].Columns("ParentId"));

Finally you can add a repeater in the page that puts the commas for every line

 <asp:DataList ID="Subcategories" DataKeyField="ParentCatId" 
DataSource='<%# Container.DataItem.CreateChildView("foo") %>' RepeatColumns="1"
 RepeatDirection="Horizontal" ItemStyle-HorizontalAlign="left" ItemStyle-VerticalAlign="top" 
runat="server" >

In this way you will do it client side but with only one query, passing minimal data between database and frontend

Sklivvz
  • 30,601
  • 24
  • 116
  • 172
0

I tried the solution priyanka.sarkar mentioned and the didn't quite get it working as the OP asked. Here's the solution I ended up with:

SELECT ID, 
        SUBSTRING((
            SELECT ',' + T2.SomeColumn
            FROM  @T T2 
            WHERE WHERE T1.id = T2.id
            FOR XML PATH('')), 2, 1000000)
    FROM @T T1
GROUP BY ID
mrogunlana
  • 827
  • 9
  • 11
-1
SELECT t.ID, 
       t.NAME, 
       (SELECT t1.SOMECOLUMN 
        FROM   TABLEB t1 
        WHERE  t1.F_ID = T.TABLEA.ID) 
FROM   TABLEA t; 

This will work for selecting from different table using sub query.

Gidil
  • 4,137
  • 2
  • 34
  • 50
ATHAR
  • 1
-1

Solution below:

SELECT GROUP_CONCAT(field_attr_best_weekday_value)as RAVI
FROM content_field_attr_best_weekday LEFT JOIN content_type_attraction
    on content_field_attr_best_weekday.nid = content_type_attraction.nid
GROUP BY content_field_attr_best_weekday.nid

Use this, you also can change the Joins

takrl
  • 6,356
  • 3
  • 60
  • 69
ravi
  • 1
-1

I have reviewed all the answers. I think in database insertion should be like:

ID     Name      SomeColumn
1.     ABC       ,X,Y Z (these are three different rows)
2.     MNO       ,R,S

The comma should be at previous end and do searching by like %,X,%

Taryn
  • 242,637
  • 56
  • 362
  • 405
rsda
  • 1