तार / समतल करने के लिए इष्टतम तरीका


103

मैं अलग-अलग पंक्तियों से एक पंक्ति में तार एकत्र करने का एक तरीका खोज रहा हूं। मैं कई अलग-अलग जगहों पर ऐसा करना चाह रहा हूं, इसलिए इसे सुविधाजनक बनाने के लिए एक समारोह होना अच्छा होगा। मैंने समाधानों का उपयोग करने की कोशिश की है COALESCEऔर FOR XML, लेकिन उन्होंने इसे मेरे लिए नहीं काटा है।

स्ट्रिंग एकत्रीकरण कुछ इस तरह होगा:

id | Name                    Result: id | Names
-- - ----                            -- - -----
1  | Matt                            1  | Matt, Rocks
1  | Rocks                           2  | Stylus
2  | Stylus

मैंने सीएलआर-परिभाषित एग्रीगेट कार्यों के लिए एक प्रतिस्थापन के रूप में COALESCEऔर पर एक नज़र डाली हैFOR XML , लेकिन जाहिर तौर पर एसक्यूएल अज़ूर सीएलआर-परिभाषित सामान का समर्थन नहीं करता है , जो मेरे लिए एक दर्द है क्योंकि मुझे पता है कि इसका उपयोग करने में सक्षम होने से यह पूरी तरह से हल हो जाएगा मेरे लिए समस्याएँ।

क्या कोई संभव समाधान है, या इसी तरह का इष्टतम तरीका है (जो सीएलआर के रूप में इष्टतम नहीं हो सकता है, लेकिन हे मैं वह ले लूंगा जो मुझे मिल सकता है) जिसे मैं अपना सामान एकत्र करने के लिए उपयोग कर सकता हूं?


किस तरह से for xmlआपके लिए काम नहीं करता है ?
मिकेल एरिकसन

4
यह काम करता है, लेकिन मैंने निष्पादन योजना पर एक नज़र डाली और प्रत्येक for xmlने क्वेरी प्रदर्शन (क्वेरी का एक थोक!) के संदर्भ में 25% उपयोग दिखाया
मैट

2
for xml pathक्वेरी करने के विभिन्न तरीके हैं । कुछ दूसरों की तुलना में तेजी से। यह आपके डेटा पर निर्भर हो सकता है, लेकिन उपयोग करने वाले distinctमेरे अनुभव को उपयोग करने की तुलना में धीमा है group by। और यदि आप उपयोग किए .value('.', nvarchar(max))गए मानों को प्राप्त करने के लिए उपयोग कर रहे हैं, तो आपको इसे बदल देना चाहिए.value('./text()[1]', nvarchar(max))
Mikael Eriksson

3
आपका स्वीकार किए जाते हैं जवाब मेरे जैसा दिखता जवाब पर stackoverflow.com/questions/11137075/... जो मैंने सोचा था कि तेजी से एक्सएमएल से है। क्वेरी लागत से मूर्ख मत बनो, आपको यह देखने के लिए पर्याप्त डेटा चाहिए। XML तेज है, जो उसी प्रश्न पर @ MikaelEriksson का उत्तर है । एक्सएमएल दृष्टिकोण के लिए ऑप्ट
माइकल बुएन

2
कृपया यहां के लिए एक देशी समाधान के लिए वोट करें: connect.microsoft.com/SQLServer/feedback/details/1026336
JohnLeevan

जवाबों:


67

उपाय

इष्टतम की परिभाषा अलग-अलग हो सकती है, लेकिन यहां नियमित लेन-देन एसक्यूएल का उपयोग करके अलग-अलग पंक्तियों से तारों को कैसे बदलना है, जो एज़्योर में ठीक काम करना चाहिए।

