كيفية سلسلة النص من صفوف متعددة في سلسلة نصية واحدة في خادم SQL؟
-
10-07-2019 - |
سؤال
خذ بعين الاعتبار جدول قاعدة بيانات يحتوي على أسماء، مع ثلاثة صفوف:
Peter
Paul
Mary
هل هناك طريقة سهلة لتحويل هذا إلى سلسلة واحدة من Peter, Paul, Mary
?
المحلول
إذا كنت على SQL Server 2017 أو الأزرق السماوي، انظر ماتيو 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]
ويمكنك أن تفعل الشيء نفسه بطريقة أكثر إحكاما إذا كنت تستطيع CONCAT الفواصل في بداية واستخدام 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
نصائح أخرى
قد تعود هذه الإجابة نتائج غير متوقعة للحصول على نتائج متسقة، استخدم إحدى طرق FOR XML PATH المفصلة في الإجابات الأخرى.
يستخدم COALESCE
:
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(@Names + ', ', '') + Name
FROM People
فقط بعض التوضيحات (حيث يبدو أن هذه الإجابة تحصل على مشاهدات منتظمة نسبيًا):
- إن Coalesce هو في الواقع مجرد وسيلة غش مفيدة تحقق شيئين:
1) لا حاجة للتهيئة @Names
مع قيمة سلسلة فارغة.
2) لا حاجة لتجريد فاصل إضافي في النهاية.
- سيعطي الحل أعلاه نتائج غير صحيحة إذا كان الصف يحتوي على باطل قيمة الاسم (إذا كان هناك باطل, ، ال باطل سيجعل
@Names
باطل بعد هذا الصف، وسيبدأ الصف التالي من جديد كسلسلة فارغة مرة أخرى.يمكن إصلاحه بسهولة باستخدام أحد الحلين:
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
اعتمادًا على السلوك الذي تريده (الخيار الأول يقوم فقط بتصفية باطلبعد الخروج، الخيار الثاني يبقيهم في القائمة برسالة علامة [استبدل "N/A" بما هو مناسب لك]).
طريقة واحدة لم تظهر بعد عبر 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
SQL Server 2017+ وSQL Azure:STRING_AGG
بدءًا من الإصدار التالي من 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;
في SQL خادم 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 abc@gmail.com
2 NULL
3 def@gmail.com, xyz@gmail.com
وهذا العمل حتى البيانات يحتوي على أحرف XML غير صالحة
وعلى '"},{"_":"'
غير آمنة لأنه إذا تحتوي '"},{"_":"',
لك البيانات التي سيتم هرب إلى "},{\"_\":\"
ويمكنك استبدال ', '
مع أي فاصل سلسلة
وفي SQL Server 2017، قاعدة البيانات SQL أزور
ويمكنك استخدام STRING_AGG وظيفة
في الخلية هناك وظيفة، <وأ href = "http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat" يختلط = "noreferrer" > 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
استخدم على تلتحم م> - يمكنك <لأ href = "https://msdn.microsoft.com/en-IN/library/ms190349.aspx" يختلط = "noreferrer" > تعرف على المزيد من هنا
للحصول على مثال: م>
<اقتباس فقرة>102
103
104
اقتباس فقرة>وثم كتابة رمز أدناه في مزود خدمة،
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
والمصفوفات بوستجرس مروعة. مثال:
إنشاء بعض بيانات الاختبار:
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)
وحررت
ومنذ كيو 9.0 هو href="https://stackoverflow.com/a/13425819/952135"> .
وأوراكل 11g الإصدار 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 حرفا.
في 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
واقترح حل 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
وتحتاج إلى إنشاء المتغير الذي سيعقد النتيجة النهائية الخاصة بك وحدد في ذلك، مثل ذلك.
تحليل أسهل الحل
DECLARE @char VARCHAR(MAX);
SELECT @char = COALESCE(@char + ', ' + [column], [column])
FROM [table];
PRINT @char;
وبدءا كيو 9.0 وهذا هو بسيط جدا:
select string_agg(name, ',')
from names;
في الإصدارات قبل يمكن استخدام 9.0 array_agg()
كما يتضح من hgmnz
في SQL خادم vNext وسيتم بناء هذا في مع وظيفة STRING_AGG، قراءة المزيد حول هذا الموضوع هنا: https://msdn.microsoft.com/en-us/library/mt790580.aspx
وعن طريق XML ساعدني في الحصول على الصفوف فصل بفواصل. للفاصلة إضافية يمكننا استخدام وظيفة استبدال من SQL Server. بدلا من إضافة فاصلة، واستخدام "البيانات () 'وAS سلسلة الصفوف مع مسافات، والتي في وقت لاحق يمكن استبدال الفواصل كما بناء الجملة مكتوبة أدناه.
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 جدا (ولكن يهمني أن تنتظر حتى أعود إلى العمل لأن جوجل لا تفعل الاشياء العمل في المنزل: -)
ومع إجابات أخرى، يجب أن يكون الشخص الذي يقرأ الإجابة على علم الجدول مجال معين مثل السيارة أو الطالب. يجب إنشاء الجدول وملؤها مع البيانات لاختبار الحل.
وفيما يلي مثال يستخدم SQL خادم "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
لأوراكل قواعد بيانات، انظر هذا السؤال: <لأ href = "https://stackoverflow.com/questions/1076011/how-can-multiple-rows-be-concatenated-into-one-in-oracle-without- خلق واحد في ستور "> كيف يمكن أن تكون متصلا صفوف متعددة في واحد في أوراكل دون إنشاء إجراء مخزن؟
وأفضل إجابة على ما يبدو من قبلEmmanuel، وذلك باستخدام وظيفة المدمج في LISTAGG ()، وهي متاحة في أوراكل 11g الإصدار 2 والإصدارات الأحدث.
SELECT question_id,
LISTAGG(element_id, ',') WITHIN GROUP (ORDER BY element_id)
FROM YOUR_TABLE;
GROUP BY question_id
وأشار وكما @ user762952 بها، وفقا لوثائق أوراكل http://www.oracle-base.com/articles/misc/string-aggregation-techniques.php أو وظيفة WM_CONCAT () هو أيضا خيارا. يبدو مستقرا، ولكن توصي أوراكل صراحة ضد استخدامه لأي SQL التطبيق، وذلك باستخدام على مسؤوليتك الخاصة.
وبخلاف ذلك، سيكون لديك لكتابة وظيفة الخاصة بك؛ وثيقة أوراكل فوق لديه دليل على كيفية القيام بذلك.
أنا حقا أحب أناقة جواب دانا.أردت فقط أن تجعلها كاملة.
DECLARE @names VARCHAR(MAX)
SET @names = ''
SELECT @names = @names + ', ' + Name FROM Names
-- Deleting last two symbols (', ')
SET @sSql = LEFT(@sSql, LEN(@sSql) - 1)
لتجنب القيم الخالية يمكنك استخدام CONCAT ()
DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name)
FROM Names
select @names
ستتطلب هذه الإجابة بعض الامتيازات في الخادم حتى تعمل.
الجمعيات هي خيار جيد بالنسبة لك.هناك الكثير من المواقع التي تشرح كيفية إنشائها.الذي أعتقد أنه تم شرحه جيدًا هو هذا واحد
إذا أردت، فقد قمت بالفعل بإنشاء التجميع، ومن الممكن تنزيل ملف DLL هنا.
بمجرد تنزيله، ستحتاج إلى تشغيل البرنامج النصي التالي في 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
نأمل أن يساعد!!!
وعادة ما تستخدم اختيار مثل هذا لسلسلة سلاسل في 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
إذا كنت ترغب في التعامل مع القيم الخالية يمكنك القيام بذلك عن طريق إضافة بند فيها أو إضافة تلتحم آخر حول أول واحد.
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(COALESCE(@Names + ', ', '') + Name, @Names) FROM People
مثال 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
وهذا يمكن أن يكون مفيدا للغاية
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
تنطبق هذه الطريقة على قاعدة بيانات Teradata Aster فقط لأنها تستخدم وظيفة NPATH الخاصة بها.
مرة أخرى، لدينا طلاب الجدول
SubjectID StudentName
---------- -------------
1 Mary
1 John
1 Sam
2 Alaina
2 Edward
ثم مع NPATH يكون مجرد تحديد واحد:
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]