Розглянемо таблицю бази даних, що містить імена, з трьома рядками:
Peter
Paul
Mary
Чи є простий спосіб перетворити це в єдиний рядок Peter, Paul, Mary
?
Розглянемо таблицю бази даних, що містить імена, з трьома рядками:
Peter
Paul
Mary
Чи є простий спосіб перетворити це в єдиний рядок Peter, Paul, Mary
?
Відповіді:
Якщо ви перебуваєте на SQL Server 2017 або Azure, дивіться відповідь Mathieu Renda .
У мене було подібне питання, коли я намагався з'єднати дві таблиці з відносинами один на багато. У 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
<
або &
. Дивіться коментар @ BenHinman
FOR XML PATH ('')
. Це означає, що його не слід вважати надійним, оскільки будь-який патч або оновлення можуть змінити, як ці функції. Це в основному покладається на застарілу функцію.
FOR XML
призначена для створення XML, а не об'єднання довільних рядків. Ось чому вона вислизає &
, <
і >
кодів XML сутностей ( &
, <
, >
). Я припускаю, що це також уникне "
і '
в, "
і '
в атрибути також. Це НЕ GROUP_CONCAT()
, string_agg()
, array_agg()
, listagg()
і т.д. , навіть якщо ви можете частково зробити його зробити це. Ми повинні витрачати свій час на вимогу Microsoft впровадити належну функцію.
string_agg
доданий у v.Next. і все це може піти.
Ця відповідь може повернути несподівані результати. Для послідовних результатів використовуйте один із методів FOR XML PATH, детально описаних в інших відповідях.
Використання COALESCE
:
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(@Names + ', ', '') + Name
FROM People
Лише якесь пояснення (оскільки, здається, ця відповідь має відносно регулярні погляди):
1) Не потрібно ініціалізувати @Names
значення порожнього рядка.
2) Не потрібно знімати зайвий роздільник в кінці.
@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 s, другий варіант зберігає їх у списку з маркером (замініть "N / A" на те, що вам підходить]).
Починаючи з наступної версії SQL Server, ми можемо, нарешті, об'єднатись у рядки, не вдаючись до якоїсь змінної або відмови від XML.
Без групування
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;
Один метод, який ще не показаний через 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
+ ', '
він все одно додає простір між кожним об'єднаним елементом.
SELECT STUFF(REPLACE((SELECT '#!'+city AS 'data()' FROM #cityzip FOR XML PATH ('')),' #!',', '),1,2,'')
SELECT Stuff(
(SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'')
ви можете використовувати синтаксис 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 abc@gmail.com
2 NULL
3 def@gmail.com, xyz@gmail.com
Це спрацює, навіть якщо ваші дані містять недійсні символи XML
'"},{"_":"'
безпечно , тому що, якщо ваші дані містять '"},{"_":"',
буде втік в"},{\"_\":\"
Ви можете замінити ', '
будь-яким роздільником рядків
Ви можете використовувати нову функцію STRING_AGG
<
, >
, &
і т.д. , які FOR XML PATH('')
будуть автоматично уникнути.
У 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
SEPARATOR '", "'
я пропущу кілька символів наприкінці останнього запису. чому це може статися?
CHAR
, ви повинні кинути його, наприклад , через GROUP_CONCAT( CAST(id AS CHAR(8)) ORDER BY id ASC SEPARATOR ',')
2) , якщо у вас є багато значень підходять, ви повинні збільшити , group_concat_max_len
як написано в stackoverflow.com/a/1278210/1498405
Використовуйте 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
Declare @Numbers AS Nvarchar(MAX)
і це працювало чудово. Чи можете ви пояснити, чому ви рекомендуєте не використовувати його будь ласка?
Масиви постгресів є приголомшливими. Приклад:
Створіть кілька тестових даних:
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)
Зроблено
З PostgreSQL 9.0 це ще простіше .
select array_to_string(array_agg(name||'('||id||')'
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 символів.
LISTAGG
працює ідеально! Просто прочитайте документ, зв'язаний тут. wm_concat
видалено з версії 12c і далі.
У 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
<
або &
.
У мене вдома немає доступу до SQL-сервера, тому я думаю, що тут синтаксис, але це більш-менш:
DECLARE @names VARCHAR(500)
SELECT @names = @names + ' ' + Name
FROM Names
SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ' ' END + Name FROM Names
SELECT @names = @names + ISNULL(' ' + Name, '')
Було запропоновано рекурсивне рішення 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
name
стовпець в розділених комами рядок на 4 групи в id
с. На перший погляд, я думаю, що це більше роботи, ніж більшість інших рішень для SQL Server.
Починаючи з PostgreSQL 9.0, це досить просто:
select string_agg(name, ',')
from names;
У версіях до 9.0 array_agg()
можна використовувати, як показано hgmnz
SELECT string_agg(non_text_type::text, ',') FROM table
varchar
абоchar
У SQL Server vNext це буде вбудовано за допомогою функції STRING_AGG, детальніше про це читайте тут: https://msdn.microsoft.com/en-us/library/mt790580.aspx
Використання XML допомогло мені отримати рядки, розділені комами. Для додаткової коми можна використовувати функцію заміни SQL Server. Замість додавання коми використання AS 'data ()' об'єднає рядки з пробілами, які згодом можуть бути замінені комами як синтаксис, написаний нижче.
REPLACE(
(select FName AS 'data()' from NameList for xml path(''))
, ' ', ', ')
Готове до використання рішення без зайвих коми:
select substring(
(select ', '+Name AS 'data()' from Names for xml path(''))
,3, 255) as "MyList"
Порожній список призведе до значення NULL. Зазвичай ви будете вставляти список у стовпчик таблиці чи змінну програми: налаштуйте максимальну довжину 255 відповідно до ваших потреб.
(Дівакар та Єнс Франдсен дали хороші відповіді, але потребують вдосконалення.)
', '
з , ','
якщо ви не хочете , щоб додатковий простір.
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
DECLARE @Names VARCHAR(8000)
SELECT @name = ''
SELECT @Names = @Names + ',' + Names FROM People
SELECT SUBSTRING(2, @Names, 7998)
Це ставить бродячу кому на початку.
Однак якщо вам потрібні інші стовпці або для CSV дочірньої таблиці, вам потрібно загорнути це у скалярне поле, визначене користувачем (UDF).
Ви можете використовувати шлях XML як співвіднесений підзапит у пункті SELECT (але мені доведеться почекати, поки я повернусь до роботи, тому що Google не працює вдома :-)
З іншими відповідями особа, що читає відповідь, повинна знати про конкретну таблицю домену, наприклад транспортний засіб або студент. Для перевірки рішення необхідно створити таблицю та заповнити її даними.
Нижче наводиться приклад, який використовує таблицю "Інформація_Схема.Колони" SQL Server ". Використовуючи це рішення, не потрібно створювати таблиці або додавати дані. Цей приклад створює розділений комами список імен стовпців для всіх таблиць бази даних.
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
Для баз даних Oracle див. Це питання: Як можна об'єднати кілька рядків в один 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, наведеному вище, є посібник, як це зробити.
Щоб уникнути нульових значень, ви можете використовувати CONCAT ()
DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name)
FROM Names
select @names
Мені дуже сподобалася елегантність відповіді Дани . Просто хотілося зробити його завершеним.
DECLARE @names VARCHAR(MAX)
SET @names = ''
SELECT @names = @names + ', ' + Name FROM Names
-- Deleting last two symbols (', ')
SET @sSql = LEFT(@sSql, LEN(@sSql) - 1)
SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ', ' END + Name FROM Names
потім не потрібно усікати його.
Ця відповідь потребує певної привілеї для роботи сервера.
Збори - це хороший варіант для вас. Існує маса сайтів, які пояснюють, як її створити. Один я думаю , що дуже добре пояснив це один
Якщо ви хочете, я вже створив збірку, і завантажити DLL можна тут .
Завантаживши його, вам потрібно буде запустити такий сценарій у своєму SQL сервері:
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
Сподіваюся, це допомагає !!!
Приклад завершеного 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
Я зазвичай використовую 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
Це працювало для мене ( SqlServer 2016 ):
SELECT CarNamesString = STUFF((
SELECT ',' + [Name]
FROM tbl_cars
FOR XML PATH('')
), 1, 1, '')
Ось джерело: https://www.mytecbits.com/
І рішення для MySql (оскільки ця сторінка відображається в Google для MySql)
SELECT [Name],
GROUP_CONCAT(DISTINCT [Name] SEPARATOR ',')
FROM tbl_cars
В Oracle це так wm_concat
. Я вважаю, що ця функція доступна у версії 10 г і вище.
Це теж може бути корисно
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