;WITH Partitioned AS
(
    SELECT 
        ID,
        Name,
        ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Name) AS NameNumber,
        COUNT(*) OVER (PARTITION BY ID) AS NameCount
    FROM dbo.SourceTable
),
Concatenated AS
(
    SELECT 
        ID, 
        CAST(Name AS nvarchar) AS FullName, 
        Name, 
        NameNumber, 
        NameCount 
    FROM Partitioned 
    WHERE NameNumber = 1

    UNION ALL

    SELECT 
        P.ID, 
        CAST(C.FullName + ', ' + P.Name AS nvarchar), 
        P.Name, 
        P.NameNumber, 
        P.NameCount
    FROM Partitioned AS P
        INNER JOIN Concatenated AS C 
                ON P.ID = C.ID 
                AND P.NameNumber = C.NameNumber + 1
)
SELECT 
    ID,
    FullName
FROM Concatenated
WHERE NameNumber = NameCount

व्याख्या

दृष्टिकोण तीन चरणों तक उबलता है:

  1. का उपयोग कर पंक्तियों की संख्या OVERऔर PARTITIONसमूहन और उन्हें आदेश के रूप में की जरूरत है। परिणाम PartitionedCTE है। हम बाद में परिणामों को फ़िल्टर करने के लिए प्रत्येक विभाजन में पंक्तियों की गिनती रखते हैं।

  2. पुनरावर्ती CTE ( Concatenated) का उपयोग करके पंक्ति संख्याओं ( NameNumberस्तंभ) के माध्यम से पुनरावृति स्तंभ में Nameमान जोड़ते हैं FullName

  3. सभी परिणामों को फ़िल्टर करें लेकिन सबसे अधिक वाले NameNumber

कृपया ध्यान रखें कि इस क्वेरी को प्रेडिक्टेबल बनाने के लिए दोनों को समूहीकरण (उदाहरण के लिए, आपके परिदृश्य में पंक्तियों को समान रूप IDसे समाहित किया गया है) और सॉर्ट करना है (मैंने माना कि आप स्ट्रिंग को वर्णानुक्रम से पहले क्रमबद्ध करते हैं)।

मैंने निम्न डेटा के साथ SQL सर्वर 2012 पर समाधान का त्वरित परीक्षण किया है:

INSERT dbo.SourceTable (ID, Name)
VALUES 
(1, 'Matt'),
(1, 'Rocks'),
(2, 'Stylus'),
(3, 'Foo'),
(3, 'Bar'),
(3, 'Baz')

क्वेरी परिणाम:

ID          FullName
----------- ------------------------------
2           Stylus
3           Bar, Baz, Foo
1           Matt, Rocks

5
मैंने xmlpath के खिलाफ इस तरह से समय की खपत की जाँच की और मैं लगभग 4 मिलीसेकंड बनाम लगभग 54 मिलीसेकंड तक पहुँच गया। इसलिए xmplath तरीका विशेष रूप से बड़े मामलों में बेहतर है। मैं एक अलग उत्तर में तुलना कोड लिखूंगा।
QMaster

यह बेहतर है क्योंकि यह दृष्टिकोण केवल अधिकतम 100 मानों के लिए काम करता है।
रोमानो ज़ुम्बे

@ romano-zumbé MAXRECURSION का उपयोग CTE की सीमा को सेट करने के लिए जो भी आपको चाहिए।
सर्ज बेलोव

1
हैरानी की बात है, सीटीई मेरे लिए धीमी थी। sqlperformance.com/2014/08/t-sql-queries/… तकनीकों के एक समूह की तुलना करता है, और मेरे परिणामों से सहमत होता है।
निकोले

1 मिलियन से अधिक रिकॉर्ड वाली तालिका के लिए यह समाधान काम नहीं करता है। इसके अलावा, हमारे पास पुनरावर्ती गहराई पर एक सीमा है
अरदलन शाहघोली

52

क्या XML पथ का उपयोग करने के तरीके वास्तव में धीमे हैं? इत्ज़िक बेन-गान लिखते हैं कि इस पद्धति का उनकी टी-एसक्यूएल क्वेरी बुक में अच्छा प्रदर्शन है (श्री बेन-गान एक भरोसेमंद स्रोत है, मेरे विचार में)।

create table #t (id int, name varchar(20))

insert into #t
values (1, 'Matt'), (1, 'Rocks'), (2, 'Stylus')

select  id
        ,Names = stuff((select ', ' + name as [text()]
        from #t xt
        where xt.id = t.id
        for xml path('')), 1, 2, '')
