Объединить несколько результатов в подзапросе в одно значение, разделенное запятыми

84

У меня есть две таблицы:

TableA
------
ID,
Name

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

Отношения - это один ряд TableA- много таких TableB.

Теперь я хочу увидеть такой результат:

ID     Name      SomeColumn

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

Это не сработает (несколько результатов в подзапросе):

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

Это тривиальная проблема, если я выполняю обработку на стороне клиента. Но это будет означать, что мне придется выполнять X запросов на каждой странице, где X - это количество результатов TableA.

Обратите внимание, что я не могу просто выполнить GROUP BY или что-то подобное, так как он вернет несколько результатов для строк TableA.

Я не уверен, может ли работать UDF, использующий COALESCE или что-то подобное?

Донни Томас
источник

Ответы:

134

Даже это послужит цели

Пример данных

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'

Запрос:

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

Вывод:

ID  Name    SomeColumn
1   ABC     X,Y,Z
2   MNO     R,S
приянка.саркар
источник
13
Я не уверен, почему это не было принято, поскольку оно решает проблему, не требуя пользовательской функции. Вы можете увидеть ту же идею, выраженную здесь codecorner.galanter.net/2009/06/25/…, которая предшествует этому ответу и, следовательно, может быть «оригиналом»
Пол Д'Амбра,
1
То же самое здесь, не уверен, почему это не оценивается выше
Марсель
1
Привет, Приянка, не могли бы вы сказать мне, нужно ли здесь условие "and t1.name = t2.name" и почему?
Koen
2
Это отлично. Я искал оптимизировать функцию UDF, как указано в принятом ответе, который убивал мой сервер. Я перешел от 102 секунд поиска к менее 1. Сравнение планов выполнения составило 78% -22%, но это не связано со временем выполнения ...
toxaq
Просто напоминание о том, что вам понадобится этот ведущий символ ',' иначе вы получите угловые скобки в вашем выводе.
Тим Скарборо
45

1. Создайте 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. Используйте в подзапросе:

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

3. Если вы используете хранимую процедуру, вы можете сделать следующее:

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
Донни Томас
источник
1
Это все еще похоже на взлом. Я все еще использую подзапросы, так что еще много дополнительной обработки. Я уверен, что существует лучшее решение (реструктуризация таблиц или другой взгляд на проблему).
Донни Томас
1
Я бы не назвал это взломом. Это более эффективно, чем курсор, и в нем отсутствуют накладные расходы, необходимые для создания временной таблицы с данными, структурированными так, как вы хотите.
Скотт Лоуренс
1
Жаль, что столбцы не могут быть параметрами. В его нынешнем виде вам нужно будет создать функцию для каждого детского отношения!
Джон Пол Джонс
1
Это нормально - мне нужно объединить только эти конкретные столбцы. Остальные - «традиционные» соединения.
Донни Томас,
Я не припоминаю, как лучше всего это сделать без этого метода.
aF.
11

Я думаю, что с COALESCE вы на правильном пути. См. Здесь пример построения строки с разделителями-запятыми:

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

Бен Хоффштейн
источник
2
Потрясающе! Я видел несколько ссылок, в которых обсуждалась COALESCE, но они включали создание UDF с триггерами. Отправленная вами ссылка содержит ключ с одним оператором SELECT. Я добавляю ответ с правильным решением, чтобы другим было легче его найти. Благодаря!
Донни Томас
1
Привет, Бен, я думаю, что для ответа требуется немного больше деталей, а именно, как создать UDF и т. Д. Как только я разберусь с этим, я добавлю решение как ответ, доступный для редактирования сообществом. Пожалуйста, не стесняйтесь редактировать его, после чего я приму это как ответ. Извините за путаницу.
Донни Томас
11

В MySQL есть функция group_concat , которая вернет то, что вы просите.

SELECT TableA.ID, TableA.Name, group_concat(TableB.SomeColumn) 
as SomColumnGroup FROM TableA LEFT JOIN TableB ON 
TableB.TableA_ID = TableA.ID
Джейкоб
источник
1
Это было бы идеально, если бы подобная функция была в SQL Server. В нынешнем виде я использую решение Бена, чтобы сколотить то, что я хочу.
Донни Томас
0

Возможно, вам потребуется предоставить более подробную информацию для более точного ответа.

Поскольку ваш набор данных кажется узким, вы можете просто использовать строку для каждого результата и выполнять постобработку на клиенте.

Итак, если вы действительно хотите, чтобы сервер выполнял работу, верните набор результатов, например

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

что, конечно же, является простым INNER JOIN по ID

После того, как вы получите набор результатов обратно на клиенте, сохраните переменную с именем CurrentName и используйте ее в качестве триггера, когда следует прекратить сбор SomeColumn в полезную вещь, которую вы хотите.

Билл
источник
Я думал об этом, но не был уверен, что это элегантное решение - я бы хотел, чтобы SQL Server возвращал правильно построенный набор результатов, а не то, что нужно будет обрабатывать дальше. Вам нужны дополнительные детали? Я упростил структуру таблицы, но думаю, у вас она есть.
Донни Томас
0

Предполагая, что у вас есть только предложения WHERE в таблице A, создайте хранимую процедуру таким образом:

SELECT Id, Name From tableA WHERE ...

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

Затем заполните им DataSet ds. потом

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

Наконец, вы можете добавить на страницу повторитель, который ставит запятые для каждой строки

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

Таким образом, вы сделаете это на стороне клиента, но с помощью только одного запроса, передав минимальные данные между базой данных и внешним интерфейсом.

Скливвз
источник
0

Я попробовал решение, упомянутое priyanka.sarkar, и оно не совсем заработало, как попросил OP. Вот решение, к которому я пришел:

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
источник
-1

Решение ниже:

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

Используйте это, вы также можете изменить присоединения

рави
источник
-1
SELECT t.ID, 
       t.NAME, 
       (SELECT t1.SOMECOLUMN 
        FROM   TABLEB t1 
        WHERE  t1.F_ID = T.TABLEA.ID) 
FROM   TABLEA t; 

Это будет работать для выбора из другой таблицы с помощью подзапроса.

ATHAR
источник
-1

Я просмотрел все ответы. Я думаю, что вставка базы данных должна быть такой:

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

Запятая должна быть в предыдущем конце и искать по подобию %,X,%

RSDA
источник