Как объединить текст из нескольких строк в одну текстовую строку на сервере SQL?

1608

Рассмотрим таблицу базы данных, содержащую имена, с тремя строками:

Peter
Paul
Mary

Есть ли простой способ превратить это в одну строку Peter, Paul, Mary?

  • 21
    Для ответов, специфичных для SQL Server, попробуйте этот вопрос .
  • 14
    Для MySQL, проверьте Group_Concat из этого ответа
Показать ещё 6 комментариев
Теги:
sql-server
csv
group-concat
string-concatenation

45 ответов

1191
Лучший ответ

Если вы находитесь на SQL Server 2017 или Azure, см. Ответ Матье Ренды.

У меня была аналогичная проблема, когда я пытался присоединиться к двум таблицам с отношениями "один ко многим". В SQL 2005 я обнаружил, что метод XML PATH очень легко обрабатывает конкатенацию строк.

Если есть таблица под названием " STUDENTS

SubjectID       StudentName
----------      -------------
1               Mary
1               John
1               Sam
2               Alaina
2               Edward

Результат, который я ожидал, был:

SubjectID       StudentName
----------      -------------
1               Mary, John, Sam
2               Alaina, Edward

Я использовал следующий T-SQL:

SELECT Main.SubjectID,
       LEFT(Main.Students,Len(Main.Students)-1) As "Students"
FROM
    (
        SELECT DISTINCT ST2.SubjectID, 
            (
                SELECT ST1.StudentName + ',' AS [text()]
                FROM dbo.Students ST1
                WHERE ST1.SubjectID = ST2.SubjectID
                ORDER BY ST1.SubjectID
                FOR XML PATH ('')
            ) [Students]
        FROM dbo.Students ST2
    ) [Main]

Вы можете сделать то же самое более компактно, если вы можете вначале запрвать запятые и использовать substring чтобы пропустить первый, поэтому вам не нужно делать подзапрос:

SELECT DISTINCT ST2.SubjectID, 
    SUBSTRING(
        (
            SELECT ','+ST1.StudentName  AS [text()]
            FROM dbo.Students ST1
            WHERE ST1.SubjectID = ST2.SubjectID
            ORDER BY ST1.SubjectID
            FOR XML PATH ('')
        ), 2, 1000) [Students]
FROM dbo.Students ST2
  • 1
    Я получаю сообщение об ошибке «Неверный синтаксис рядом с ключевым словом« для »» под управлением MS SQL Server 2008 R2
  • 10
    Отличное решение. Следующее может быть полезно, если вам нужно обрабатывать специальные символы, такие как в HTML: Роб Фарли: Обработка специальных символов с помощью FOR XML PATH ('') .
Показать ещё 19 комментариев
971

Этот ответ может возвращать неожиданные результаты, когда присутствует предложение ORDER BY. Для получения согласованных результатов используйте один из методов FOR XML PATH, подробно описанный в других ответах.

Использовать COALESCE:

DECLARE @Names VARCHAR(8000) 
SELECT @Names = COALESCE(@Names + ', ', '') + Name 
FROM People

Просто какое-то объяснение (поскольку этот ответ, кажется, получает относительно регулярные взгляды):

  • Coalesce - действительно просто полезный чит, который выполняет две вещи:

1) Не нужно инициализировать @Names с пустым строковым значением.