from #t t
group by id

idतालिका का आकार एक समस्या बन जाने के बाद, उस कॉलम पर एक इंडेक्स डालना न भूलें ।
milivojeviCH

1
और xml पथ कार्य ( stackoverflow.com/a/31212160/1026 ) के लिए कैसे सामान / पढ़ने के बाद , मुझे विश्वास है कि यह अपने नाम में XML के बावजूद एक अच्छा समाधान है :)
निकोले

1
@slackterman रिकॉर्ड किए जाने वाले रिकॉर्ड की संख्या पर निर्भर करता है। मुझे लगता है कि XML, CTE की तुलना में कम काउंट्स में डिफेक्ट है, लेकिन ऊपरी वॉल्यूम काउंट्स पर, रिकर्सन डिपार्टमेंट लिमिट को कम करता है और नेविगेट करना आसान है, अगर सही तरीके से और सक्सेसफुल तरीके से किया जाए।
गोल्डबिशप

अगर आप अपने डेटा में emojis या विशेष / सरोगेट अक्षर हैं XML XML तरीकों के लिए झटका !!!
डेविनबॉस्ट

1
इस कोड के परिणामस्वरूप xml- एन्कोडेड पाठ ( &स्विच किया गया &, और इसी तरह)। एक अधिक सही for xmlसमाधान यहाँ प्रदान किया गया है
फ्रेडरिक

34

हममें से जिन्होंने यह पाया और Azure SQL डेटाबेस का उपयोग नहीं कर रहे हैं:

STRING_AGG()PostgreSQL, SQL Server 2017 और Azure SQL
https://www.postgresql.org/docs/current/static/functions-aggregate.html
https://docs.microsoft.com/en-us/sql/t-sql/ पर कार्य / स्ट्रिंग-agg-Transact-SQL

GROUP_CONCAT()MySQL में
http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_group-concat

(Azure अपडेट के लिए @Brianjorden और @milanio को धन्यवाद)

उदाहरण कोड:

select Id
, STRING_AGG(Name, ', ') Names 
from Demo
group by Id

एसक्यूएल फिडल: http://sqlfiddle.com/# -18/89251/1


1
मैंने अभी इसका परीक्षण किया है और अब यह Azure SQL डेटाबेस के साथ ठीक काम करता है।
मिलनियो

5
STRING_AGG2017 को वापस धकेल दिया गया। यह 2016 में उपलब्ध नहीं है।
मॉर्गन थ्राप्प

1
SQL सर्वर संस्करण परिवर्तन के लिए आमिर और मॉर्गन थ्रैप धन्यवाद। अपडेट किया गया। (लेखन के समय यह संस्करण 2016 में समर्थित होने का दावा किया गया था।)
हैर्बकी

25

हालाँकि @serge का उत्तर सही है, लेकिन मैंने xmlpath के खिलाफ उनके तरीके की समय की खपत की तुलना की और मैंने पाया कि xmlpath बहुत तेज़ है। मैं तुलना कोड लिखूंगा और आप इसे स्वयं देख सकते हैं। यह @ रास्ता है:

DECLARE @startTime datetime2;
DECLARE @endTime datetime2;
DECLARE @counter INT;
SET @counter = 1;

set nocount on;

declare @YourTable table (ID int, Name nvarchar(50))

WHILE @counter < 1000
BEGIN
    insert into @YourTable VALUES (ROUND(@counter/10,0), CONVERT(NVARCHAR(50), @counter) + 'CC')
    SET @counter = @counter + 1;
END

SET @startTime = GETDATE()

;WITH Partitioned AS
(
    SELECT 
        ID,
        Name,
        ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Name) AS NameNumber,
        COUNT(*) OVER (PARTITION BY ID) AS NameCount
    FROM @YourTable
),
Concatenated AS
(
    SELECT ID, CAST(Name AS nvarchar) AS FullName, Name, NameNumber, NameCount FROM Partitioned WHERE NameNumber = 1

    UNION ALL

    SELECT 
        P.ID, CAST(C.FullName + ', ' + P.Name AS nvarchar), P.Name, P.NameNumber, P.NameCount
    FROM Partitioned AS P
        INNER JOIN Concatenated AS C ON P.ID = C.ID AND P.NameNumber = C.NameNumber + 1
)
SELECT 
    ID,
    FullName
FROM Concatenated
WHERE NameNumber = NameCount

SET @endTime = GETDATE();

SELECT DATEDIFF(millisecond,@startTime, @endTime)
--Take about 54 milliseconds

और यह xmlpath तरीका है:

DECLARE @startTime datetime2;
DECLARE @endTime datetime2;
DECLARE @counter INT;
SET @counter = 1;

set nocount on;

declare @YourTable table (RowID int, HeaderValue int, ChildValue varchar(5))

WHILE @counter < 1000
BEGIN
    insert into @YourTable VALUES (@counter, ROUND(@counter/10,0), CONVERT(NVARCHAR(50), @counter) + 'CC')
    SET @counter = @counter + 1;
END

SET @startTime = GETDATE();

set nocount off
SELECT
    t1.HeaderValue
        ,STUFF(
                   (SELECT
                        ', ' + t2.ChildValue
                        FROM @YourTable t2
                        WHERE t1.HeaderValue=t2.HeaderValue
                        ORDER BY t2.ChildValue
                        FOR XML PATH(''), TYPE
                   ).value('.','varchar(max)')
                   ,1,2, ''
              ) AS ChildValues
    FROM @YourTable t1
    GROUP BY t1.HeaderValue

SET @endTime = GETDATE();

SELECT DATEDIFF(millisecond,@startTime, @endTime)
--Take about 4 milliseconds

2
+1, आप QMaster (डार्क आर्ट्स के) आप! मुझे और भी नाटकीय रूप मिला। (~ 3000 msec CTE बनाम ~ 70 मिसे XML पर SQL Server 2008 R2 पर Windows Server 2008 R2 पर Intel Xeon E5-2630 v4 @ 2.20 GHZ x2 w / ~ 1 GB मुफ्त)। केवल सुझाव हैं: 1) दोनों संस्करणों के लिए या तो ओपी (या अधिमानतः) जेनेरिक शब्दों का उपयोग करें, 2) चूंकि ओपी का क्यू है कि "कंसट्रेट / एग्रीगेट स्ट्रिंग्स " कैसे है और यह केवल स्ट्रिंग्स के लिए आवश्यक है (बनाम एक संख्यात्मक मान, जेनेरिक) शर्तें बहुत सामान्य हैं। बस "GroupNumber" और "StringValue" का उपयोग करें, 3) घोषणा करें और "Delimiter" चर का उपयोग करें और "Len (Delimiter)" बनाम "2" का उपयोग करें।
टॉम

1
XML एन्कोडिंग के लिए विशेष वर्ण का विस्तार नहीं करने के लिए +1 (जैसे 'और' का विस्तार 'और कई अन्य हीन समाधानों की तरह नहीं है)
उलटा इंजीनियर

13

अपडेट: एमएस SQL ​​सर्वर 2017+, एज़्योर SQL डेटाबेस

आप उपयोग कर सकते हैं STRING_AGG:।

ओपी के अनुरोध के लिए उपयोग बहुत सरल है:

SELECT id, STRING_AGG(name, ', ') AS names
FROM some_table
GROUP BY id

अधिक पढ़ें

खैर मेरे पुराने गैर-उत्तर को सही तरीके से हटा दिया गया (नीचे इन-टैक छोड़ दिया गया), लेकिन अगर भविष्य में कोई भी यहां उतरने के लिए होता है, तो अच्छी खबर है। उन्होंने Azure SQL डेटाबेस में STRING_AGG () को भी शामिल किया है। मूल और अंतर्निहित समर्थन के साथ इस पोस्ट में मूल रूप से अनुरोध की गई सटीक कार्यक्षमता प्रदान करनी चाहिए। @hrobky ने उस समय SQL सर्वर 2016 सुविधा के रूप में इसका उल्लेख किया था।

--- पुरानी पोस्ट: सीधे @hrobky को जवाब देने के लिए यहां पर्याप्त प्रतिष्ठा नहीं है, लेकिन STRING_AGG बहुत अच्छा लग रहा है, हालांकि यह केवल SQL Server 2016 vNext में उपलब्ध है। उम्मीद है कि यह जल्द ही Azure SQL Datababse को फॉलो करेगी।


2
मैंने अभी इसका परीक्षण किया है और यह Azure SQL Database
milanio

4
STRING_AGG()किसी भी संगतता स्तर में SQL सर्वर 2017 में उपलब्ध होने के लिए कहा गया है। docs.microsoft.com/en-us/sql/t-sql/functions/…
उपयोगकर्ता

1
हाँ। STRING_AGG SQL Server 2016 में उपलब्ध नहीं है।
Magne

2

आप उदाहरण के लिए, तार को जोड़ने के लिए + का उपयोग कर सकते हैं:

declare @test nvarchar(max)
set @test = ''
select @test += name from names

यदि आप @test का चयन करते हैं, तो यह आपको संक्षिप्त नाम देगा


जब यह समर्थित है तब से कृपया SQL बोली या संस्करण निर्दिष्ट करें।
होर्बकी

यह SQL Server 2012 में काम करता है। ध्यान दें कि अल्पविराम से अलग की गई सूची बनाई जा सकती हैselect @test += name + ', ' from names
आर्ट श्मिट

4
यह अपरिभाषित व्यवहार का उपयोग करता है, और सुरक्षित नहीं है। यह विशेष रूप से एक अजीब / गलत परिणाम देने की संभावना है यदि आपके पास ORDER BYआपकी क्वेरी में है। आपको सूचीबद्ध विकल्पों में से एक का उपयोग करना चाहिए।
दन्नन्नो

1
इस प्रकार के क्वेरी को कभी भी परिभाषित व्यवहार नहीं किया गया था, और SQL सर्वर 2019 में हमने पाया कि पूर्व संस्करणों की तुलना में गलत व्यवहार लगातार अधिक था। इस दृष्टिकोण का उपयोग न करें।
मैथ्यू रोडेटस

2

मुझे सर्ज का उत्तर बहुत आशाजनक लग रहा था, लेकिन जैसा कि लिखा गया था, उसके साथ मुझे प्रदर्शन के मुद्दों का भी सामना करना पड़ा। हालांकि, जब मैंने इसे अस्थायी तालिकाओं का उपयोग करने के लिए पुनर्गठित किया और इसमें डबल सीटीई टेबल शामिल नहीं थे, तो प्रदर्शन 1000 मिनट के रिकॉर्ड के लिए 1 मिनट 40 सेकंड से उप-सेकंड तक चला गया। यह SQL सर्वर के पुराने संस्करणों पर XML के लिए ऐसा करने की आवश्यकता वाले किसी भी व्यक्ति के लिए है:

DECLARE @STRUCTURED_VALUES TABLE (
     ID                 INT
    ,VALUE              VARCHAR(MAX) NULL
    ,VALUENUMBER        BIGINT
    ,VALUECOUNT         INT
);

INSERT INTO @STRUCTURED_VALUES
SELECT   ID
        ,VALUE
        ,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY VALUE) AS VALUENUMBER
        ,COUNT(*) OVER (PARTITION BY ID)    AS VALUECOUNT
FROM    RAW_VALUES_TABLE;

WITH CTE AS (
    SELECT   SV.ID
            ,SV.VALUE
            ,SV.VALUENUMBER
            ,SV.VALUECOUNT
    FROM    @STRUCTURED_VALUES SV
    WHERE   VALUENUMBER = 1

    UNION ALL

    SELECT   SV.ID
            ,CTE.VALUE + ' ' + SV.VALUE AS VALUE
            ,SV.VALUENUMBER
            ,SV.VALUECOUNT
    FROM    @STRUCTURED_VALUES SV
    JOIN    CTE 
        ON  SV.ID = CTE.ID
        AND SV.VALUENUMBER = CTE.VALUENUMBER + 1

)
SELECT   ID
        ,VALUE
FROM    CTE
WHERE   VALUENUMBER = VALUECOUNT
ORDER BY ID
;
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.