2) Не нужно снимать дополнительный разделитель в конце.

  • Вышеприведенное решение даст неверные результаты, если строка имеет значение NULL Name (если существует NULL, NULL будет делать @Names NULL после этой строки, а следующая строка снова начнется как пустая строка. двух решений:
DECLARE @Names VARCHAR(8000) 
SELECT @Names = COALESCE(@Names + ', ', '') + Name
FROM People
WHERE Name IS NOT NULL

или:

DECLARE @Names VARCHAR(8000) 
SELECT @Names = COALESCE(@Names + ', ', '') + 
    ISNULL(Name, 'N/A')
FROM People

В зависимости от того, какое поведение вы хотите (первый вариант просто фильтрует NULL, второй вариант сохраняет их в списке с сообщением маркера [replace 'N/A' с тем, что подходит вам]).

  • 67
    Чтобы было ясно, coalesce не имеет ничего общего с созданием списка, он просто гарантирует, что значения NULL не включены.
  • 16
    @Graeme Perrow Это не исключает значения NULL (для этого требуется WHERE - это приведет к потере результатов, если одно из входных значений равно NULL), и это требуется в этом подходе, потому что: NULL + non-NULL -> NULL и не NULL + NULL -> NULL; Кроме того, @Name по умолчанию равно NULL, и, фактически, это свойство используется здесь как неявный сторож, чтобы определить, следует ли добавить ',' или нет.
Показать ещё 18 комментариев
306

Один метод, еще не показанный с помощью команды XML data() в MS SQL Server:

Предположим, что таблица с именем NameList имеет один столбец с именем FName,

SELECT FName + ', ' AS 'data()' 
FROM NameList 
FOR XML PATH('')

возвращает:

"Peter, Paul, Mary, "

Необходимо использовать только дополнительную запятую.

Изменить: Как принято из комментария @NReilingh, вы можете использовать следующий метод для удаления конечной запятой. Предполагая одинаковые имена таблиц и столбцов:

STUFF(REPLACE((SELECT '#!' + LTRIM(RTRIM(FName)) AS 'data()' FROM NameList
FOR XML PATH('')),' #!',', '), 1, 2, '') as Brands
  • 13
    святой с ** т это удивительно! Когда выполняется сам по себе, как в вашем примере, результат форматируется как гиперссылка, при нажатии (в SSMS) открывается новое окно, содержащее данные, но при использовании в качестве части более крупного запроса оно просто отображается в виде строки. Это строка? или это XML, который мне нужно обрабатывать по-разному в приложении, которое будет использовать эти данные?
  • 8
    Этот подход также XML-экранирует символы, такие как <и>. Итак, ВЫБОР '<b>' + FName + '</ b>' приводит к "& lt; b & gt; John & lt; / b & gt; & lt; b & gt; Paul ..."
Показать ещё 9 комментариев
262

В SQL Server 2005

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

В SQL Server 2016

вы можете использовать синтаксис FOR JSON

т.е.

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

И результат будет

Id  Emails
1   [email protected]
2   NULL
3   [email protected], [email protected]

Это будет работать, даже ваши данные содержат недопустимые символы XML

'"},{"_":"' безопасен, потому что, если в ваших данных есть '"},{"_":"', он будет экранирован до "},{\"_\":\"

Вы можете заменить ', ' на любой разделитель строк


А в SQL Server 2017 база данных Azure SQL

Вы можете использовать новую функцию STRING_AGG

  • 3
    Хорошее использование функции STUFF, чтобы убрать два первых символа.
  • 3
    Мне больше всего нравится это решение, потому что я могу легко использовать его в списке выбора, добавив «как <label>». Я не уверен, как это сделать с помощью решения @Ritesh.
Показать ещё 2 комментария
220

SQL Server 2017+ и SQL Azure: STRING_AGG

Начиная со следующей версии SQL Server, мы можем, наконец, объединиться между строк, не прибегая к какой-либо переменной или XML-witchery.

STRING_AGG (Transact-SQL)

Без группировки

SELECT STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department;

С группировкой:

SELECT GroupName, STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department
GROUP BY GroupName;

С группировкой и подсеризацией

SELECT GroupName, STRING_AGG(Name, ', ') WITHIN GROUP (ORDER BY Name ASC) AS Departments
FROM HumanResources.Department 
GROUP BY GroupName;
  • 1
    И, в отличие от решений CLR, вы можете контролировать сортировку.
  • 2
    Это работает с SQL Azure. Отличный ответ!
Показать ещё 1 комментарий
108

В MySQL есть функция GROUP_CONCAT(), которая позволяет объединять значения из нескольких строк. Пример:

SELECT 1 AS a, GROUP_CONCAT(name ORDER BY name ASC SEPARATOR ', ') AS people 
FROM users 
WHERE id IN (1,2,3) 
GROUP BY a
  • 2
    Раньше любил этот, еще не видел альтернативы этой функции с любым другим БД!
  • 1
    Это полностью решило мою проблему. Я пытался вытащить все даты оплаты для данного заряда на счете, это решило это отлично. Спасибо!
Показать ещё 4 комментария
50

Используйте COALESCE - Подробнее читайте здесь

Пример:

102

103

104

Затем напишите ниже код на сервере sql,

Declare @Numbers AS Nvarchar(MAX) -- It must not be MAX if you have few numbers 
SELECT  @Numbers = COALESCE(@Numbers + ',', '') + Number
FROM   TableName where Number IS NOT NULL

SELECT @Numbers

Вывод:

102,103,104
  • 2
    Это действительно лучшее решение IMO, поскольку оно позволяет избежать проблем с кодировкой, которые создает FOR XML. Я использовал Declare @Numbers AS Nvarchar(MAX) и он работал нормально. Можете ли вы объяснить, почему вы рекомендуете не использовать его, пожалуйста?
  • 6
    Это решение уже было опубликовано 8 лет назад! stackoverflow.com/a/194887/986862
Показать ещё 1 комментарий
45

Массивы Postgres являются удивительными. Пример:

Создайте несколько тестовых данных:

postgres=# \c test
You are now connected to database "test" as user "hgimenez".
test=# create table names (name text);
CREATE TABLE                                      
test=# insert into names (name) values ('Peter'), ('Paul'), ('Mary');                                                          
INSERT 0 3
test=# select * from names;
 name  
-------
 Peter
 Paul
 Mary
(3 rows)

Совокупность их в массиве:

test=# select array_agg(name) from names;
 array_agg     
------------------- 
 {Peter,Paul,Mary}
(1 row)

Преобразование массива в строку с разделителями-запятыми:

test=# select array_to_string(array_agg(name), ', ') from names;
 array_to_string
-------------------
 Peter, Paul, Mary
(1 row)

DONE

Так как PostgreSQL 9.0 это еще проще.

  • 0
    Если вам нужно более одного столбца, например, идентификатор сотрудника в скобках, используйте оператор select array_to_string(array_agg(name||'('||id||')' : select array_to_string(array_agg(name||'('||id||')'
  • 0
    Не применимо к sql-серверу , только к mysql
45

Oracle 11g Release 2 поддерживает функцию LISTAGG. Документация здесь.

COLUMN employees FORMAT A50

SELECT deptno, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS employees
FROM   emp
GROUP BY deptno;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 ADAMS,FORD,JONES,SCOTT,SMITH
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

3 rows selected.

Предупреждение

Будьте внимательны при реализации этой функции, если есть возможность получения строки длиной более 4000 символов. Это вызовет исключение. Если это случай, вам нужно либо обработать исключение, либо перевернуть свою собственную функцию, которая предотвращает пересылку объединенной строки из 4000 символов.

  • 1
    Для более старых версий Oracle идеально подходит wm_concat. Его использование объясняется в ссылке подарок от Алекса. Спасибо Алекс!
  • 0
    LISTAGG работает отлично! Просто прочитайте документ, связанный здесь. wm_concat удален с версии 12c и выше.
32

В SQL Server 2005 и более поздних версиях используйте следующий запрос, чтобы объединить строки.

DECLARE @t table
(
    Id int,
    Name varchar(10)
)
INSERT INTO @t
SELECT 1,'a' UNION ALL
SELECT 1,'b' UNION ALL
SELECT 2,'c' UNION ALL
SELECT 2,'d' 

SELECT ID,
stuff(
(
    SELECT ','+ [Name] FROM @t WHERE Id = t.Id FOR XML PATH('')
),1,1,'') 
FROM (SELECT DISTINCT ID FROM @t ) t
  • 2
    Я считаю, что это не удается, когда значения содержат символы XML, такие как < или & .
26

У меня нет доступа к SQL Server дома, поэтому я думаю о синтаксисе здесь, но это более или менее:

DECLARE @names VARCHAR(500)

SELECT @names = @names + ' ' + Name
FROM Names
  • 10
    Вам нужно будет инициализировать @names чем-то ненулевым, иначе вы получите NULL повсюду; вам также нужно обработать разделитель (включая ненужный)
  • 3
    единственная проблема с этим подходом (который я использую все время) состоит в том, что вы не можете встроить его
Показать ещё 2 комментария
24

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

Самое простое решение

DECLARE @char VARCHAR(MAX);

SELECT @char = COALESCE(@char + ', ' + [column], [column]) 
FROM [table];

PRINT @char;
23

Было предложено рекурсивное решение CTE, но код не предоставлен. Приведенный ниже код является примером рекурсивного CTE - обратите внимание, что хотя результаты совпадают с вопросом, данные не совсем соответствуют данному описанию, так как я предполагаю, что вы действительно хотите делать это по группам строк, не все строк в таблице. Изменение его для соответствия всем строкам в таблице оставлено в качестве упражнения для читателя.

;with basetable as 
(   SELECT id, CAST(name as varchar(max))name, 
        ROW_NUMBER() OVER(Partition By id     order by seq) rw, 
        COUNT(*) OVER (Partition By id) recs 
FROM (VALUES (1, 'Johnny', 1), (1,'M', 2), 
                  (2,'Bill', 1), (2, 'S.', 4), (2, 'Preston', 5), (2, 'Esq.', 6),
        (3, 'Ted', 1), (3,'Theodore', 2), (3,'Logan', 3),
                  (4, 'Peter', 1), (4,'Paul', 2), (4,'Mary', 3)

           )g(id, name, seq)
),
rCTE as (
    SELECT recs, id, name, rw from basetable where rw=1
    UNION ALL
    SELECT b.recs, r.ID, r.name +', '+ b.name name, r.rw+1
    FROM basetable b
         inner join rCTE r
    on b.id = r.id and b.rw = r.rw+1
)
SELECT name FROM rCTE
WHERE recs = rw and ID=4
  • 0
    Для ошеломленного: этот запрос вставляет 12 строк (3 столбца) во временную базовую таблицу, затем создает рекурсивное выражение общей таблицы (rCTE), а затем выравнивает столбец name в строку с разделителями-запятыми для 4 групп id . На первый взгляд, я думаю, что это больше работы, чем то, что делают большинство других решений для SQL Server.
  • 1
    @knb: не уверен, что это похвала, осуждение или просто сюрприз. Базовая таблица такова, что мне нравятся мои примеры, и они не имеют ничего общего с вопросом.
22

Начиная с PostgreSQL 9.0, это довольно просто:

select string_agg(name, ',') 
from names;

В версиях до 9.0 array_agg() можно использовать, как показано hgmnz

  • 0
    Чтобы сделать это со столбцами, которые не имеют текстовый тип, вам нужно добавить приведение типа: SELECT string_agg(non_text_type::text, ',') FROM table
  • 0
    @TorbenKohlmeier: это нужно только для не символьных столбцов (например, целое, десятичное). Работает просто отлично для varchar или char
20

В SQL Server vNext это будет построено с помощью функции STRING_AGG, более подробно об этом читайте здесь: https://msdn.microsoft.com/en-us/library/mt790580.aspx

17

Готовое к использованию решение без дополнительных запятых:

select substring(
        (select ', '+Name AS 'data()' from Names for xml path(''))
       ,3, 255) as "MyList"

Пустой список приведет к значению NULL. Обычно вы вставляете список в столбец таблицы или программную переменную: отрегулируйте максимальную длину 255 до ваших потребностей.

(Дивакар и Йенс Франдсен дали хорошие ответы, но нуждаются в улучшении.)

  • 0
    Пробел перед запятой при использовании этого :(
  • 1
    Просто замените ', ' на ',' если вам не нужно дополнительное место.
17

Использование XML помогло мне получить строки, разделенные запятыми. Для дополнительной запятой мы можем использовать функцию замены SQL Server. Вместо добавления запятой использование AS 'data()' будет конкатенировать строки с пробелами, которые позже могут быть заменены запятыми в качестве синтаксиса, написанного ниже.

REPLACE(
        (select FName AS 'data()'  from NameList  for xml path(''))
         , ' ', ', ') 
  • 2
    Это лучший ответ здесь, на мой взгляд. Использование объявления переменная не годится, когда вам нужно объединить в другую таблицу, и это хорошо и кратко. Хорошая работа.
  • 7
    это не работает хорошо, если в данных FName уже есть пробелы, например «Мое имя»
Показать ещё 1 комментарий
10
DECLARE @Names VARCHAR(8000)
SELECT @name = ''
SELECT @Names = @Names + ',' + Names FROM People
SELECT SUBSTRING(2, @Names, 7998)

Вначале ставится паразитная запятая.

Однако, если вам нужны другие столбцы или CSV для дочерней таблицы, вам необходимо обернуть это в поле скалярного пользователя (UDF).

Вы также можете использовать XML-путь как коррелированный подзапрос в предложении SELECT (но мне придется подождать, пока я вернусь на работу, потому что Google не делает работу дома: -)

9
SELECT STUFF((SELECT ', ' + name FROM [table] FOR XML PATH('')), 1, 2, '')

Здесь образец:

DECLARE @t TABLE (name VARCHAR(10))
INSERT INTO @t VALUES ('Peter'), ('Paul'), ('Mary')
SELECT STUFF((SELECT ', ' + name FROM @t FOR XML PATH('')), 1, 2, '')
--Peter, Paul, Mary
  • 0
    Большое спасибо за наименьшее возможное решение вместе с рабочим примером! Я понятия не имел, почему работает ответ с наибольшим количеством голосов, и как его повторить.
9

С другими ответами человек, читающий ответ, должен знать определенную таблицу домена, такую ​​как автомобиль или ученик. Таблица должна быть создана и заполнена данными для проверки решения.

Ниже приведен пример использования таблицы SQL Server "Information_Schema.Columns". Используя это решение, таблицы не нужно создавать или добавлять данные. В этом примере создается список имен столбцов, разделенных запятыми, для всех таблиц в базе данных.

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 
7

Для Oracle DB см. этот вопрос: Как несколько строк могут быть объединены в один в Oracle без создания хранимой процедуры?

Лучший ответ, по-видимому, принадлежит @Emmanuel, используя встроенную функцию LISTAGG(), доступную в Oracle 11g Release 2 и более поздних версиях.

SELECT question_id,
   LISTAGG(element_id, ',') WITHIN GROUP (ORDER BY element_id)
FROM YOUR_TABLE;
GROUP BY question_id

как указано в @user762952, и согласно документации Oracle http://www.oracle-base.com/articles/misc/string-aggregation-techniques.php, функция WM_CONCAT() также является опцией. Он кажется стабильным, но Oracle явно рекомендует не использовать его для любого приложения SQL, поэтому используйте его на свой страх и риск.

Кроме этого, вам придется написать свою собственную функцию; документ Oracle выше содержит руководство о том, как это сделать.

7

Мне действительно понравилась элегантность ответа Даны. Просто хотел сделать это.

DECLARE @names VARCHAR(MAX)
SET @names = ''

SELECT @names = @names + ', ' + Name FROM Names 

-- Deleting last two symbols (', ')
SET @sSql = LEFT(@sSql, LEN(@sSql) - 1)
  • 0
    Если вы удаляете последние два символа ',', то вам необходимо добавить ',' после имени ('SELECT \ @names = \ @names + Name +', 'FROM Names'). Таким образом, последние два символа всегда будут «,».
  • 0
    В моем случае мне нужно было избавиться от ведущей запятой, поэтому измените запрос на SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ', ' END + Name FROM Names тогда вы не будете должны усечь это потом.
6

Этот ответ потребует некоторой привилегии на работе сервера.

Ассемблирование - хороший вариант для вас. Есть много сайтов, которые объясняют, как его создать. Я думаю, это очень хорошо объясняется, это один

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

После его загрузки вам нужно будет запустить следующий script на вашем SQL Server:

CREATE Assembly concat_assembly 
   AUTHORIZATION dbo 
   FROM '<PATH TO Concat.dll IN SERVER>' 
   WITH PERMISSION_SET = SAFE; 
GO 

CREATE AGGREGATE dbo.concat ( 

    @Value NVARCHAR(MAX) 
  , @Delimiter NVARCHAR(4000) 

) RETURNS NVARCHAR(MAX) 
EXTERNAL Name concat_assembly.[Concat.Concat]; 
GO  

sp_configure 'clr enabled', 1;
RECONFIGURE

Обратите внимание, что путь к сборке может быть доступен для сервера. Поскольку вы успешно выполнили все этапы, вы можете использовать такую ​​функцию, как:

SELECT dbo.Concat(field1, ',')
FROM Table1

Надеюсь, это поможет!!!

6

Чтобы избежать нулевых значений, вы можете использовать CONCAT()

DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name) 
FROM Names
select @names
  • 0
    Было бы неплохо узнать, почему работает CONCAT. Ссылка на MSDN была бы хороша.
5

MySQL завершен Пример:

У нас есть пользователи, у которых может быть много данных, и мы хотим иметь выход, где мы можем видеть всех пользователей. Даты в списке:

Результат:

___________________________
| id   |  rowList         |
|-------------------------|
| 0    | 6, 9             |
| 1    | 1,2,3,4,5,7,8,1  |
|_________________________|

Настройка таблицы:

CREATE TABLE `Data` (
  `id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1;


INSERT INTO `Data` (`id`, `user_id`) VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 1),
(5, 1),
(6, 0),
(7, 1),
(8, 1),
(9, 0),
(10, 1);


CREATE TABLE `User` (
  `id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


INSERT INTO `User` (`id`) VALUES
(0),
(1);

Запрос:

SELECT User.id, GROUP_CONCAT(Data.id ORDER BY Data.id) AS rowList FROM User LEFT JOIN Data ON User.id = Data.user_id GROUP BY User.id
5

Если вы хотите иметь дело с нулями, вы можете сделать это, добавив предложение where или добавив еще один COALESCE вокруг первого.

DECLARE @Names VARCHAR(8000) 
SELECT @Names = COALESCE(COALESCE(@Names + ', ', '') + Name, @Names) FROM People
5

Обычно я использую select, подобный этому, для конкатенации строк в SQL Server:

with lines as 
( 
  select 
    row_number() over(order by id) id, -- id is a line id
    line -- line of text.
  from
    source -- line source
), 
result_lines as 
( 
  select 
    id, 
    cast(line as nvarchar(max)) line 
  from 
    lines 
  where 
    id = 1 
  union all 
  select 
    l.id, 
    cast(r.line + N', ' + l.line as nvarchar(max))
  from 
    lines l 
    inner join 
    result_lines r 
    on 
      l.id = r.id + 1 
) 
select top 1 
  line
from
  result_lines
order by
  id desc
4

Это тоже может быть полезно

create table #test (id int,name varchar(10))
--use separate inserts on older versions of SQL Server
insert into #test values (1,'Peter'), (1,'Paul'), (1,'Mary'), (2,'Alex'), (3,'Jack')

DECLARE @t VARCHAR(255)
SELECT @t = ISNULL(@t + ',' + name, name) FROM #test WHERE id = 1
select @t
drop table #test

возвращает

Peter,Paul,Mary
  • 5
    К сожалению, это поведение официально не поддерживается. MSDN говорит: «Если на переменную ссылаются в списке выбора, ей должно быть присвоено скалярное значение, или инструкция SELECT должна возвращать только одну строку». И есть люди, которые наблюдали проблемы: sqlmag.com/sql-server/multi-row-variable-assignment-and-order
4

В Oracle это wm_concat. Я считаю, что эта функция доступна в 10g release и выше.

3

Вот полное решение для этого:

-- Table Creation
CREATE TABLE Tbl
( CustomerCode    VARCHAR(50)
, CustomerName    VARCHAR(50)
, Type VARCHAR(50)
,Items    VARCHAR(50)
)

insert into Tbl
SELECT 'C0001','Thomas','BREAKFAST','Milk'
union SELECT 'C0001','Thomas','BREAKFAST','Bread'
union SELECT 'C0001','Thomas','BREAKFAST','Egg'
union SELECT 'C0001','Thomas','LUNCH','Rice'
union SELECT 'C0001','Thomas','LUNCH','Fish Curry'
union SELECT 'C0001','Thomas','LUNCH','Lessy'
union SELECT 'C0002','JOSEPH','BREAKFAST','Bread'
union SELECT 'C0002','JOSEPH','BREAKFAST','Jam'
union SELECT 'C0002','JOSEPH','BREAKFAST','Tea'
union SELECT 'C0002','JOSEPH','Supper','Tea'
union SELECT 'C0002','JOSEPH','Brunch','Roti'

-- function creation
GO
CREATE  FUNCTION [dbo].[fn_GetItemsByType]
(   
    @CustomerCode VARCHAR(50)
    ,@Type VARCHAR(50)
)
RETURNS @ItemType TABLE  ( Items VARCHAR(5000) )
AS
BEGIN

        INSERT INTO @ItemType(Items)
    SELECT  STUFF((SELECT distinct ',' + [Items]
         FROM Tbl 
         WHERE CustomerCode = @CustomerCode
            AND Type=@Type
            FOR XML PATH(''))
        ,1,1,'') as  Items



    RETURN 
END

GO

-- fianl Query
DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT distinct ',' + QUOTENAME(Type) 
                    from Tbl
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT CustomerCode,CustomerName,' + @cols + '
             from 
             (
                select  
                    distinct CustomerCode
                    ,CustomerName
                    ,Type
                    ,F.Items
                    FROM Tbl T
                    CROSS APPLY [fn_GetItemsByType] (T.CustomerCode,T.Type) F
            ) x
            pivot 
            (
                max(Items)
                for Type in (' + @cols + ')
            ) p '

execute(@query) 
3

Не то, чтобы я сделал какой-либо анализ производительности, так как в моем списке было менее 10 элементов, но я был поражен, посмотрев через 30 нечетных ответов. У меня все еще был поворот в аналогичном ответе, уже приведенном аналогично использованию COALESCE для одной группы и даже не нужно было устанавливать мою переменную (по умолчанию она равна NULL) и предполагает, что все записи в моей исходной таблице данных не являются пустыми:

DECLARE @MyList VARCHAR(1000), @Delimiter CHAR(2) = ', '
SELECT @MyList = CASE WHEN @MyList > '' THEN @MyList + @Delimiter ELSE '' END + FieldToConcatenate FROM MyData

Я уверен, COALESCE внутренне использует ту же идею. Давайте надеяться, что MS не изменит это на мне.

3

- SQL Server 2005 +

CREATE TABLE dbo.Students
(
    StudentId INT
    , Name VARCHAR(50)
    , CONSTRAINT PK_Students PRIMARY KEY (StudentId)
);

CREATE TABLE dbo.Subjects
(
    SubjectId INT
    , Name VARCHAR(50)
    , CONSTRAINT PK_Subjects PRIMARY KEY (SubjectId)
);

CREATE TABLE dbo.Schedules
(
    StudentId INT
    , SubjectId INT
    , CONSTRAINT PK__Schedule PRIMARY KEY (StudentId, SubjectId)
    , CONSTRAINT FK_Schedule_Students FOREIGN KEY (StudentId) REFERENCES dbo.Students (StudentId)
    , CONSTRAINT FK_Schedule_Subjects FOREIGN KEY (SubjectId) REFERENCES dbo.Subjects (SubjectId)
);

INSERT dbo.Students (StudentId, Name) VALUES
    (1, 'Mary')
    , (2, 'John')
    , (3, 'Sam')
    , (4, 'Alaina')
    , (5, 'Edward')
;

INSERT dbo.Subjects (SubjectId, Name) VALUES
    (1, 'Physics')
    , (2, 'Geography')
    , (3, 'French')
    , (4, 'Gymnastics')
;

INSERT dbo.Schedules (StudentId, SubjectId) VALUES
    (1, 1)      --Mary, Physics
    , (2, 1)    --John, Physics
    , (3, 1)    --Sam, Physics
    , (4, 2)    --Alaina, Geography
    , (5, 2)    --Edward, Geography
;

SELECT 
    sub.SubjectId
    , sub.Name AS [SubjectName]
    , ISNULL( x.Students, '') AS Students
FROM
    dbo.Subjects sub
    OUTER APPLY
    (
        SELECT 
            CASE ROW_NUMBER() OVER (ORDER BY stu.Name) WHEN 1 THEN '' ELSE ', ' END
            + stu.Name
        FROM
            dbo.Students stu
            INNER JOIN dbo.Schedules sch
                ON stu.StudentId = sch.StudentId
        WHERE
            sch.SubjectId = sub.SubjectId
        ORDER BY
            stu.Name
        FOR XML PATH('')
    ) x (Students)
;
3

Этот метод применяется к базе данных Teradata Aster только в том случае, если используется функция NPATH.

Снова у нас есть стол Студенты

SubjectID       StudentName
----------      -------------
1               Mary
1               John
1               Sam
2               Alaina
2               Edward

Затем с NPATH он просто один SELECT:

SELECT * FROM npath(
  ON Students
  PARTITION BY SubjectID
  ORDER BY StudentName
  MODE(nonoverlapping)
  PATTERN('A*')
  SYMBOLS(
    'true' as A
  )
  RESULT(
    FIRST(SubjectID of A) as SubjectID,
    ACCUMULATE(StudentName of A) as StudentName
  )
);

Результат:

SubjectID       StudentName
----------      -------------
1               [John, Mary, Sam]
2               [Alaina, Edward]
2

Как насчет этого:

   ISNULL(SUBSTRING(REPLACE((select ',' FName as 'data()' from NameList for xml path('')), ' ,',', '), 2, 300), '') 'MyList'

Если "300" может быть любой ширины с учетом максимального количества элементов, которые, по вашему мнению, будут отображаться.

2

Один из способов сделать это в SQL Server должен был бы вернуть содержимое таблицы как XML (для XML raw), преобразовать результат в строку и затем заменить теги на ",".

1

С помощью рекурсивного запроса вы можете это сделать:

-- Create example table
CREATE TABLE tmptable (NAME VARCHAR(30)) ;

-- Insert example data
INSERT INTO tmptable VALUES('PETER');
INSERT INTO tmptable VALUES('PAUL');
INSERT INTO tmptable VALUES('MARY');

-- Recurse query
with tblwithrank as (
select * , row_number() over(order by name) rang , count(*) over() NbRow
from tmptable
),
tmpRecursive as (
select *, cast(name as varchar(2000)) as AllName from tblwithrank  where rang=1
union all
select f0.*,  cast(f0.name + ',' + f1.AllName as varchar(2000)) as AllName 
from tblwithrank f0 inner join tmpRecursive f1 on f0.rang=f1.rang +1 
)
select AllName from tmpRecursive
where rang=NbRow
1

@User1460901 Вы можете попробовать что-то вроде этого:

WITH cte_base AS (
    SELECT CustomerCode, CustomerName,
    CASE WHEN Typez = 'Breakfast' THEN Items ELSE NULL END AS 'BREAKFAST'
    , CASE WHEN Typez = 'Lunch' THEN Items ELSE NULL END AS 'LUNCH'
    FROM #Customer
    )
    SELECT distinct CustomerCode, CustomerName,
    SUBSTRING(
    (   
        SELECT ','+BREAKFAST AS [text()]
        FROM cte_base b1
        WHERE b1.CustomerCode = b2.CustomerCode AND b1.CustomerName = b2.CustomerName
        ORDER BY b1.BREAKFAST
        FOR XML PATH('')
        ), 2, 1000
    ) [BREAKFAST], 
    SUBSTRING(
    (   
        SELECT ','+LUNCH AS [text()]
        FROM cte_base b1
        WHERE b1.CustomerCode = b2.CustomerCode AND b1.CustomerName = b2.CustomerName
        ORDER BY b1.LUNCH
        FOR XML PATH('')
        ), 2, 1000
    ) [LUNCH]
    FROM cte_base b2
1

Ниже приведена простая процедура PL/SQL для реализации данного сценария с использованием "basic loop" и "rownum",

Определение таблицы

CREATE TABLE "NAMES" ("NAME" VARCHAR2(10 BYTE))) ;

Вставьте значения в эту таблицу

INSERT INTO NAMES VALUES('PETER');
INSERT INTO NAMES VALUES('PAUL');
INSERT INTO NAMES VALUES('MARY');

Процедура начинается здесь

DECLARE 

MAXNUM INTEGER;
CNTR INTEGER := 1;
C_NAME NAMES.NAME%TYPE;
NSTR VARCHAR2(50);

BEGIN

SELECT MAX(ROWNUM) INTO MAXNUM FROM NAMES;

LOOP

SELECT NAME INTO  C_NAME FROM 
(SELECT ROWNUM RW, NAME FROM NAMES ) P WHERE P.RW = CNTR;

NSTR := NSTR ||','||C_NAME;
CNTR := CNTR + 1;
EXIT WHEN CNTR > MAXNUM;

END LOOP;

dbms_output.put_line(SUBSTR(NSTR,2));

END;

результат

PETER,PAUL,MARY
  • 0
    Вопрос заключается в том, чтобы получить ответ, специфичный для SQL Server. Если есть вопрос по PL / SQL, вы можете ответить на него. Однако сначала проверьте wm_concat и посмотрите, не является ли это более простым методом.
1

Хотя это слишком поздно и уже имеет множество решений. Вот простое решение для MySQL:

SELECT t1.id,
        GROUP_CONCAT(t1.id) ids
 FROM table t1 JOIN table t2 ON (t1.id = t2.id)
 GROUP BY t1.id
  • 0
    Этот вопрос специфичен для SQL-сервера, поэтому вряд ли кто-то найдет этот ответ. Есть ли специфичный для mysql вопрос об этом?
1
SELECT PageContent = Stuff(
    (   SELECT PageContent
        FROM dbo.InfoGuide
        WHERE CategoryId = @CategoryId
          AND SubCategoryId = @SubCategoryId
        for xml path(''), type
    ).value('.[1]','nvarchar(max)'),
    1, 1, '')
FROM dbo.InfoGuide info
1

С типом TABLE это очень просто. Представьте себе, что ваша таблица называется Students и имеет столбец name.

declare @rowsCount INT
declare @i INT = 1
declare @names varchar(max) = ''

DECLARE @MyTable TABLE
(
  Id int identity,
  Name varchar(500)
)
insert into @MyTable select name from Students
set @rowsCount = (select COUNT(Id) from @MyTable)

while @i < @rowsCount
begin
 set @names = @names + ', ' + (select name from @MyTable where Id = @i)
 set @i = @i + 1
end
select @names

Этот пример протестирован в MS SQL Server 2008 R2

1

В oracle есть еще несколько способов,

    create table name
    (first_name varchar2(30));

    insert into name values ('Peter');
    insert into name values ('Paul');
    insert into name values ('Mary');

    Solution 1:
    select substr(max(sys_connect_by_path (first_name, ',')),2) from (select rownum r, first_name from name ) n start with r=1 connect by prior r+1=r
    o/p=> Peter,Paul,Mary

    Soution 2:
    select  rtrim(xmlagg (xmlelement (e, first_name || ',')).extract ('//text()'), ',') first_name from name
    o/p=> Peter,Paul,Mary
0

Приведенные примеры являются вложенными операторами выбора. Есть ли способ, который можно переместить в объединение? и привести колонку из этой объединенной таблицы?

-2

Зависит от поставщика базы данных. MySQL имеет concat_ws. MS SQL Server ожидает, что вы сделаете это в своем клиентском приложении.

Обновление: вы также можете сделать это во внешней процедуре или UDF, возможно, используя курсор или вызывая код CLR.

  • 0
    @Joel, функция в MySQL - CONCAT_WS (), но она полезна только для 1 строки в результате.
-3
   declare @phone varchar(max)='' 
   select @phone=@phone + mobileno +',' from  members
   select @phone
  • 0
    Почему бы не +', ' Как хотел OP, а также вы не удаляете последний ';'. Я думаю, что этот ответ такой же, а также этот ответ ;).
  • 0
    У меня была эта проблема, и я нашел ответ, но я хочу объединить с ';' поэтому я вставляю это здесь, последний элемент пуст
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню